aboutsummaryrefslogtreecommitdiff
path: root/src/nvim
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim')
-rw-r--r--src/nvim/CMakeLists.txt64
-rw-r--r--src/nvim/api/buffer.c368
-rw-r--r--src/nvim/api/private/helpers.c126
-rw-r--r--src/nvim/api/private/helpers.h14
-rw-r--r--src/nvim/api/vim.c167
-rw-r--r--src/nvim/auevents.lua6
-rw-r--r--src/nvim/buffer.c116
-rw-r--r--src/nvim/buffer_defs.h39
-rw-r--r--src/nvim/change.c42
-rw-r--r--src/nvim/channel.c30
-rw-r--r--src/nvim/context.c2
-rw-r--r--src/nvim/cursor.c11
-rw-r--r--src/nvim/diff.c36
-rw-r--r--src/nvim/diff.h7
-rw-r--r--src/nvim/digraph.c237
-rw-r--r--src/nvim/edit.c84
-rw-r--r--src/nvim/eval.c656
-rw-r--r--src/nvim/eval.h1
-rw-r--r--src/nvim/eval.lua3
-rw-r--r--src/nvim/eval/typval.c2
-rw-r--r--src/nvim/event/libuv_process.c2
-rw-r--r--src/nvim/event/loop.c4
-rw-r--r--src/nvim/event/process.h1
-rw-r--r--src/nvim/ex_cmds.c332
-rw-r--r--src/nvim/ex_cmds.lua28
-rw-r--r--src/nvim/ex_cmds2.c102
-rw-r--r--src/nvim/ex_cmds_defs.h2
-rw-r--r--src/nvim/ex_docmd.c943
-rw-r--r--src/nvim/ex_getln.c96
-rw-r--r--src/nvim/ex_getln.h2
-rw-r--r--src/nvim/file_search.c4
-rw-r--r--src/nvim/fileio.c66
-rw-r--r--src/nvim/fold.c5
-rw-r--r--src/nvim/generators/gen_char_blob.lua70
-rwxr-xr-xsrc/nvim/generators/gen_declarations.lua4
-rw-r--r--src/nvim/getchar.c19
-rw-r--r--src/nvim/globals.h13
-rw-r--r--src/nvim/hashtab.h2
-rw-r--r--src/nvim/highlight.c31
-rw-r--r--src/nvim/highlight_defs.h3
-rw-r--r--src/nvim/lib/kbtree.h6
-rw-r--r--src/nvim/log.c1
-rw-r--r--src/nvim/lua/converter.c56
-rw-r--r--src/nvim/lua/executor.c312
-rw-r--r--src/nvim/lua/executor.h2
-rw-r--r--src/nvim/lua/treesitter.c652
-rw-r--r--src/nvim/lua/treesitter.h14
-rw-r--r--src/nvim/lua/vim.lua234
-rw-r--r--src/nvim/macros.h2
-rw-r--r--src/nvim/main.c22
-rw-r--r--src/nvim/mark.c60
-rw-r--r--src/nvim/mark_extended.c1139
-rw-r--r--src/nvim/mark_extended.h282
-rw-r--r--src/nvim/mark_extended_defs.h54
-rw-r--r--src/nvim/memline.c28
-rw-r--r--src/nvim/memory.c2
-rw-r--r--src/nvim/message.c19
-rw-r--r--src/nvim/misc1.c43
-rw-r--r--src/nvim/mouse.c29
-rw-r--r--src/nvim/move.c53
-rw-r--r--src/nvim/normal.c120
-rw-r--r--src/nvim/ops.c181
-rw-r--r--src/nvim/option.c89
-rw-r--r--src/nvim/option_defs.h22
-rw-r--r--src/nvim/options.lua28
-rw-r--r--src/nvim/os/env.c188
-rw-r--r--src/nvim/os/shell.c3
-rw-r--r--src/nvim/os/tty.c1
-rw-r--r--src/nvim/os/users.c83
-rw-r--r--src/nvim/os_unix.c2
-rw-r--r--src/nvim/path.c35
-rw-r--r--src/nvim/path.h11
-rw-r--r--src/nvim/po/af.po2
-rw-r--r--src/nvim/po/check.vim11
-rw-r--r--src/nvim/po/fi.po2
-rw-r--r--src/nvim/po/uk.po4
-rw-r--r--src/nvim/pos.h4
-rw-r--r--src/nvim/quickfix.c4634
-rw-r--r--src/nvim/regexp_nfa.c2
-rw-r--r--src/nvim/screen.c58
-rw-r--r--src/nvim/search.c164
-rw-r--r--src/nvim/search.h9
-rw-r--r--src/nvim/spell.c97
-rw-r--r--src/nvim/spellfile.c22
-rw-r--r--src/nvim/state.c2
-rw-r--r--src/nvim/strings.c14
-rw-r--r--src/nvim/syntax.c20
-rw-r--r--src/nvim/tag.c1268
-rw-r--r--src/nvim/tag.h27
-rw-r--r--src/nvim/terminal.c21
-rw-r--r--src/nvim/testdir/Makefile25
-rwxr-xr-xsrc/nvim/testdir/runnvim.sh5
-rw-r--r--src/nvim/testdir/runtest.vim5
-rw-r--r--src/nvim/testdir/setup.vim1
-rw-r--r--src/nvim/testdir/shared.vim101
-rw-r--r--src/nvim/testdir/test42.inbin2438 -> 2373 bytes
-rw-r--r--src/nvim/testdir/test48.in82
-rw-r--r--src/nvim/testdir/test48.ok23
-rw-r--r--src/nvim/testdir/test49.vim2
-rw-r--r--src/nvim/testdir/test_alot.vim3
-rw-r--r--src/nvim/testdir/test_assert.vim35
-rw-r--r--src/nvim/testdir/test_autocmd.vim106
-rw-r--r--src/nvim/testdir/test_backup.vim58
-rw-r--r--src/nvim/testdir/test_cindent.vim60
-rw-r--r--src/nvim/testdir/test_clientserver.vim13
-rw-r--r--src/nvim/testdir/test_cmdline.vim132
-rw-r--r--src/nvim/testdir/test_compiler.vim10
-rw-r--r--src/nvim/testdir/test_const.vim20
-rw-r--r--src/nvim/testdir/test_debugger.vim56
-rw-r--r--src/nvim/testdir/test_diffmode.vim25
-rw-r--r--src/nvim/testdir/test_digraph.vim13
-rw-r--r--src/nvim/testdir/test_display.vim56
-rw-r--r--src/nvim/testdir/test_exit.vim88
-rw-r--r--src/nvim/testdir/test_expr.vim3
-rw-r--r--src/nvim/testdir/test_filetype.vim5
-rw-r--r--src/nvim/testdir/test_fnamemodify.vim75
-rw-r--r--src/nvim/testdir/test_fold.vim35
-rw-r--r--src/nvim/testdir/test_functions.vim7
-rw-r--r--src/nvim/testdir/test_gf.vim25
-rw-r--r--src/nvim/testdir/test_gn.vim27
-rw-r--r--src/nvim/testdir/test_goto.vim406
-rw-r--r--src/nvim/testdir/test_help.vim6
-rw-r--r--src/nvim/testdir/test_ins_complete.vim18
-rw-r--r--src/nvim/testdir/test_join.vim21
-rw-r--r--src/nvim/testdir/test_let.vim162
-rw-r--r--src/nvim/testdir/test_mksession_utf8.vim57
-rw-r--r--src/nvim/testdir/test_normal.vim491
-rw-r--r--src/nvim/testdir/test_options.vim6
-rw-r--r--src/nvim/testdir/test_popup.vim53
-rw-r--r--src/nvim/testdir/test_profile.vim268
-rw-r--r--src/nvim/testdir/test_quickfix.vim1261
-rw-r--r--src/nvim/testdir/test_quotestar.vim22
-rw-r--r--src/nvim/testdir/test_recover.vim6
-rw-r--r--src/nvim/testdir/test_registers.vim86
-rw-r--r--src/nvim/testdir/test_search.vim4
-rw-r--r--src/nvim/testdir/test_spell.vim37
-rw-r--r--src/nvim/testdir/test_startup.vim150
-rw-r--r--src/nvim/testdir/test_statusline.vim22
-rw-r--r--src/nvim/testdir/test_substitute.vim12
-rw-r--r--src/nvim/testdir/test_swap.vim84
-rw-r--r--src/nvim/testdir/test_syntax.vim9
-rw-r--r--src/nvim/testdir/test_tagfunc.vim84
-rw-r--r--src/nvim/testdir/test_tagjump.vim47
-rw-r--r--src/nvim/testdir/test_textobjects.vim10
-rw-r--r--src/nvim/testdir/test_timers.vim17
-rw-r--r--src/nvim/testdir/test_vimscript.vim72
-rw-r--r--src/nvim/testdir/test_virtualedit.vim144
-rw-r--r--src/nvim/testdir/test_window_cmd.vim135
-rw-r--r--src/nvim/testdir/view_util.vim1
-rw-r--r--src/nvim/tui/input.c23
-rw-r--r--src/nvim/tui/input.h5
-rw-r--r--src/nvim/tui/terminfo.c3
-rw-r--r--src/nvim/tui/tui.c57
-rw-r--r--src/nvim/ui_compositor.c16
-rw-r--r--src/nvim/undo.c77
-rw-r--r--src/nvim/undo_defs.h16
-rw-r--r--src/nvim/version.c80
-rw-r--r--src/nvim/window.c81
158 files changed, 13762 insertions, 5257 deletions
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index aa8100873b..bc8e64dd41 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -52,7 +52,8 @@ set(UNICODE_TABLES_GENERATOR ${GENERATOR_DIR}/gen_unicode_tables.lua)
set(UNICODE_DIR ${PROJECT_SOURCE_DIR}/unicode)
set(GENERATED_UNICODE_TABLES ${GENERATED_DIR}/unicode_tables.generated.h)
set(VIM_MODULE_FILE ${GENERATED_DIR}/lua/vim_module.generated.h)
-set(VIM_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/src/nvim/lua/vim.lua)
+set(LUA_VIM_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/src/nvim/lua/vim.lua)
+set(LUA_SHARED_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/shared.lua)
set(CHAR_BLOB_GENERATOR ${GENERATOR_DIR}/gen_char_blob.lua)
set(LINT_SUPPRESS_FILE ${PROJECT_BINARY_DIR}/errors.json)
set(LINT_SUPPRESS_URL_BASE "https://raw.githubusercontent.com/neovim/doc/gh-pages/reports/clint")
@@ -85,6 +86,10 @@ file(GLOB NVIM_HEADERS *.h)
file(GLOB XDIFF_SOURCES xdiff/*.c)
file(GLOB XDIFF_HEADERS xdiff/*.h)
+file(GLOB TREESITTER_SOURCES ../tree_sitter/*.c)
+file(GLOB TS_SOURCE_AMALGAM ../tree_sitter/lib.c)
+list(REMOVE_ITEM TREESITTER_SOURCES ${TS_SOURCE_AMALGAM})
+
foreach(subdir
os
api
@@ -141,6 +146,7 @@ set(CONV_SOURCES
ex_cmds.c
ex_docmd.c
fileio.c
+ lua/treesitter.c
mbyte.c
memline.c
message.c
@@ -172,6 +178,9 @@ if(NOT MSVC)
set_source_files_properties(
eval.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion")
endif()
+
+ # tree-sitter: inlined external project, we don't maintain it. #10124
+ set_source_files_properties(${TREESITTER_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion -Wno-pedantic -Wno-shadow -Wno-missing-prototypes -Wno-unused-variable")
endif()
if(NOT "${MIN_LOG_LEVEL}" MATCHES "^$")
@@ -209,6 +218,34 @@ function(get_preproc_output varname iname)
endif()
endfunction()
+# Handle generating version from Git.
+set(use_git_version 0)
+if(NVIM_VERSION_MEDIUM)
+ message(STATUS "NVIM_VERSION_MEDIUM: ${NVIM_VERSION_MEDIUM}")
+elseif(${CMAKE_VERSION} VERSION_LESS "3.2.0")
+ message(STATUS "Skipping version-string generation (requires CMake 3.2.0+)")
+elseif(EXISTS ${PROJECT_SOURCE_DIR}/.git)
+ find_program(GIT_EXECUTABLE git)
+ if(GIT_EXECUTABLE)
+ message(STATUS "Using NVIM_VERSION_MEDIUM from Git")
+ set(use_git_version 1)
+ else()
+ message(STATUS "Skipping version-string generation (cannot find git)")
+ endif()
+endif()
+if(use_git_version)
+ # Create a update_version_stamp target to update the version during build.
+ file(RELATIVE_PATH relbuild "${PROJECT_SOURCE_DIR}" "${CMAKE_BINARY_DIR}")
+ add_custom_target(update_version_stamp ALL
+ COMMAND ${LUA_PRG} scripts/update_version_stamp.lua
+ ${relbuild}/config/auto/versiondef_git.h
+ "v${NVIM_VERSION_MAJOR}.${NVIM_VERSION_MINOR}.${NVIM_VERSION_PATCH}"
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+ BYPRODUCTS ${CMAKE_BINARY_DIR}/config/auto/versiondef_git.h)
+else()
+ file(WRITE ${CMAKE_BINARY_DIR}/config/auto/versiondef_git.h "")
+endif()
+
# NVIM_GENERATED_FOR_HEADERS: generated headers to be included in headers
# NVIM_GENERATED_FOR_SOURCES: generated headers to be included in sources
# NVIM_GENERATED_SOURCES: generated source files
@@ -237,12 +274,16 @@ foreach(sfile ${NVIM_SOURCES}
get_preproc_output(PREPROC_OUTPUT ${gf_i})
+ set(depends "${HEADER_GENERATOR}" "${sfile}")
+ if(use_git_version AND "${f}" STREQUAL "version.c")
+ # Ensure auto/versiondef_git.h exists after "make clean".
+ list(APPEND depends update_version_stamp)
+ endif()
add_custom_command(
OUTPUT "${gf_c_h}" "${gf_h_h}"
COMMAND ${CMAKE_C_COMPILER} ${sfile} ${PREPROC_OUTPUT} ${gen_cflags} ${C_FLAGS_ARRAY}
COMMAND "${LUA_PRG}" "${HEADER_GENERATOR}" "${sfile}" "${gf_c_h}" "${gf_h_h}" "${gf_i}"
- DEPENDS "${HEADER_GENERATOR}" "${sfile}"
- )
+ 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$")
@@ -277,11 +318,13 @@ add_custom_command(
add_custom_command(
OUTPUT ${VIM_MODULE_FILE}
- COMMAND ${LUA_PRG} ${CHAR_BLOB_GENERATOR} ${VIM_MODULE_SOURCE}
- ${VIM_MODULE_FILE} vim_module
+ COMMAND ${LUA_PRG} ${CHAR_BLOB_GENERATOR} ${VIM_MODULE_FILE}
+ ${LUA_VIM_MODULE_SOURCE} vim_module
+ ${LUA_SHARED_MODULE_SOURCE} shared_module
DEPENDS
${CHAR_BLOB_GENERATOR}
- ${VIM_MODULE_SOURCE}
+ ${LUA_VIM_MODULE_SOURCE}
+ ${LUA_SHARED_MODULE_SOURCE}
)
list(APPEND NVIM_GENERATED_SOURCES
@@ -379,6 +422,7 @@ if(Iconv_LIBRARIES)
endif()
if(WIN32)
+ list(APPEND NVIM_LINK_LIBRARIES netapi32)
list(APPEND NVIM_LINK_LIBRARIES ${WINPTY_LIBRARIES})
endif()
@@ -395,6 +439,7 @@ list(APPEND NVIM_LINK_LIBRARIES
${LIBVTERM_LIBRARIES}
${LIBTERMKEY_LIBRARIES}
${UNIBILIUM_LIBRARIES}
+ ${UTF8PROC_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT}
)
@@ -414,12 +459,13 @@ endif()
add_executable(nvim ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS}
${NVIM_GENERATED_SOURCES} ${NVIM_SOURCES} ${NVIM_HEADERS}
- ${XDIFF_SOURCES} ${XDIFF_HEADERS})
+ ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${TREESITTER_SOURCES})
target_link_libraries(nvim ${NVIM_EXEC_LINK_LIBRARIES})
install_helper(TARGETS nvim)
set_property(TARGET nvim APPEND PROPERTY
INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS})
+set_property(TARGET nvim PROPERTY ENABLE_EXPORTS TRUE)
if(ENABLE_LTO AND (POLICY CMP0069))
include(CheckIPOSupported)
@@ -500,7 +546,7 @@ add_library(
EXCLUDE_FROM_ALL
${NVIM_SOURCES} ${NVIM_GENERATED_SOURCES}
${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS}
- ${XDIFF_SOURCES} ${XDIFF_HEADERS}
+ ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${TREESITTER_SOURCES}
)
set_property(TARGET libnvim APPEND PROPERTY
INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS})
@@ -525,7 +571,7 @@ else()
EXCLUDE_FROM_ALL
${NVIM_SOURCES} ${NVIM_GENERATED_SOURCES}
${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS}
- ${XDIFF_SOURCES} ${XDIFF_HEADERS}
+ ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${TREESITTER_SOURCES}
${UNIT_TEST_FIXTURES}
)
target_link_libraries(nvim-test ${NVIM_TEST_LINK_LIBRARIES})
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 3e1209d1b1..8f5718d97e 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -23,7 +23,10 @@
#include "nvim/memory.h"
#include "nvim/misc1.h"
#include "nvim/ex_cmds.h"
+#include "nvim/map_defs.h"
+#include "nvim/map.h"
#include "nvim/mark.h"
+#include "nvim/mark_extended.h"
#include "nvim/fileio.h"
#include "nvim/move.h"
#include "nvim/syntax.h"
@@ -101,25 +104,47 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err)
return rv;
}
-/// Activates buffer-update events on a channel, or as lua callbacks.
+/// Activates buffer-update events on a channel, or as Lua callbacks.
+///
+/// Example (Lua): capture buffer updates in a global `events` variable
+/// (use "print(vim.inspect(events))" to see its contents):
+/// <pre>
+/// events = {}
+/// vim.api.nvim_buf_attach(0, false, {
+/// on_lines=function(...) table.insert(events, {...}) end})
+/// </pre>
+///
+/// @see |nvim_buf_detach()|
+/// @see |api-buffer-updates-lua|
///
/// @param channel_id
/// @param buffer Buffer handle, or 0 for current buffer
-/// @param send_buffer Set to true if the initial notification should contain
-/// the whole buffer. If so, the first notification will be a
-/// `nvim_buf_lines_event`. Otherwise, the first notification will be
-/// a `nvim_buf_changedtick_event`. Not used for lua callbacks.
+/// @param send_buffer True if the initial notification should contain the
+/// whole buffer: first notification will be `nvim_buf_lines_event`.
+/// Else the first notification will be `nvim_buf_changedtick_event`.
+/// Not for Lua callbacks.
/// @param opts Optional parameters.
-/// - `on_lines`: lua callback received on change.
-/// - `on_changedtick`: lua callback received on changedtick
-/// increment without text change.
-/// - `utf_sizes`: include UTF-32 and UTF-16 size of
-/// the replaced region.
-/// See |api-buffer-updates-lua| for more information
+/// - on_lines: Lua callback invoked on change.
+/// Return `true` to detach. Args:
+/// - buffer handle
+/// - b:changedtick
+/// - first line that changed (zero-indexed)
+/// - last line that was changed
+/// - last line in the updated range
+/// - byte count of previous contents
+/// - deleted_codepoints (if `utf_sizes` is true)
+/// - deleted_codeunits (if `utf_sizes` is true)
+/// - on_changedtick: Lua callback invoked on changedtick
+/// increment without text change. Args:
+/// - buffer handle
+/// - b:changedtick
+/// - on_detach: Lua callback invoked on detach. Args:
+/// - buffer handle
+/// - utf_sizes: include UTF-32 and UTF-16 size of the replaced
+/// region, as args to `on_lines`.
/// @param[out] err Error details, if any
-/// @return False when updates couldn't be enabled because the buffer isn't
-/// loaded or `opts` contained an invalid key; otherwise True.
-/// TODO: LUA_API_NO_EVAL
+/// @return False if attach failed (invalid parameter, or buffer isn't loaded);
+/// otherwise True. TODO: LUA_API_NO_EVAL
Boolean nvim_buf_attach(uint64_t channel_id,
Buffer buffer,
Boolean send_buffer,
@@ -183,13 +208,14 @@ error:
/// Deactivates buffer-update events on the channel.
///
-/// For Lua callbacks see |api-lua-detach|.
+/// @see |nvim_buf_attach()|
+/// @see |api-lua-detach| for detaching Lua callbacks
///
/// @param channel_id
/// @param buffer Buffer handle, or 0 for current buffer
/// @param[out] err Error details, if any
-/// @return False when updates couldn't be disabled because the buffer
-/// isn't loaded; otherwise True.
+/// @return False if detach failed (because the buffer isn't loaded);
+/// otherwise True.
Boolean nvim_buf_detach(uint64_t channel_id,
Buffer buffer,
Error *err)
@@ -529,7 +555,8 @@ void nvim_buf_set_lines(uint64_t channel_id,
(linenr_T)(end - 1),
MAXLNUM,
(long)extra,
- false);
+ false,
+ kExtmarkUndo);
changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra, true);
fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra);
@@ -984,6 +1011,254 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
return rv;
}
+/// Returns position for a given extmark id
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param id Extmark id
+/// @param[out] err Error details, if any
+/// @return (row, col) tuple or empty list () if extmark id was absent
+ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
+ Integer id, Error *err)
+ FUNC_API_SINCE(7)
+{
+ Array rv = ARRAY_DICT_INIT;
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return rv;
+ }
+
+ if (!ns_initialized((uint64_t)ns_id)) {
+ api_set_error(err, kErrorTypeValidation, _("Invalid ns_id"));
+ return rv;
+ }
+
+ Extmark *extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id);
+ if (!extmark) {
+ return rv;
+ }
+ ADD(rv, INTEGER_OBJ((Integer)extmark->line->lnum-1));
+ ADD(rv, INTEGER_OBJ((Integer)extmark->col-1));
+ return rv;
+}
+
+/// Gets extmarks in "traversal order" from a |charwise| region defined by
+/// buffer positions (inclusive, 0-indexed |api-indexing|).
+///
+/// Region can be given as (row,col) tuples, or valid extmark ids (whose
+/// positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1)
+/// respectively, thus the following are equivalent:
+///
+/// <pre>
+/// nvim_buf_get_extmarks(0, my_ns, 0, -1, {})
+/// nvim_buf_get_extmarks(0, my_ns, [0,0], [-1,-1], {})
+/// </pre>
+///
+/// If `end` is less than `start`, traversal works backwards. (Useful
+/// with `limit`, to get the first marks prior to a given position.)
+///
+/// Example:
+///
+/// <pre>
+/// local a = vim.api
+/// local pos = a.nvim_win_get_cursor(0)
+/// local ns = a.nvim_create_namespace('my-plugin')
+/// -- Create new extmark at line 1, column 1.
+/// local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, 0, {})
+/// -- Create new extmark at line 3, column 1.
+/// local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, 0, {})
+/// -- Get extmarks only from line 3.
+/// local ms = a.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {})
+/// -- Get all marks in this buffer + namespace.
+/// local all = a.nvim_buf_get_extmarks(0, ns, 0, -1, {})
+/// print(vim.inspect(ms))
+/// </pre>
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param start Start of range, given as (row, col) or valid extmark id
+/// (whose position defines the bound)
+/// @param end End of range, given as (row, col) or valid extmark id
+/// (whose position defines the bound)
+/// @param opts Optional parameters. Keys:
+/// - limit: Maximum number of marks to return
+/// @param[out] err Error details, if any
+/// @return List of [extmark_id, row, col] tuples in "traversal order".
+Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start,
+ Object end, Dictionary opts, Error *err)
+ FUNC_API_SINCE(7)
+{
+ Array rv = ARRAY_DICT_INIT;
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return rv;
+ }
+
+ if (!ns_initialized((uint64_t)ns_id)) {
+ api_set_error(err, kErrorTypeValidation, _("Invalid ns_id"));
+ return rv;
+ }
+ Integer limit = -1;
+
+ for (size_t i = 0; i < opts.size; i++) {
+ String k = opts.items[i].key;
+ Object *v = &opts.items[i].value;
+ if (strequal("limit", k.data)) {
+ if (v->type != kObjectTypeInteger) {
+ api_set_error(err, kErrorTypeValidation, "limit is not an integer");
+ return rv;
+ }
+ limit = v->data.integer;
+ v->data.integer = LUA_NOREF;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ return rv;
+ }
+ }
+
+ if (limit == 0) {
+ return rv;
+ }
+
+
+ bool reverse = false;
+
+ linenr_T l_lnum;
+ colnr_T l_col;
+ if (!extmark_get_index_from_obj(buf, ns_id, start, &l_lnum, &l_col, err)) {
+ return rv;
+ }
+
+ linenr_T u_lnum;
+ colnr_T u_col;
+ if (!extmark_get_index_from_obj(buf, ns_id, end, &u_lnum, &u_col, err)) {
+ return rv;
+ }
+
+ if (l_lnum > u_lnum || (l_lnum == u_lnum && l_col > u_col)) {
+ reverse = true;
+ linenr_T tmp_lnum = l_lnum;
+ l_lnum = u_lnum;
+ u_lnum = tmp_lnum;
+ colnr_T tmp_col = l_col;
+ l_col = u_col;
+ u_col = tmp_col;
+ }
+
+
+ ExtmarkArray marks = extmark_get(buf, (uint64_t)ns_id, l_lnum, l_col, u_lnum,
+ u_col, (int64_t)limit, reverse);
+
+ for (size_t i = 0; i < kv_size(marks); i++) {
+ Array mark = ARRAY_DICT_INIT;
+ Extmark *extmark = kv_A(marks, i);
+ ADD(mark, INTEGER_OBJ((Integer)extmark->mark_id));
+ ADD(mark, INTEGER_OBJ(extmark->line->lnum-1));
+ ADD(mark, INTEGER_OBJ(extmark->col-1));
+ ADD(rv, ARRAY_OBJ(mark));
+ }
+
+ kv_destroy(marks);
+ return rv;
+}
+
+/// Creates or updates an extmark.
+///
+/// To create a new extmark, pass id=0. The extmark id will be returned.
+// To move an existing mark, pass its id.
+///
+/// It is also allowed to create a new mark by passing in a previously unused
+/// id, but the caller must then keep track of existing and unused ids itself.
+/// (Useful over RPC, to avoid waiting for the return value.)
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param id Extmark id, or 0 to create new
+/// @param line Line number where to place the mark
+/// @param col Column where to place the mark
+/// @param opts Optional parameters. Currently not used.
+/// @param[out] err Error details, if any
+/// @return Id of the created/updated extmark
+Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id,
+ Integer line, Integer col,
+ Dictionary opts, Error *err)
+ FUNC_API_SINCE(7)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return 0;
+ }
+
+ if (!ns_initialized((uint64_t)ns_id)) {
+ api_set_error(err, kErrorTypeValidation, _("Invalid ns_id"));
+ return 0;
+ }
+
+ if (opts.size > 0) {
+ api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ return 0;
+ }
+
+ size_t len = 0;
+ if (line < 0 || line > buf->b_ml.ml_line_count) {
+ api_set_error(err, kErrorTypeValidation, "line value outside range");
+ return 0;
+ } else if (line < buf->b_ml.ml_line_count) {
+ len = STRLEN(ml_get_buf(buf, (linenr_T)line+1, false));
+ }
+
+ if (col == -1) {
+ col = (Integer)len;
+ } else if (col < -1 || col > (Integer)len) {
+ api_set_error(err, kErrorTypeValidation, "col value outside range");
+ return 0;
+ }
+
+ uint64_t id_num;
+ if (id == 0) {
+ id_num = extmark_free_id_get(buf, (uint64_t)ns_id);
+ } else if (id > 0) {
+ id_num = (uint64_t)id;
+ } else {
+ api_set_error(err, kErrorTypeValidation, _("Invalid mark id"));
+ return 0;
+ }
+
+ extmark_set(buf, (uint64_t)ns_id, id_num,
+ (linenr_T)line+1, (colnr_T)col+1, kExtmarkUndo);
+
+ return (Integer)id_num;
+}
+
+/// Removes an extmark.
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param id Extmark id
+/// @param[out] err Error details, if any
+/// @return true if the extmark was found, else false
+Boolean nvim_buf_del_extmark(Buffer buffer,
+ Integer ns_id,
+ Integer id,
+ Error *err)
+ FUNC_API_SINCE(7)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return false;
+ }
+ if (!ns_initialized((uint64_t)ns_id)) {
+ api_set_error(err, kErrorTypeValidation, _("Invalid ns_id"));
+ return false;
+ }
+
+ return extmark_del(buf, (uint64_t)ns_id, (uint64_t)id, kExtmarkUndo);
+}
+
/// Adds a highlight to buffer.
///
/// Useful for plugins that dynamically generate highlights to a buffer
@@ -1050,7 +1325,8 @@ Integer nvim_buf_add_highlight(Buffer buffer,
return ns_id;
}
-/// Clears namespaced objects, highlights and virtual text, from a line range
+/// Clears namespaced objects (highlights, extmarks, virtual text) from
+/// a region.
///
/// Lines are 0-indexed. |api-indexing| To clear the namespace in the entire
/// buffer, specify line_start=0 and line_end=-1.
@@ -1082,6 +1358,10 @@ void nvim_buf_clear_namespace(Buffer buffer,
}
bufhl_clear_line_range(buf, (int)ns_id, (int)line_start+1, (int)line_end);
+ extmark_clear(buf, ns_id == -1 ? 0 : (uint64_t)ns_id,
+ (linenr_T)line_start+1,
+ (linenr_T)line_end,
+ kExtmarkUndo);
}
/// Clears highlights and virtual text from namespace and range of lines
@@ -1192,6 +1472,56 @@ free_exit:
return 0;
}
+/// Get the virtual text (annotation) for a buffer line.
+///
+/// The virtual text is returned as list of lists, whereas the inner lists have
+/// either one or two elements. The first element is the actual text, the
+/// optional second element is the highlight group.
+///
+/// The format is exactly the same as given to nvim_buf_set_virtual_text().
+///
+/// If there is no virtual text associated with the given line, an empty list
+/// is returned.
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param line Line to get the virtual text from (zero-indexed)
+/// @param[out] err Error details, if any
+/// @return List of virtual text chunks
+Array nvim_buf_get_virtual_text(Buffer buffer, Integer lnum, Error *err)
+ FUNC_API_SINCE(7)
+{
+ Array chunks = ARRAY_DICT_INIT;
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return chunks;
+ }
+
+ if (lnum < 0 || lnum >= MAXLNUM) {
+ api_set_error(err, kErrorTypeValidation, "Line number outside range");
+ return chunks;
+ }
+
+ BufhlLine *lineinfo = bufhl_tree_ref(&buf->b_bufhl_info, (linenr_T)(lnum + 1),
+ false);
+ if (!lineinfo) {
+ return chunks;
+ }
+
+ for (size_t i = 0; i < lineinfo->virt_text.size; i++) {
+ Array chunk = ARRAY_DICT_INIT;
+ VirtTextChunk *vtc = &lineinfo->virt_text.items[i];
+ ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text)));
+ if (vtc->hl_id > 0) {
+ ADD(chunk, STRING_OBJ(cstr_to_string(
+ (const char *)syn_id2name(vtc->hl_id))));
+ }
+ ADD(chunks, ARRAY_OBJ(chunk));
+ }
+
+ return chunks;
+}
+
Dictionary nvim__buf_stats(Buffer buffer, Error *err)
{
Dictionary rv = ARRAY_DICT_INIT;
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 2056cb07e3..b8d62e42a1 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -10,6 +10,7 @@
#include "nvim/api/private/helpers.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/handle.h"
+#include "nvim/api/vim.h"
#include "nvim/msgpack_rpc/helpers.h"
#include "nvim/lua/executor.h"
#include "nvim/ascii.h"
@@ -23,6 +24,7 @@
#include "nvim/eval/typval.h"
#include "nvim/map_defs.h"
#include "nvim/map.h"
+#include "nvim/mark_extended.h"
#include "nvim/option.h"
#include "nvim/option_defs.h"
#include "nvim/version.h"
@@ -1505,3 +1507,127 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf)
return mappings;
}
+
+// Returns an extmark given an id or a positional index
+// If throw == true then an error will be raised if nothing
+// was found
+// Returns NULL if something went wrong
+Extmark *extmark_from_id_or_pos(Buffer buffer, Integer ns, Object id,
+ Error *err, bool throw)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return NULL;
+ }
+
+ Extmark *extmark = NULL;
+ if (id.type == kObjectTypeArray) {
+ if (id.data.array.size != 2) {
+ api_set_error(err, kErrorTypeValidation,
+ _("Position must have 2 elements"));
+ return NULL;
+ }
+ linenr_T row = (linenr_T)id.data.array.items[0].data.integer;
+ colnr_T col = (colnr_T)id.data.array.items[1].data.integer;
+ if (row < 1 || col < 1) {
+ if (throw) {
+ api_set_error(err, kErrorTypeValidation, _("Row and column MUST be > 0"));
+ }
+ return NULL;
+ }
+ extmark = extmark_from_pos(buf, (uint64_t)ns, row, col);
+ } else if (id.type != kObjectTypeInteger) {
+ if (throw) {
+ api_set_error(err, kErrorTypeValidation,
+ _("Mark id must be an int or [row, col]"));
+ }
+ return NULL;
+ } else if (id.data.integer < 0) {
+ if (throw) {
+ api_set_error(err, kErrorTypeValidation, _("Mark id must be positive"));
+ }
+ return NULL;
+ } else {
+ extmark = extmark_from_id(buf,
+ (uint64_t)ns,
+ (uint64_t)id.data.integer);
+ }
+
+ if (!extmark) {
+ if (throw) {
+ api_set_error(err, kErrorTypeValidation, _("Mark doesn't exist"));
+ }
+ return NULL;
+ }
+ return extmark;
+}
+
+// Is the Namespace in use?
+bool ns_initialized(uint64_t ns)
+{
+ if (ns < 1) {
+ return false;
+ }
+ return ns < (uint64_t)next_namespace_id;
+}
+
+/// Gets the line and column of an extmark.
+///
+/// Extmarks may be queried by position, name or even special names
+/// in the future such as "cursor".
+///
+/// @param[out] lnum extmark line
+/// @param[out] colnr extmark column
+///
+/// @return true if the extmark was found, else false
+bool extmark_get_index_from_obj(buf_T *buf, Integer ns, Object obj, linenr_T
+ *lnum, colnr_T *colnr, Error *err)
+{
+ // Check if it is mark id
+ if (obj.type == kObjectTypeInteger) {
+ Integer id = obj.data.integer;
+ if (id == 0) {
+ *lnum = 1;
+ *colnr = 1;
+ return true;
+ } else if (id == -1) {
+ *lnum = MAXLNUM;
+ *colnr = MAXCOL;
+ return true;
+ } else if (id < 0) {
+ api_set_error(err, kErrorTypeValidation, _("Mark id must be positive"));
+ return false;
+ }
+
+ Extmark *extmark = extmark_from_id(buf, (uint64_t)ns, (uint64_t)id);
+ if (extmark) {
+ *lnum = extmark->line->lnum;
+ *colnr = extmark->col;
+ return true;
+ } else {
+ api_set_error(err, kErrorTypeValidation, _("No mark with requested id"));
+ return false;
+ }
+
+ // Check if it is a position
+ } else if (obj.type == kObjectTypeArray) {
+ Array pos = obj.data.array;
+ if (pos.size != 2
+ || pos.items[0].type != kObjectTypeInteger
+ || pos.items[1].type != kObjectTypeInteger) {
+ api_set_error(err, kErrorTypeValidation,
+ _("Position must have 2 integer elements"));
+ return false;
+ }
+ Integer line = pos.items[0].data.integer;
+ Integer col = pos.items[1].data.integer;
+ *lnum = (linenr_T)(line >= 0 ? line + 1 : MAXLNUM);
+ *colnr = (colnr_T)(col >= 0 ? col + 1 : MAXCOL);
+ return true;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ _("Position must be a mark id Integer or position Array"));
+ return false;
+ }
+}
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index 0ea7667428..8930f252f6 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -102,6 +102,20 @@ typedef struct {
int did_emsg;
} TryState;
+// `msg_list` controls the collection of abort-causing non-exception errors,
+// which would otherwise be ignored. This pattern is from do_cmdline().
+//
+// TODO(bfredl): prepare error-handling at "top level" (nv_event).
+#define TRY_WRAP(code) \
+ do { \
+ struct msglist **saved_msg_list = msg_list; \
+ struct msglist *private_msg_list; \
+ msg_list = &private_msg_list; \
+ private_msg_list = NULL; \
+ code \
+ msg_list = saved_msg_list; /* Restore the exception context. */ \
+ } while (0)
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/private/helpers.h.generated.h"
#endif
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 602733fd31..19601b6539 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -25,6 +25,7 @@
#include "nvim/highlight.h"
#include "nvim/window.h"
#include "nvim/types.h"
+#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
#include "nvim/screen.h"
#include "nvim/memline.h"
@@ -39,6 +40,7 @@
#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/state.h"
+#include "nvim/mark_extended.h"
#include "nvim/syntax.h"
#include "nvim/getchar.h"
#include "nvim/os/input.h"
@@ -53,20 +55,6 @@
# include "api/vim.c.generated.h"
#endif
-// `msg_list` controls the collection of abort-causing non-exception errors,
-// which would otherwise be ignored. This pattern is from do_cmdline().
-//
-// TODO(bfredl): prepare error-handling at "top level" (nv_event).
-#define TRY_WRAP(code) \
- do { \
- struct msglist **saved_msg_list = msg_list; \
- struct msglist *private_msg_list; \
- msg_list = &private_msg_list; \
- private_msg_list = NULL; \
- code \
- msg_list = saved_msg_list; /* Restore the exception context. */ \
- } while (0)
-
void api_vim_init(void)
FUNC_API_NOEXPORT
{
@@ -85,10 +73,70 @@ void api_vim_free_all_mem(void)
map_free(String, handle_T)(namespace_ids);
}
+/// Executes Vimscript (multiline block of Ex-commands), like anonymous
+/// |:source|.
+///
+/// Unlike |nvim_command()| this function supports heredocs, script-scope (s:),
+/// etc.
+///
+/// On execution error: fails with VimL error, does not update v:errmsg.
+///
+/// @see |execute()|
+/// @see |nvim_command()|
+///
+/// @param src Vimscript code
+/// @param output Capture and return all (non-error, non-shell |:!|) output
+/// @param[out] err Error details (Vim error), if any
+/// @return Output (non-error, non-shell |:!|) if `output` is true,
+/// else empty string.
+String nvim_exec(String src, Boolean output, Error *err)
+ FUNC_API_SINCE(7)
+{
+ const int save_msg_silent = msg_silent;
+ garray_T *const save_capture_ga = capture_ga;
+ garray_T capture_local;
+ if (output) {
+ ga_init(&capture_local, 1, 80);
+ capture_ga = &capture_local;
+ }
+
+ try_start();
+ msg_silent++;
+ do_source_str(src.data, "nvim_exec()");
+ capture_ga = save_capture_ga;
+ msg_silent = save_msg_silent;
+ try_end(err);
+
+ if (ERROR_SET(err)) {
+ goto theend;
+ }
+
+ if (output && capture_local.ga_len > 1) {
+ String s = (String){
+ .data = capture_local.ga_data,
+ .size = (size_t)capture_local.ga_len,
+ };
+ // redir usually (except :echon) prepends a newline.
+ if (s.data[0] == '\n') {
+ memmove(s.data, s.data + 1, s.size - 1);
+ s.data[s.size - 1] = '\0';
+ s.size = s.size - 1;
+ }
+ return s; // Caller will free the memory.
+ }
+theend:
+ if (output) {
+ ga_clear(&capture_local);
+ }
+ return (String)STRING_INIT;
+}
+
/// Executes an ex-command.
///
/// On execution error: fails with VimL error, does not update v:errmsg.
///
+/// @see |nvim_exec()|
+///
/// @param command Ex-command string
/// @param[out] err Error details (Vim error), if any
void nvim_command(String command, Error *err)
@@ -345,53 +393,16 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt,
return cstr_as_string(ptr);
}
-/// Executes an ex-command and returns its (non-error) output.
-/// Shell |:!| output is not captured.
-///
-/// On execution error: fails with VimL error, does not update v:errmsg.
-///
-/// @param command Ex-command string
-/// @param[out] err Error details (Vim error), if any
+/// @deprecated
+/// @see nvim_exec
String nvim_command_output(String command, Error *err)
FUNC_API_SINCE(1)
+ FUNC_API_DEPRECATED_SINCE(7)
{
- const int save_msg_silent = msg_silent;
- garray_T *const save_capture_ga = capture_ga;
- garray_T capture_local;
- ga_init(&capture_local, 1, 80);
-
- try_start();
- msg_silent++;
- capture_ga = &capture_local;
- do_cmdline_cmd(command.data);
- capture_ga = save_capture_ga;
- msg_silent = save_msg_silent;
- try_end(err);
-
- if (ERROR_SET(err)) {
- goto theend;
- }
-
- if (capture_local.ga_len > 1) {
- String s = (String){
- .data = capture_local.ga_data,
- .size = (size_t)capture_local.ga_len,
- };
- // redir usually (except :echon) prepends a newline.
- if (s.data[0] == '\n') {
- memmove(s.data, s.data + 1, s.size - 1);
- s.data[s.size - 1] = '\0';
- s.size = s.size - 1;
- }
- return s; // Caller will free the memory.
- }
-
-theend:
- ga_clear(&capture_local);
- return (String)STRING_INIT;
+ return nvim_exec(command, true, err);
}
-/// Evaluates a VimL expression (:help expression).
+/// Evaluates a VimL |expression|.
/// Dictionaries and Lists are recursively expanded.
///
/// On execution error: fails with VimL error, does not update v:errmsg.
@@ -436,6 +447,15 @@ Object nvim_eval(String expr, Error *err)
return rv;
}
+/// @deprecated Use nvim_exec_lua() instead.
+Object nvim_execute_lua(String code, Array args, Error *err)
+ FUNC_API_SINCE(3)
+ FUNC_API_DEPRECATED_SINCE(7)
+ FUNC_API_REMOTE_ONLY
+{
+ return executor_exec_lua_api(code, args, err);
+}
+
/// Execute Lua code. Parameters (if any) are available as `...` inside the
/// chunk. The chunk can return a value.
///
@@ -448,8 +468,9 @@ Object nvim_eval(String expr, Error *err)
/// or executing the Lua code.
///
/// @return Return value of Lua code if present or NIL.
-Object nvim_execute_lua(String code, Array args, Error *err)
- FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY
+Object nvim_exec_lua(String code, Array args, Error *err)
+ FUNC_API_SINCE(7)
+ FUNC_API_REMOTE_ONLY
{
return executor_exec_lua_api(code, args, err);
}
@@ -1054,10 +1075,9 @@ fail:
/// @param enter Enter the window (make it the current window)
/// @param config Map defining the window configuration. Keys:
/// - `relative`: Sets the window layout to "floating", placed at (row,col)
-/// coordinates relative to one of:
+/// coordinates relative to:
/// - "editor" The global editor grid
-/// - "win" Window given by the `win` field, or current window by
-/// default.
+/// - "win" Window given by the `win` field, or current window.
/// - "cursor" Cursor position in current window.
/// - `win`: |window-ID| for relative="win".
/// - `anchor`: Decides which corner of the float to place at (row,col):
@@ -1088,9 +1108,10 @@ fail:
/// float where the text should not be edited. Disables
/// 'number', 'relativenumber', 'cursorline', 'cursorcolumn',
/// 'foldcolumn', 'spell' and 'list' options. 'signcolumn'
-/// is changed to `auto`. The end-of-buffer region is hidden
-/// by setting `eob` flag of 'fillchars' to a space char,
-/// and clearing the |EndOfBuffer| region in 'winhighlight'.
+/// is changed to `auto` and 'colorcolumn' is cleared. The
+/// end-of-buffer region is hidden by setting `eob` flag of
+/// 'fillchars' to a space char, and clearing the
+/// |EndOfBuffer| region in 'winhighlight'.
/// @param[out] err Error details, if any
///
/// @return Window handle, or 0 on error
@@ -1109,6 +1130,10 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dictionary config,
if (enter) {
win_enter(wp, false);
}
+ if (!win_valid(wp)) {
+ api_set_error(err, kErrorTypeException, "Window was closed immediately");
+ return 0;
+ }
if (buffer > 0) {
nvim_win_set_buf(wp->handle, buffer, err);
}
@@ -1260,13 +1285,13 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err)
Array lines = string_to_array(data, crlf);
ADD(args, ARRAY_OBJ(lines));
ADD(args, INTEGER_OBJ(phase));
- rv = nvim_execute_lua(STATIC_CSTR_AS_STRING("return vim.paste(...)"), args,
- err);
+ rv = nvim_exec_lua(STATIC_CSTR_AS_STRING("return vim.paste(...)"), args,
+ err);
if (ERROR_SET(err)) {
draining = true;
goto theend;
}
- if (!(State & CMDLINE) && !(State & INSERT) && (phase == -1 || phase == 1)) {
+ if (!(State & (CMDLINE | INSERT)) && (phase == -1 || phase == 1)) {
ResetRedobuff();
AppendCharToRedobuff('a'); // Dot-repeat.
}
@@ -1284,7 +1309,7 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err)
}
}
}
- if (!(State & CMDLINE) && !(State & INSERT) && (phase == -1 || phase == 3)) {
+ if (!(State & (CMDLINE | INSERT)) && (phase == -1 || phase == 3)) {
AppendCharToRedobuff(ESC); // Dot-repeat.
}
theend:
@@ -1304,7 +1329,7 @@ theend:
/// @param lines |readfile()|-style list of lines. |channel-lines|
/// @param type Edit behavior: any |getregtype()| result, or:
/// - "b" |blockwise-visual| mode (may include width, e.g. "b3")
-/// - "c" |characterwise| mode
+/// - "c" |charwise| mode
/// - "l" |linewise| mode
/// - "" guess by contents, see |setreg()|
/// @param after Insert after cursor (like |p|), or before (like |P|).
@@ -2395,7 +2420,7 @@ Array nvim_get_proc_children(Integer pid, Error *err)
Array a = ARRAY_DICT_INIT;
ADD(a, INTEGER_OBJ(pid));
String s = cstr_to_string("return vim._os_proc_children(select(1, ...))");
- Object o = nvim_execute_lua(s, a, err);
+ Object o = nvim_exec_lua(s, a, err);
api_free_string(s);
api_free_array(a);
if (o.type == kObjectTypeArray) {
@@ -2441,7 +2466,7 @@ Object nvim_get_proc(Integer pid, Error *err)
Array a = ARRAY_DICT_INIT;
ADD(a, INTEGER_OBJ(pid));
String s = cstr_to_string("return vim._os_proc_info(select(1, ...))");
- Object o = nvim_execute_lua(s, a, err);
+ Object o = nvim_exec_lua(s, a, err);
api_free_string(s);
api_free_array(a);
if (o.type == kObjectTypeArray && o.data.array.size == 0) {
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index c223679596..a52789c795 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -21,9 +21,9 @@ return {
'BufWritePre', -- before writing a buffer
'ChanInfo', -- info was received about channel
'ChanOpen', -- channel was opened
- 'CmdLineChanged', -- command line was modified
- 'CmdLineEnter', -- after entering cmdline mode
- 'CmdLineLeave', -- before leaving cmdline mode
+ 'CmdlineChanged', -- command line was modified
+ 'CmdlineEnter', -- after entering cmdline mode
+ 'CmdlineLeave', -- before leaving cmdline mode
'CmdUndefined', -- command undefined
'CmdWinEnter', -- after entering the cmdline window
'CmdWinLeave', -- before leaving the cmdline window
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index b50c764ac3..33ffff39f6 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -53,6 +53,7 @@
#include "nvim/indent_c.h"
#include "nvim/main.h"
#include "nvim/mark.h"
+#include "nvim/mark_extended.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -410,11 +411,11 @@ bool buf_valid(buf_T *buf)
/// caller should get a new buffer very soon!
/// The 'bufhidden' option can force freeing and deleting.
/// @param abort_if_last
-/// If TRUE, do not close the buffer if autocommands cause
+/// If true, do not close the buffer if autocommands cause
/// there to be only one window with this buffer. e.g. when
/// ":quit" is supposed to close the window but autocommands
/// close all other windows.
-void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last)
+void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
{
bool unload_buf = (action != 0);
bool del_buf = (action == DOBUF_DEL || action == DOBUF_WIPE);
@@ -816,6 +817,7 @@ static void free_buffer_stuff(buf_T *buf, int free_flags)
}
uc_clear(&buf->b_ucmds); // clear local user commands
buf_delete_signs(buf, (char_u *)"*"); // delete any signs
+ extmark_free_all(buf); // delete any extmarks
bufhl_clear_all(buf); // delete any highligts
map_clear_int(buf, MAP_ALL_MODES, true, false); // clear local mappings
map_clear_int(buf, MAP_ALL_MODES, true, true); // clear local abbrevs
@@ -929,7 +931,7 @@ void handle_swap_exists(bufref_T *old_curbuf)
// User selected Recover at ATTENTION prompt.
msg_scroll = true;
- ml_recover();
+ ml_recover(false);
MSG_PUTS("\n"); // don't overwrite the last message
cmdline_row = msg_row;
do_modelines(0);
@@ -1236,7 +1238,7 @@ do_buffer(
return FAIL;
}
} else {
- EMSG2(_("E89: %s will be killed(add ! to override)"),
+ EMSG2(_("E89: %s will be killed (add ! to override)"),
(char *)buf->b_fname);
return FAIL;
}
@@ -1583,10 +1585,12 @@ void enter_buffer(buf_T *buf)
open_buffer(false, NULL, 0);
} else {
- if (!msg_silent) {
+ if (!msg_silent && !shortmess(SHM_FILEINFO)) {
need_fileinfo = true; // display file info after redraw
}
- (void)buf_check_timestamp(curbuf, false); // check if file changed
+ // check if file changed
+ (void)buf_check_timestamp(curbuf, false);
+
curwin->w_topline = 1;
curwin->w_topfill = 0;
apply_autocmds(EVENT_BUFENTER, NULL, NULL, false, curbuf);
@@ -1947,6 +1951,7 @@ void free_buf_options(buf_T *buf, int free_p_ff)
clear_string_option(&buf->b_p_path);
clear_string_option(&buf->b_p_tags);
clear_string_option(&buf->b_p_tc);
+ clear_string_option(&buf->b_p_tfu);
clear_string_option(&buf->b_p_dict);
clear_string_option(&buf->b_p_tsr);
clear_string_option(&buf->b_p_qe);
@@ -2689,7 +2694,7 @@ setfname(
buf_T *buf,
char_u *ffname,
char_u *sfname,
- int message // give message when buffer already exists
+ bool message // give message when buffer already exists
)
{
buf_T *obuf = NULL;
@@ -3796,14 +3801,20 @@ int build_stl_str_hl(
buf_T *const save_curbuf = curbuf;
win_T *const save_curwin = curwin;
+ const int save_VIsual_active = VIsual_active;
curwin = wp;
curbuf = wp->w_buffer;
+ // Visual mode is only valid in the current window.
+ if (curwin != save_curwin) {
+ VIsual_active = false;
+ }
// Note: The result stored in `t` is unused.
str = eval_to_string_safe(out_p, &t, use_sandbox);
curwin = save_curwin;
curbuf = save_curbuf;
+ VIsual_active = save_VIsual_active;
// Remove the variable we just stored
do_unlet(S_LEN("g:actual_curbuf"), true);
@@ -4629,7 +4640,8 @@ do_arg_all(
if (i < alist->al_ga.ga_len
&& (AARGLIST(alist)[i].ae_fnum == buf->b_fnum
|| path_full_compare(alist_name(&AARGLIST(alist)[i]),
- buf->b_ffname, true) & kEqualFiles)) {
+ buf->b_ffname,
+ true, true) & kEqualFiles)) {
int weight = 1;
if (old_curtab == curtab) {
@@ -5179,6 +5191,13 @@ bool bt_help(const buf_T *const buf)
return buf != NULL && buf->b_help;
}
+// Return true if "buf" is a normal buffer, 'buftype' is empty.
+bool bt_normal(const buf_T *const buf)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return buf != NULL && buf->b_p_bt[0] == NUL;
+}
+
// Return true if "buf" is the quickfix buffer.
bool bt_quickfix(const buf_T *const buf)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
@@ -5487,6 +5506,7 @@ void bufhl_clear_line_range(buf_T *buf,
linenr_T line_start,
linenr_T line_end)
{
+ // TODO(bfredl): implement kb_itr_interval to jump directly to the first line
kbitr_t(bufhl) itr;
BufhlLine *l, t = BUFHLLINE_INIT(line_start);
if (!kb_itr_get(bufhl, &buf->b_bufhl_info, &t, &itr)) {
@@ -5614,6 +5634,86 @@ void bufhl_mark_adjust(buf_T* buf,
}
}
+/// Adjust a placed highlight for column changes and joined/broken lines
+bool bufhl_mark_col_adjust(buf_T *buf,
+ linenr_T lnum,
+ colnr_T mincol,
+ long lnum_amount,
+ long col_amount)
+{
+ bool moved = false;
+ BufhlLine *lineinfo = bufhl_tree_ref(&buf->b_bufhl_info, lnum, false);
+ if (!lineinfo) {
+ // Old line empty, nothing to do
+ return false;
+ }
+ // Create the new line below only if needed
+ BufhlLine *lineinfo2 = NULL;
+
+ colnr_T delcol = MAXCOL;
+ if (lnum_amount == 0 && col_amount < 0) {
+ delcol = mincol+(int)col_amount;
+ }
+
+ size_t newidx = 0;
+ for (size_t i = 0; i < kv_size(lineinfo->items); i++) {
+ BufhlItem *item = &kv_A(lineinfo->items, i);
+ bool delete = false;
+ if (item->start >= mincol) {
+ moved = true;
+ item->start += (int)col_amount;
+ if (item->stop < MAXCOL) {
+ item->stop += (int)col_amount;
+ }
+ if (lnum_amount != 0) {
+ if (lineinfo2 == NULL) {
+ lineinfo2 = bufhl_tree_ref(&buf->b_bufhl_info,
+ lnum+lnum_amount, true);
+ }
+ kv_push(lineinfo2->items, *item);
+ delete = true;
+ }
+ } else {
+ if (item->start >= delcol) {
+ moved = true;
+ item->start = delcol;
+ }
+ if (item->stop == MAXCOL || item->stop+1 >= mincol) {
+ if (item->stop == MAXCOL) {
+ if (delcol < MAXCOL
+ && delcol > (colnr_T)STRLEN(ml_get_buf(buf, lnum, false))) {
+ delete = true;
+ }
+ } else {
+ moved = true;
+ item->stop += (int)col_amount;
+ }
+ assert(lnum_amount >= 0);
+ if (lnum_amount > 0) {
+ item->stop = MAXCOL;
+ }
+ } else if (item->stop+1 >= delcol) {
+ moved = true;
+ item->stop = delcol-1;
+ }
+ // we covered the entire range with a visual delete or something
+ if (item->stop < item->start) {
+ delete = true;
+ }
+ }
+
+ if (!delete) {
+ if (i != newidx) {
+ kv_A(lineinfo->items, newidx) = kv_A(lineinfo->items, i);
+ }
+ newidx++;
+ }
+ }
+ kv_size(lineinfo->items) = newidx;
+
+ return moved;
+}
+
/// Get highlights to display at a specific line
///
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 16c7804be0..700d8b82e6 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -115,14 +115,18 @@ typedef uint16_t disptick_T; // display tick type
#include "nvim/os/fs_defs.h" // for FileID
#include "nvim/terminal.h" // for Terminal
+#include "nvim/lib/kbtree.h"
+#include "nvim/mark_extended.h"
+
/*
* The taggy struct is used to store the information about a :tag command.
*/
typedef struct taggy {
- char_u *tagname; /* tag name */
- fmark_T fmark; /* cursor position BEFORE ":tag" */
- int cur_match; /* match number */
- int cur_fnum; /* buffer number used for cur_match */
+ char_u *tagname; // tag name
+ fmark_T fmark; // cursor position BEFORE ":tag"
+ int cur_match; // match number
+ int cur_fnum; // buffer number used for cur_match
+ char_u *user_data; // used with tagfunc
} taggy_T;
typedef struct buffblock buffblock_T;
@@ -647,6 +651,7 @@ struct file_buffer {
char_u *b_p_cpt; ///< 'complete'
char_u *b_p_cfu; ///< 'completefunc'
char_u *b_p_ofu; ///< 'omnifunc'
+ char_u *b_p_tfu; ///< 'tagfunc'
int b_p_eol; ///< 'endofline'
int b_p_fixeol; ///< 'fixendofline'
int b_p_et; ///< 'expandtab'
@@ -803,6 +808,10 @@ struct file_buffer {
kvec_t(BufhlLine *) b_bufhl_move_space; // temporary space for highlights
+ PMap(uint64_t) *b_extmark_ns; // extmark namespaces
+ kbtree_t(extmarklines) b_extlines; // extmarks
+ kvec_t(ExtmarkLine *) b_extmark_move_space; // temp space for extmarks
+
// array of channel_id:s which have asked to receive updates for this
// buffer.
kvec_t(uint64_t) update_channels;
@@ -909,19 +918,19 @@ typedef struct w_line {
* or row (FR_ROW) layout or is a leaf, which has a window.
*/
struct frame_S {
- char fr_layout; /* FR_LEAF, FR_COL or FR_ROW */
+ char fr_layout; // FR_LEAF, FR_COL or FR_ROW
int fr_width;
- int fr_newwidth; /* new width used in win_equal_rec() */
+ int fr_newwidth; // new width used in win_equal_rec()
int fr_height;
- int fr_newheight; /* new height used in win_equal_rec() */
- frame_T *fr_parent; /* containing frame or NULL */
- frame_T *fr_next; /* frame right or below in same parent, NULL
- for first */
- frame_T *fr_prev; /* frame left or above in same parent, NULL
- for last */
- /* fr_child and fr_win are mutually exclusive */
- frame_T *fr_child; /* first contained frame */
- win_T *fr_win; /* window that fills this frame */
+ int fr_newheight; // new height used in win_equal_rec()
+ frame_T *fr_parent; // containing frame or NULL
+ frame_T *fr_next; // frame right or below in same parent, NULL
+ // for last
+ frame_T *fr_prev; // frame left or above in same parent, NULL
+ // for first
+ // fr_child and fr_win are mutually exclusive
+ frame_T *fr_child; // first contained frame
+ win_T *fr_win; // window that fills this frame
};
#define FR_LEAF 0 /* frame is a leaf */
diff --git a/src/nvim/change.c b/src/nvim/change.c
index ba80e71ae6..8a782c2b20 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -17,6 +17,7 @@
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/mark.h"
+#include "nvim/mark_extended.h"
#include "nvim/memline.h"
#include "nvim/misc1.h"
#include "nvim/move.h"
@@ -358,6 +359,24 @@ void changed_bytes(linenr_T lnum, colnr_T col)
}
}
+/// insert/delete bytes at column
+///
+/// Like changed_bytes() but also adjust extmark for "added" bytes.
+/// When "added" is negative text was deleted.
+static void inserted_bytes(linenr_T lnum, colnr_T col, int added)
+{
+ if (added > 0) {
+ extmark_col_adjust(curbuf, lnum, col+1, 0, added, kExtmarkUndo);
+ } else if (added < 0) {
+ // TODO(bfredl): next revision of extmarks should handle both these
+ // with the same entry point. Also with more sane params..
+ extmark_col_adjust_delete(curbuf, lnum, col+2,
+ col+(-added)+1, kExtmarkUndo, 0);
+ }
+
+ changed_bytes(lnum, col);
+}
+
/// Appended "count" lines below line "lnum" in the current buffer.
/// Must be called AFTER the change and after mark_adjust().
/// Takes care of marking the buffer to be redrawn and sets the changed flag.
@@ -372,7 +391,7 @@ void appended_lines_mark(linenr_T lnum, long count)
// Skip mark_adjust when adding a line after the last one, there can't
// be marks there. But it's still needed in diff mode.
if (lnum + count < curbuf->b_ml.ml_line_count || curwin->w_p_diff) {
- mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, false);
+ mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, false, kExtmarkUndo);
}
changed_lines(lnum + 1, 0, lnum + 1, count, true);
}
@@ -390,7 +409,8 @@ void deleted_lines(linenr_T lnum, long count)
/// be triggered to display the cursor.
void deleted_lines_mark(linenr_T lnum, long count)
{
- mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, -count, false);
+ mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, -count, false,
+ kExtmarkUndo);
changed_lines(lnum, 0, lnum + count, -count, true);
}
@@ -628,7 +648,7 @@ void ins_char_bytes(char_u *buf, size_t charlen)
ml_replace(lnum, newp, false);
// mark the buffer as changed and prepare for displaying
- changed_bytes(lnum, (colnr_T)col);
+ inserted_bytes(lnum, (colnr_T)col, (int)(newlen - oldlen));
// If we're in Insert or Replace mode and 'showmatch' is set, then briefly
// show the match for right parens and braces.
@@ -674,7 +694,7 @@ void ins_str(char_u *s)
assert(bytes >= 0);
memmove(newp + col + newlen, oldp + col, (size_t)bytes);
ml_replace(lnum, newp, false);
- changed_bytes(lnum, col);
+ inserted_bytes(lnum, col, newlen);
curwin->w_cursor.col += newlen;
}
@@ -795,7 +815,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine)
}
// mark the buffer as changed and prepare for displaying
- changed_bytes(lnum, curwin->w_cursor.col);
+ inserted_bytes(lnum, col, -count);
return OK;
}
@@ -951,6 +971,9 @@ int open_line(
bool did_append; // appended a new line
int saved_pi = curbuf->b_p_pi; // copy of preserveindent setting
+ linenr_T lnum = curwin->w_cursor.lnum;
+ colnr_T mincol = curwin->w_cursor.col + 1;
+
// make a copy of the current line so we can mess with it
char_u *saved_line = vim_strsave(get_cursor_line_ptr());
@@ -1574,7 +1597,8 @@ int open_line(
// be marks there. But still needed in diff mode.
if (curwin->w_cursor.lnum + 1 < curbuf->b_ml.ml_line_count
|| curwin->w_p_diff) {
- mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false);
+ mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false,
+ kExtmarkUndo);
}
did_append = true;
} else {
@@ -1663,8 +1687,12 @@ int open_line(
if (flags & OPENLINE_MARKFIX) {
mark_col_adjust(curwin->w_cursor.lnum,
curwin->w_cursor.col + less_cols_off,
- 1L, (long)-less_cols, 0);
+ 1L, (long)-less_cols, 0, kExtmarkNOOP);
}
+ // Always move extmarks - Here we move only the line where the
+ // cursor is, the previous mark_adjust takes care of the lines after
+ extmark_col_adjust(curbuf, lnum, mincol, 1L, (long)-less_cols,
+ kExtmarkUndo);
} else {
changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col);
}
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
index f9102fa0e2..2bb568f025 100644
--- a/src/nvim/channel.c
+++ b/src/nvim/channel.c
@@ -272,11 +272,35 @@ static void close_cb(Stream *stream, void *data)
channel_decref(data);
}
+
+/// Starts a job and returns the associated channel
+///
+/// @param[in] argv Arguments vector specifying the command to run,
+/// NULL-terminated
+/// @param[in] on_stdout Callback to read the job's stdout
+/// @param[in] on_stderr Callback to read the job's stderr
+/// @param[in] on_exit Callback to receive the job's exit status
+/// @param[in] pty True if the job should run attached to a pty
+/// @param[in] rpc True to communicate with the job using msgpack-rpc,
+/// `on_stdout` is ignored
+/// @param[in] detach True if the job should not be killed when nvim exits,
+/// ignored if `pty` is true
+/// @param[in] cwd Initial working directory for the job. Nvim's working
+/// directory if `cwd` is NULL
+/// @param[in] pty_width Width of the pty, ignored if `pty` is false
+/// @param[in] pty_height Height of the pty, ignored if `pty` is false
+/// @param[in] term_name `$TERM` for the pty
+/// @param[in] env Nvim's configured environment is used if this is NULL,
+/// otherwise defines all environment variables
+/// @param[out] status_out 0 for invalid arguments, > 0 for the channel id,
+/// < 0 if the job can't start
+///
+/// @returns [allocated] channel
Channel *channel_job_start(char **argv, CallbackReader on_stdout,
CallbackReader on_stderr, Callback on_exit,
bool pty, bool rpc, bool detach, const char *cwd,
uint16_t pty_width, uint16_t pty_height,
- char *term_name, varnumber_T *status_out)
+ char *term_name, char **env, varnumber_T *status_out)
{
assert(cwd == NULL || os_isdir_executable(cwd));
@@ -314,6 +338,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
proc->events = chan->events;
proc->detach = detach;
proc->cwd = cwd;
+ proc->env = env;
char *cmd = xstrdup(proc->argv[0]);
bool has_out, has_err;
@@ -328,6 +353,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
if (status) {
EMSG3(_(e_jobspawn), os_strerror(status), cmd);
xfree(cmd);
+ os_free_fullenv(proc->env);
if (proc->type == kProcessTypePty) {
xfree(chan->stream.pty.term_name);
}
@@ -336,6 +362,8 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
return NULL;
}
xfree(cmd);
+ os_free_fullenv(proc->env);
+
wstream_init(&proc->in, 0);
if (has_out) {
diff --git a/src/nvim/context.c b/src/nvim/context.c
index 2f872ff2bf..1ae0510762 100644
--- a/src/nvim/context.c
+++ b/src/nvim/context.c
@@ -254,7 +254,7 @@ static inline void ctx_save_funcs(Context *ctx, bool scriptonly)
size_t cmd_len = sizeof("func! ") + STRLEN(name);
char *cmd = xmalloc(cmd_len);
snprintf(cmd, cmd_len, "func! %s", name);
- String func_body = nvim_command_output(cstr_as_string(cmd), &err);
+ String func_body = nvim_exec(cstr_as_string(cmd), true, &err);
xfree(cmd);
if (!ERROR_SET(&err)) {
ADD(ctx->funcs, STRING_OBJ(func_body));
diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c
index f2b3cfe690..036ae32589 100644
--- a/src/nvim/cursor.c
+++ b/src/nvim/cursor.c
@@ -93,11 +93,12 @@ int coladvance(colnr_T wcol)
static int coladvance2(
pos_T *pos,
- bool addspaces, /* change the text to achieve our goal? */
- bool finetune, /* change char offset for the exact column */
- colnr_T wcol /* column to move to */
+ bool addspaces, // change the text to achieve our goal?
+ bool finetune, // change char offset for the exact column
+ colnr_T wcol_arg // column to move to (can be negative)
)
{
+ colnr_T wcol = wcol_arg;
int idx;
char_u *ptr;
char_u *line;
@@ -165,6 +166,7 @@ static int coladvance2(
if (virtual_active()
&& addspaces
+ && wcol >= 0
&& ((col != wcol && col != wcol + 1) || csize > 1)) {
/* 'virtualedit' is set: The difference between wcol and col is
* filled with spaces. */
@@ -244,8 +246,9 @@ static int coladvance2(
mark_mb_adjustpos(curbuf, pos);
}
- if (col < wcol)
+ if (wcol < 0 || col < wcol) {
return FAIL;
+ }
return OK;
}
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index db3ef7ac47..dccde01d29 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -44,7 +44,7 @@
#include "nvim/os/shell.h"
static int diff_busy = false; // using diff structs, don't change them
-static int diff_need_update = false; // ex_diffupdate needs to be called
+static bool diff_need_update = false; // ex_diffupdate needs to be called
// Flags obtained from the 'diffopt' option
#define DIFF_FILLER 0x001 // display filler lines
@@ -57,8 +57,9 @@ static int diff_need_update = false; // ex_diffupdate needs to be called
#define DIFF_VERTICAL 0x080 // vertical splits
#define DIFF_HIDDEN_OFF 0x100 // diffoff when hidden
#define DIFF_INTERNAL 0x200 // use internal xdiff algorithm
+#define DIFF_CLOSE_OFF 0x400 // diffoff when closing window
#define ALL_WHITE_DIFF (DIFF_IWHITE | DIFF_IWHITEALL | DIFF_IWHITEEOL)
-static int diff_flags = DIFF_INTERNAL | DIFF_FILLER;
+static int diff_flags = DIFF_INTERNAL | DIFF_FILLER | DIFF_CLOSE_OFF;
static long diff_algorithm = 0;
@@ -490,7 +491,8 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1,
}
if (tp == curtab) {
- diff_redraw(true);
+ // Don't redraw right away, this updates the diffs, which can be slow.
+ need_diff_redraw = true;
// Need to recompute the scroll binding, may remove or add filler
// lines (e.g., when adding lines above w_topline). But it's slow when
@@ -634,8 +636,9 @@ static int diff_check_sanity(tabpage_T *tp, diff_T *dp)
/// Mark all diff buffers in the current tab page for redraw.
///
/// @param dofold Also recompute the folds
-static void diff_redraw(int dofold)
+void diff_redraw(bool dofold)
{
+ need_diff_redraw = false;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (!wp->w_p_diff) {
continue;
@@ -723,8 +726,8 @@ static int diff_write_buffer(buf_T *buf, diffin_T *din)
char_u cbuf[MB_MAXBYTES + 1];
c = PTR2CHAR(s);
- c = enc_utf8 ? utf_fold(c) : TOLOWER_LOC(c);
- orig_len = MB_PTR2LEN(s);
+ c = utf_fold(c);
+ orig_len = utfc_ptr2len(s);
if (utf_char2bytes(c, cbuf) != orig_len) {
// TODO(Bram): handle byte length difference
memmove(ptr + len, s, orig_len);
@@ -1472,6 +1475,13 @@ void ex_diffoff(exarg_T *eap)
diff_buf_clear();
}
+ if (!diffwin) {
+ diff_need_update = false;
+ curtab->tp_diff_invalid = false;
+ curtab->tp_diff_update = false;
+ diff_clear(curtab);
+ }
+
// Remove "hor" from from 'scrollopt' if there are no diff windows left.
if (!diffwin && (vim_strchr(p_sbo, 'h') != NULL)) {
do_cmdline_cmd("set sbo-=hor");
@@ -1712,6 +1722,7 @@ static void diff_copy_entry(diff_T *dprev, diff_T *dp, int idx_orig,
///
/// @param tp
void diff_clear(tabpage_T *tp)
+ FUNC_ATTR_NONNULL_ALL
{
diff_T *p;
diff_T *next_p;
@@ -2141,6 +2152,9 @@ int diffopt_changed(void)
} else if (STRNCMP(p, "hiddenoff", 9) == 0) {
p += 9;
diff_flags_new |= DIFF_HIDDEN_OFF;
+ } else if (STRNCMP(p, "closeoff", 8) == 0) {
+ p += 8;
+ diff_flags_new |= DIFF_CLOSE_OFF;
} else if (STRNCMP(p, "indent-heuristic", 16) == 0) {
p += 16;
diff_indent_heuristic = XDF_INDENT_HEURISTIC;
@@ -2216,6 +2230,13 @@ bool diffopt_hiddenoff(void)
return (diff_flags & DIFF_HIDDEN_OFF) != 0;
}
+// Return true if 'diffopt' contains "closeoff".
+bool diffopt_closeoff(void)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return (diff_flags & DIFF_CLOSE_OFF) != 0;
+}
+
/// Find the difference within a changed line.
///
/// @param wp window whose current buffer to check
@@ -2690,7 +2711,8 @@ void ex_diffgetput(exarg_T *eap)
// Adjust marks. This will change the following entries!
if (added != 0) {
- mark_adjust(lnum, lnum + count - 1, (long)MAXLNUM, (long)added, false);
+ mark_adjust(lnum, lnum + count - 1, (long)MAXLNUM, (long)added, false,
+ kExtmarkUndo);
if (curwin->w_cursor.lnum >= lnum) {
// Adjust the cursor position if it's in/after the changed
// lines.
diff --git a/src/nvim/diff.h b/src/nvim/diff.h
index 3624ce29bb..99a60381bd 100644
--- a/src/nvim/diff.h
+++ b/src/nvim/diff.h
@@ -4,6 +4,13 @@
#include "nvim/pos.h"
#include "nvim/ex_cmds_defs.h"
+// Value set from 'diffopt'.
+EXTERN int diff_context INIT(= 6); // context for folds
+EXTERN int diff_foldcolumn INIT(= 2); // 'foldcolumn' for diff mode
+EXTERN bool diff_need_scrollbind INIT(= false);
+
+EXTERN bool need_diff_redraw INIT(= false); // need to call diff_redraw()
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "diff.h.generated.h"
#endif
diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c
index f6ec350488..65d95ff158 100644
--- a/src/nvim/digraph.c
+++ b/src/nvim/digraph.c
@@ -128,101 +128,154 @@ static digr_T digraphdefault[] =
{ 'P', 'M', 0x9e },
{ 'A', 'C', 0x9f },
{ 'N', 'S', 0xa0 },
+#define DG_START_LATIN 0xa1
{ '!', 'I', 0xa1 },
+ { '~', '!', 0xa1 }, // ¡ Vim 5.x compatible
{ 'C', 't', 0xa2 },
+ { 'c', '|', 0xa2 }, // ¢ Vim 5.x compatible
{ 'P', 'd', 0xa3 },
+ { '$', '$', 0xa3 }, // £ Vim 5.x compatible
{ 'C', 'u', 0xa4 },
+ { 'o', 'x', 0xa4 }, // ¤ Vim 5.x compatible
{ 'Y', 'e', 0xa5 },
+ { 'Y', '-', 0xa5 }, // ¥ Vim 5.x compatible
{ 'B', 'B', 0xa6 },
+ { '|', '|', 0xa6 }, // ¦ Vim 5.x compatible
{ 'S', 'E', 0xa7 },
{ '\'', ':', 0xa8 },
{ 'C', 'o', 0xa9 },
+ { 'c', 'O', 0xa9 }, // © Vim 5.x compatible
{ '-', 'a', 0xaa },
{ '<', '<', 0xab },
{ 'N', 'O', 0xac },
+ { '-', ',', 0xac }, // ¬ Vim 5.x compatible
{ '-', '-', 0xad },
{ 'R', 'g', 0xae },
{ '\'', 'm', 0xaf },
+ { '-', '=', 0xaf }, // ¯ Vim 5.x compatible
{ 'D', 'G', 0xb0 },
+ { '~', 'o', 0xb0 }, // ° Vim 5.x compatible
{ '+', '-', 0xb1 },
{ '2', 'S', 0xb2 },
+ { '2', '2', 0xb2 }, // ² Vim 5.x compatible
{ '3', 'S', 0xb3 },
+ { '3', '3', 0xb3 }, // ³ Vim 5.x compatible
{ '\'', '\'', 0xb4 },
{ 'M', 'y', 0xb5 },
{ 'P', 'I', 0xb6 },
+ { 'p', 'p', 0xb6 }, // ¶ Vim 5.x compatible
{ '.', 'M', 0xb7 },
+ { '~', '.', 0xb7 }, // · Vim 5.x compatible
{ '\'', ',', 0xb8 },
{ '1', 'S', 0xb9 },
+ { '1', '1', 0xb9 }, // ¹ Vim 5.x compatible
{ '-', 'o', 0xba },
{ '>', '>', 0xbb },
{ '1', '4', 0xbc },
{ '1', '2', 0xbd },
{ '3', '4', 0xbe },
{ '?', 'I', 0xbf },
+ { '~', '?', 0xbf }, // ¿ Vim 5.x compatible
{ 'A', '!', 0xc0 },
+ { 'A', '`', 0xc0 }, // À Vim 5.x compatible
{ 'A', '\'', 0xc1 },
{ 'A', '>', 0xc2 },
+ { 'A', '^', 0xc2 }, // Â Vim 5.x compatible
{ 'A', '?', 0xc3 },
+ { 'A', '~', 0xc3 }, // Ã Vim 5.x compatible
{ 'A', ':', 0xc4 },
+ { 'A', '"', 0xc4 }, // Ä Vim 5.x compatible
{ 'A', 'A', 0xc5 },
+ { 'A', '@', 0xc5 }, // Ã… Vim 5.x compatible
{ 'A', 'E', 0xc6 },
{ 'C', ',', 0xc7 },
{ 'E', '!', 0xc8 },
+ { 'E', '`', 0xc8 }, // È Vim 5.x compatible
{ 'E', '\'', 0xc9 },
{ 'E', '>', 0xca },
+ { 'E', '^', 0xca }, // Ê Vim 5.x compatible
{ 'E', ':', 0xcb },
+ { 'E', '"', 0xcb }, // Ë Vim 5.x compatible
{ 'I', '!', 0xcc },
+ { 'I', '`', 0xcc }, // Ì Vim 5.x compatible
{ 'I', '\'', 0xcd },
{ 'I', '>', 0xce },
+ { 'I', '^', 0xce }, // ÃŽ Vim 5.x compatible
{ 'I', ':', 0xcf },
+ { 'I', '"', 0xcf }, // Ã Vim 5.x compatible
{ 'D', '-', 0xd0 },
{ 'N', '?', 0xd1 },
+ { 'N', '~', 0xd1 }, // Ñ Vim 5.x compatible
{ 'O', '!', 0xd2 },
+ { 'O', '`', 0xd2 }, // Ã’ Vim 5.x compatible
{ 'O', '\'', 0xd3 },
{ 'O', '>', 0xd4 },
+ { 'O', '^', 0xd4 }, // Ô Vim 5.x compatible
{ 'O', '?', 0xd5 },
+ { 'O', '~', 0xd5 }, // Õ Vim 5.x compatible
{ 'O', ':', 0xd6 },
{ '*', 'X', 0xd7 },
+ { '/', '\\', 0xd7 }, // × Vim 5.x compatible
{ 'O', '/', 0xd8 },
{ 'U', '!', 0xd9 },
+ { 'U', '`', 0xd9 }, // Ù Vim 5.x compatible
{ 'U', '\'', 0xda },
{ 'U', '>', 0xdb },
+ { 'U', '^', 0xdb }, // Û Vim 5.x compatible
{ 'U', ':', 0xdc },
{ 'Y', '\'', 0xdd },
{ 'T', 'H', 0xde },
+ { 'I', 'p', 0xde }, // Þ Vim 5.x compatible
{ 's', 's', 0xdf },
{ 'a', '!', 0xe0 },
+ { 'a', '`', 0xe0 }, // à Vim 5.x compatible
{ 'a', '\'', 0xe1 },
{ 'a', '>', 0xe2 },
+ { 'a', '^', 0xe2 }, // â Vim 5.x compatible
{ 'a', '?', 0xe3 },
+ { 'a', '~', 0xe3 }, // ã Vim 5.x compatible
{ 'a', ':', 0xe4 },
+ { 'a', '"', 0xe4 }, // ä Vim 5.x compatible
{ 'a', 'a', 0xe5 },
+ { 'a', '@', 0xe5 }, // å Vim 5.x compatible
{ 'a', 'e', 0xe6 },
{ 'c', ',', 0xe7 },
{ 'e', '!', 0xe8 },
+ { 'e', '`', 0xe8 }, // è Vim 5.x compatible
{ 'e', '\'', 0xe9 },
{ 'e', '>', 0xea },
+ { 'e', '^', 0xea }, // ê Vim 5.x compatible
{ 'e', ':', 0xeb },
+ { 'e', '"', 0xeb }, // ë Vim 5.x compatible
{ 'i', '!', 0xec },
+ { 'i', '`', 0xec }, // ì Vim 5.x compatible
{ 'i', '\'', 0xed },
{ 'i', '>', 0xee },
+ { 'i', '^', 0xee }, // î Vim 5.x compatible
{ 'i', ':', 0xef },
{ 'd', '-', 0xf0 },
{ 'n', '?', 0xf1 },
+ { 'n', '~', 0xf1 }, // ñ Vim 5.x compatible
{ 'o', '!', 0xf2 },
+ { 'o', '`', 0xf2 }, // ò Vim 5.x compatible
{ 'o', '\'', 0xf3 },
{ 'o', '>', 0xf4 },
+ { 'o', '^', 0xf4 }, // ô Vim 5.x compatible
{ 'o', '?', 0xf5 },
+ { 'o', '~', 0xf5 }, // õ Vim 5.x compatible
{ 'o', ':', 0xf6 },
{ '-', ':', 0xf7 },
{ 'o', '/', 0xf8 },
{ 'u', '!', 0xf9 },
+ { 'u', '`', 0xf9 }, // ù Vim 5.x compatible
{ 'u', '\'', 0xfa },
{ 'u', '>', 0xfb },
+ { 'u', '^', 0xfb }, // û Vim 5.x compatible
{ 'u', ':', 0xfc },
{ 'y', '\'', 0xfd },
{ 't', 'h', 0xfe },
{ 'y', ':', 0xff },
+ { 'y', '"', 0xff }, // x XX Vim 5.x compatible
{ 'A', '-', 0x0100 },
{ 'a', '-', 0x0101 },
@@ -397,6 +450,7 @@ static digr_T digraphdefault[] =
{ '\'', '0', 0x02da },
{ '\'', ';', 0x02db },
{ '\'', '"', 0x02dd },
+#define DG_START_GREEK 0x0386
{ 'A', '%', 0x0386 },
{ 'E', '%', 0x0388 },
{ 'Y', '%', 0x0389 },
@@ -478,6 +532,7 @@ static digr_T digraphdefault[] =
{ 'p', '3', 0x03e1 },
{ '\'', '%', 0x03f4 },
{ 'j', '3', 0x03f5 },
+#define DG_START_CYRILLIC 0x0401
{ 'I', 'O', 0x0401 },
{ 'D', '%', 0x0402 },
{ 'G', '%', 0x0403 },
@@ -582,6 +637,7 @@ static digr_T digraphdefault[] =
{ 'c', '3', 0x0481 },
{ 'G', '3', 0x0490 },
{ 'g', '3', 0x0491 },
+#define DG_START_HEBREW 0x05d0
{ 'A', '+', 0x05d0 },
{ 'B', '+', 0x05d1 },
{ 'G', '+', 0x05d2 },
@@ -609,6 +665,7 @@ static digr_T digraphdefault[] =
{ 'R', '+', 0x05e8 },
{ 'S', 'h', 0x05e9 },
{ 'T', '+', 0x05ea },
+#define DG_START_ARABIC 0x060c
{ ',', '+', 0x060c },
{ ';', '+', 0x061b },
{ '?', '+', 0x061f },
@@ -671,6 +728,7 @@ static digr_T digraphdefault[] =
{ '7', 'a', 0x06f7 },
{ '8', 'a', 0x06f8 },
{ '9', 'a', 0x06f9 },
+#define DG_START_LATIN_EXTENDED 0x1e02
{ 'B', '.', 0x1e02 },
{ 'b', '.', 0x1e03 },
{ 'B', '_', 0x1e06 },
@@ -722,7 +780,9 @@ static digr_T digraphdefault[] =
{ 'V', '?', 0x1e7c },
{ 'v', '?', 0x1e7d },
{ 'W', '!', 0x1e80 },
+ { 'W', '`', 0x1e80 }, // extra alternative, easier to remember
{ 'w', '!', 0x1e81 },
+ { 'w', '`', 0x1e81 }, // extra alternative, easier to remember
{ 'W', '\'', 0x1e82 },
{ 'w', '\'', 0x1e83 },
{ 'W', ':', 0x1e84 },
@@ -756,11 +816,14 @@ static digr_T digraphdefault[] =
{ 'U', '2', 0x1ee6 },
{ 'u', '2', 0x1ee7 },
{ 'Y', '!', 0x1ef2 },
+ { 'Y', '`', 0x1ef2 }, // extra alternative, easier to remember
{ 'y', '!', 0x1ef3 },
+ { 'y', '`', 0x1ef3 }, // extra alternative, easier to remember
{ 'Y', '2', 0x1ef6 },
{ 'y', '2', 0x1ef7 },
{ 'Y', '?', 0x1ef8 },
{ 'y', '?', 0x1ef9 },
+#define DG_START_GREEK_EXTENDED 0x1f00
{ ';', '\'', 0x1f00 },
{ ',', '\'', 0x1f01 },
{ ';', '!', 0x1f02 },
@@ -769,6 +832,7 @@ static digr_T digraphdefault[] =
{ '?', ',', 0x1f05 },
{ '!', ':', 0x1f06 },
{ '?', ':', 0x1f07 },
+#define DG_START_PUNCTUATION 0x2002
{ '1', 'N', 0x2002 },
{ '1', 'M', 0x2003 },
{ '3', 'M', 0x2004 },
@@ -807,6 +871,7 @@ static digr_T digraphdefault[] =
{ ':', 'X', 0x203b },
{ '\'', '-', 0x203e },
{ '/', 'f', 0x2044 },
+#define DG_START_SUB_SUPER 0x2070
{ '0', 'S', 0x2070 },
{ '4', 'S', 0x2074 },
{ '5', 'S', 0x2075 },
@@ -835,13 +900,15 @@ static digr_T digraphdefault[] =
{ '=', 's', 0x208c },
{ '(', 's', 0x208d },
{ ')', 's', 0x208e },
+#define DG_START_CURRENCY 0x20a4
{ 'L', 'i', 0x20a4 },
{ 'P', 't', 0x20a7 },
{ 'W', '=', 0x20a9 },
- { '=', 'e', 0x20ac }, // euro
- { 'E', 'u', 0x20ac }, // euro
- { '=', 'R', 0x20bd }, // rouble
- { '=', 'P', 0x20bd }, // rouble
+ { '=', 'e', 0x20ac }, // euro
+ { 'E', 'u', 0x20ac }, // euro
+ { '=', 'R', 0x20bd }, // rouble
+ { '=', 'P', 0x20bd }, // rouble
+#define DG_START_OTHER1 0x2103
{ 'o', 'C', 0x2103 },
{ 'c', 'o', 0x2105 },
{ 'o', 'F', 0x2109 },
@@ -864,6 +931,7 @@ static digr_T digraphdefault[] =
{ '3', '8', 0x215c },
{ '5', '8', 0x215d },
{ '7', '8', 0x215e },
+#define DG_START_ROMAN 0x2160
{ '1', 'R', 0x2160 },
{ '2', 'R', 0x2161 },
{ '3', 'R', 0x2162 },
@@ -888,6 +956,7 @@ static digr_T digraphdefault[] =
{ 'a', 'r', 0x2179 },
{ 'b', 'r', 0x217a },
{ 'c', 'r', 0x217b },
+#define DG_START_ARROWS 0x2190
{ '<', '-', 0x2190 },
{ '-', '!', 0x2191 },
{ '-', '>', 0x2192 },
@@ -897,6 +966,7 @@ static digr_T digraphdefault[] =
{ '<', '=', 0x21d0 },
{ '=', '>', 0x21d2 },
{ '=', '=', 0x21d4 },
+#define DG_START_MATH 0x2200
{ 'F', 'A', 0x2200 },
{ 'd', 'P', 0x2202 },
{ 'T', 'E', 0x2203 },
@@ -954,6 +1024,7 @@ static digr_T digraphdefault[] =
{ '.', 'P', 0x22c5 },
{ ':', '3', 0x22ee },
{ '.', '3', 0x22ef },
+#define DG_START_TECHNICAL 0x2302
{ 'E', 'h', 0x2302 },
{ '<', '7', 0x2308 },
{ '>', '7', 0x2309 },
@@ -966,6 +1037,7 @@ static digr_T digraphdefault[] =
{ 'I', 'l', 0x2321 },
{ '<', '/', 0x2329 },
{ '/', '>', 0x232a },
+#define DG_START_OTHER2 0x2423
{ 'V', 's', 0x2423 },
{ '1', 'h', 0x2440 },
{ '3', 'h', 0x2441 },
@@ -984,6 +1056,7 @@ static digr_T digraphdefault[] =
{ '7', '.', 0x248e },
{ '8', '.', 0x248f },
{ '9', '.', 0x2490 },
+#define DG_START_DRAWING 0x2500
{ 'h', 'h', 0x2500 },
{ 'H', 'H', 0x2501 },
{ 'v', 'v', 0x2502 },
@@ -1034,6 +1107,7 @@ static digr_T digraphdefault[] =
{ 'V', 'H', 0x254b },
{ 'F', 'D', 0x2571 },
{ 'B', 'D', 0x2572 },
+#define DG_START_BLOCK 0x2580
{ 'T', 'B', 0x2580 },
{ 'L', 'B', 0x2584 },
{ 'F', 'B', 0x2588 },
@@ -1042,6 +1116,7 @@ static digr_T digraphdefault[] =
{ '.', 'S', 0x2591 },
{ ':', 'S', 0x2592 },
{ '?', 'S', 0x2593 },
+#define DG_START_SHAPES 0x25a0
{ 'f', 'S', 0x25a0 },
{ 'O', 'S', 0x25a1 },
{ 'R', 'O', 0x25a2 },
@@ -1075,6 +1150,7 @@ static digr_T digraphdefault[] =
{ 'I', 'c', 0x25d9 },
{ 'F', 'd', 0x25e2 },
{ 'B', 'd', 0x25e3 },
+#define DG_START_SYMBOLS 0x2605
{ '*', '2', 0x2605 },
{ '*', '1', 0x2606 },
{ '<', 'H', 0x261c },
@@ -1094,9 +1170,11 @@ static digr_T digraphdefault[] =
{ 'M', 'b', 0x266d },
{ 'M', 'x', 0x266e },
{ 'M', 'X', 0x266f },
+#define DG_START_DINGBATS 0x2713
{ 'O', 'K', 0x2713 },
{ 'X', 'X', 0x2717 },
{ '-', 'X', 0x2720 },
+#define DG_START_CJK_SYMBOLS 0x3000
{ 'I', 'S', 0x3000 },
{ ',', '_', 0x3001 },
{ '.', '_', 0x3002 },
@@ -1120,6 +1198,7 @@ static digr_T digraphdefault[] =
{ '(', 'I', 0x3016 },
{ ')', 'I', 0x3017 },
{ '-', '?', 0x301c },
+#define DG_START_HIRAGANA 0x3041
{ 'A', '5', 0x3041 },
{ 'a', '5', 0x3042 },
{ 'I', '5', 0x3043 },
@@ -1208,6 +1287,7 @@ static digr_T digraphdefault[] =
{ '0', '5', 0x309c },
{ '*', '5', 0x309d },
{ '+', '5', 0x309e },
+#define DG_START_KATAKANA 0x30a1
{ 'a', '6', 0x30a1 },
{ 'A', '6', 0x30a2 },
{ 'i', '6', 0x30a3 },
@@ -1302,6 +1382,7 @@ static digr_T digraphdefault[] =
{ '-', '6', 0x30fc },
{ '*', '6', 0x30fd },
{ '+', '6', 0x30fe },
+#define DG_START_BOPOMOFO 0x3105
{ 'b', '4', 0x3105 },
{ 'p', '4', 0x3106 },
{ 'm', '4', 0x3107 },
@@ -1341,6 +1422,7 @@ static digr_T digraphdefault[] =
{ 'v', '4', 0x312a },
{ 'n', 'G', 0x312b },
{ 'g', 'n', 0x312c },
+#define DG_START_OTHER3 0x3220
{ '1', 'c', 0x3220 },
{ '2', 'c', 0x3221 },
{ '3', 'c', 0x3222 },
@@ -1359,66 +1441,6 @@ static digr_T digraphdefault[] =
{ 'f', 't', 0xfb05 },
{ 's', 't', 0xfb06 },
- // extra alternatives, easier to remember
- { 'W', '`', 0x1e80 },
- { 'w', '`', 0x1e81 },
- { 'Y', '`', 0x1ef2 },
- { 'y', '`', 0x1ef3 },
-
- // Vim 5.x compatible digraphs that don't conflict with the above
- { '~', '!', 161 }, // ¡
- { 'c', '|', 162 }, // ¢
- { '$', '$', 163 }, // £
- { 'o', 'x', 164 }, // ¤ - currency symbol in ISO 8859-1
- { 'Y', '-', 165 }, // ¥
- { '|', '|', 166 }, // ¦
- { 'c', 'O', 169 }, // ©
- { '-', ',', 172 }, // ¬
- { '-', '=', 175 }, // ¯
- { '~', 'o', 176 }, // °
- { '2', '2', 178 }, // ²
- { '3', '3', 179 }, // ³
- { 'p', 'p', 182 }, // ¶
- { '~', '.', 183 }, // ·
- { '1', '1', 185 }, // ¹
- { '~', '?', 191 }, // ¿
- { 'A', '`', 192 }, // À
- { 'A', '^', 194 }, // Â
- { 'A', '~', 195 }, // Ã
- { 'A', '"', 196 }, // Ä
- { 'A', '@', 197 }, // Ã…
- { 'E', '`', 200 }, // È
- { 'E', '^', 202 }, // Ê
- { 'E', '"', 203 }, // Ë
- { 'I', '`', 204 }, // Ì
- { 'I', '^', 206 }, // ÃŽ
- { 'I', '"', 207 }, // Ã
- { 'N', '~', 209 }, // Ñ
- { 'O', '`', 210 }, // Ã’
- { 'O', '^', 212 }, // Ô
- { 'O', '~', 213 }, // Õ
- { '/', '\\', 215 }, // × - multiplication symbol in ISO 8859-1
- { 'U', '`', 217 }, // Ù
- { 'U', '^', 219 }, // Û
- { 'I', 'p', 222 }, // Þ
- { 'a', '`', 224 }, // à
- { 'a', '^', 226 }, // â
- { 'a', '~', 227 }, // ã
- { 'a', '"', 228 }, // ä
- { 'a', '@', 229 }, // å
- { 'e', '`', 232 }, // è
- { 'e', '^', 234 }, // ê
- { 'e', '"', 235 }, // ë
- { 'i', '`', 236 }, // ì
- { 'i', '^', 238 }, // î
- { 'n', '~', 241 }, // ñ
- { 'o', '`', 242 }, // ò
- { 'o', '^', 244 }, // ô
- { 'o', '~', 245 }, // õ
- { 'u', '`', 249 }, // ù
- { 'u', '^', 251 }, // û
- { 'y', '"', 255 }, // x XX
-
{ NUL, NUL, NUL }
};
@@ -1436,7 +1458,7 @@ int do_digraph(int c)
backspaced = -1;
} else if (p_dg) {
if (backspaced >= 0) {
- c = getdigraph(backspaced, c, FALSE);
+ c = getdigraph(backspaced, c, false);
}
backspaced = -1;
@@ -1450,8 +1472,9 @@ int do_digraph(int c)
/// Find a digraph for "val". If found return the string to display it.
/// If not found return NULL.
-char_u *get_digraph_for_char(int val)
+char_u *get_digraph_for_char(int val_arg)
{
+ const int val = val_arg;
digr_T *dp;
static char_u r[3];
@@ -1508,7 +1531,7 @@ int get_digraph(int cmdline)
if (cc != ESC) {
// ESC cancels CTRL-K
- return getdigraph(c, cc, TRUE);
+ return getdigraph(c, cc, true);
}
}
return NUL;
@@ -1520,9 +1543,9 @@ int get_digraph(int cmdline)
/// @param char2
/// @param meta_char
///
-/// @return If no match, return "char2". If "meta_char" is TRUE and "char1"
+/// @return If no match, return "char2". If "meta_char" is true and "char1"
// is a space, return "char2" | 0x80.
-static int getexactdigraph(int char1, int char2, int meta_char)
+static int getexactdigraph(int char1, int char2, bool meta_char)
{
int retval = 0;
@@ -1572,7 +1595,7 @@ static int getexactdigraph(int char1, int char2, int meta_char)
/// @param meta_char
///
/// @return The digraph.
-int getdigraph(int char1, int char2, int meta_char)
+int getdigraph(int char1, int char2, bool meta_char)
{
int retval;
@@ -1642,9 +1665,20 @@ void putdigraph(char_u *str)
}
}
-void listdigraphs(void)
+static void digraph_header(const char *msg)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (msg_col > 0) {
+ msg_putchar('\n');
+ }
+ msg_outtrans_attr((const char_u *)msg, HL_ATTR(HLF_CM));
+ msg_putchar('\n');
+}
+
+void listdigraphs(bool use_headers)
{
digr_T *dp;
+ result_T previous = 0;
msg_putchar('\n');
@@ -1656,25 +1690,63 @@ void listdigraphs(void)
// May need to convert the result to 'encoding'.
tmp.char1 = dp->char1;
tmp.char2 = dp->char2;
- tmp.result = getexactdigraph(tmp.char1, tmp.char2, FALSE);
+ tmp.result = getexactdigraph(tmp.char1, tmp.char2, false);
if ((tmp.result != 0)
&& (tmp.result != tmp.char2)) {
- printdigraph(&tmp);
+ printdigraph(&tmp, use_headers ? &previous : NULL);
}
dp++;
fast_breakcheck();
}
dp = (digr_T *)user_digraphs.ga_data;
- for (int i = 0; i < user_digraphs.ga_len && !got_int; ++i) {
- printdigraph(dp);
+ for (int i = 0; i < user_digraphs.ga_len && !got_int; i++) {
+ if (previous >= 0 && use_headers) {
+ digraph_header(_("Custom"));
+ }
+ previous = -1;
+ printdigraph(dp, NULL);
fast_breakcheck();
dp++;
}
}
-static void printdigraph(digr_T *dp)
+struct dg_header_entry {
+ int dg_start;
+ const char *dg_header;
+} header_table[] = {
+ { DG_START_LATIN, N_("Latin supplement") },
+ { DG_START_GREEK, N_("Greek and Coptic") },
+ { DG_START_CYRILLIC, N_("Cyrillic") },
+ { DG_START_HEBREW, N_("Hebrew") },
+ { DG_START_ARABIC, N_("Arabic") },
+ { DG_START_LATIN_EXTENDED, N_("Latin extended") },
+ { DG_START_GREEK_EXTENDED, N_("Greek extended") },
+ { DG_START_PUNCTUATION, N_("Punctuation") },
+ { DG_START_SUB_SUPER, N_("Super- and subscripts") },
+ { DG_START_CURRENCY, N_("Currency") },
+ { DG_START_OTHER1, N_("Other") },
+ { DG_START_ROMAN, N_("Roman numbers") },
+ { DG_START_ARROWS, N_("Arrows") },
+ { DG_START_MATH, N_("Mathematical operators") },
+ { DG_START_TECHNICAL, N_("Technical") },
+ { DG_START_OTHER2, N_("Other") },
+ { DG_START_DRAWING, N_("Box drawing") },
+ { DG_START_BLOCK, N_("Block elements") },
+ { DG_START_SHAPES, N_("Geometric shapes") },
+ { DG_START_SYMBOLS, N_("Symbols") },
+ { DG_START_DINGBATS, N_("Dingbats") },
+ { DG_START_CJK_SYMBOLS, N_("CJK symbols and punctuation") },
+ { DG_START_HIRAGANA, N_("Hiragana") },
+ { DG_START_KATAKANA, N_("Katakana") },
+ { DG_START_BOPOMOFO, N_("Bopomofo") },
+ { DG_START_OTHER3, N_("Other") },
+ { 0xfffffff, NULL },
+};
+
+static void printdigraph(const digr_T *dp, result_T *previous)
+ FUNC_ATTR_NONNULL_ARG(1)
{
char_u buf[30];
char_u *p;
@@ -1684,6 +1756,17 @@ static void printdigraph(digr_T *dp)
list_width = 13;
if (dp->result != 0) {
+ if (previous != NULL) {
+ for (int i = 0; header_table[i].dg_header != NULL; i++) {
+ if (*previous < header_table[i].dg_start
+ && dp->result >= header_table[i].dg_start
+ && dp->result < header_table[i + 1].dg_start) {
+ digraph_header(_(header_table[i].dg_header));
+ break;
+ }
+ }
+ *previous = dp->result;
+ }
if (msg_col > Columns - list_width) {
msg_putchar('\n');
}
@@ -1804,7 +1887,7 @@ void ex_loadkeymap(exarg_T *eap)
// Get each line of the sourced file, break at the end.
for (;;) {
- line = eap->getline(0, eap->cookie, 0);
+ line = eap->getline(0, eap->cookie, 0, true);
if (line == NULL) {
break;
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 16c4882975..eecea03a19 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -28,6 +28,7 @@
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/main.h"
+#include "nvim/mark_extended.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -1837,6 +1838,13 @@ change_indent (
xfree(new_line);
}
+
+ // change_indent seems to bec called twice, this combination only triggers
+ // once for both calls
+ if (new_cursor_col - vcol != 0) {
+ extmark_col_adjust(curbuf, curwin->w_cursor.lnum, 0, 0, amount,
+ kExtmarkUndo);
+ }
}
/*
@@ -3048,7 +3056,9 @@ static void ins_compl_clear(void)
XFREE_CLEAR(compl_orig_text);
compl_enter_selects = false;
// clear v:completed_item
- set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc());
+ dict_T *const d = tv_dict_alloc();
+ d->dv_lock = VAR_FIXED;
+ set_vim_var_dict(VV_COMPLETED_ITEM, d);
}
/// Check that Insert completion is active.
@@ -4047,12 +4057,14 @@ static int ins_compl_get_exp(pos_T *ini)
// Find up to TAG_MANY matches. Avoids that an enormous number
// of matches is found when compl_pattern is empty
+ g_tag_at_cursor = true;
if (find_tags(compl_pattern, &num_matches, &matches,
TAG_REGEXP | TAG_NAMES | TAG_NOIC | TAG_INS_COMP
| (l_ctrl_x_mode != CTRL_X_NORMAL ? TAG_VERBOSE : 0),
TAG_MANY, curbuf->b_ffname) == OK && num_matches > 0) {
ins_compl_add_matches(num_matches, matches, p_ic);
}
+ g_tag_at_cursor = false;
p_ic = save_p_ic;
break;
@@ -4116,7 +4128,7 @@ static int ins_compl_get_exp(pos_T *ini)
compl_direction,
compl_pattern, 1L,
SEARCH_KEEP + SEARCH_NFMSG,
- RE_LAST, (linenr_T)0, NULL, NULL);
+ RE_LAST, NULL);
}
msg_silent--;
if (!compl_started || set_match_pos) {
@@ -4303,7 +4315,9 @@ static void ins_compl_delete(void)
// causes flicker, thus we can't do that.
changed_cline_bef_curs();
// clear v:completed_item
- set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc());
+ dict_T *const d = tv_dict_alloc();
+ d->dv_lock = VAR_FIXED;
+ set_vim_var_dict(VV_COMPLETED_ITEM, d);
}
// Insert the new text being completed.
@@ -4325,6 +4339,7 @@ static dict_T *ins_compl_dict_alloc(compl_T *match)
{
// { word, abbr, menu, kind, info }
dict_T *dict = tv_dict_alloc();
+ dict->dv_lock = VAR_FIXED;
tv_dict_add_str(
dict, S_LEN("word"),
(const char *)EMPTY_IF_NULL(match->cp_str));
@@ -5479,10 +5494,10 @@ insertchar (
if (c == NUL) /* only formatting was wanted */
return;
- /* Check whether this character should end a comment. */
+ // Check whether this character should end a comment.
if (did_ai && c == end_comment_pending) {
char_u *line;
- char_u lead_end[COM_MAX_LEN]; /* end-comment string */
+ char_u lead_end[COM_MAX_LEN]; // end-comment string
int middle_len, end_len;
int i;
@@ -5490,39 +5505,40 @@ insertchar (
* Need to remove existing (middle) comment leader and insert end
* comment leader. First, check what comment leader we can find.
*/
- i = get_leader_len(line = get_cursor_line_ptr(), &p, FALSE, TRUE);
- if (i > 0 && vim_strchr(p, COM_MIDDLE) != NULL) { /* Just checking */
- /* Skip middle-comment string */
- while (*p && p[-1] != ':') /* find end of middle flags */
- ++p;
+ i = get_leader_len(line = get_cursor_line_ptr(), &p, false, true);
+ if (i > 0 && vim_strchr(p, COM_MIDDLE) != NULL) { // Just checking
+ // Skip middle-comment string
+ while (*p && p[-1] != ':') { // find end of middle flags
+ p++;
+ }
middle_len = copy_option_part(&p, lead_end, COM_MAX_LEN, ",");
- /* Don't count trailing white space for middle_len */
- while (middle_len > 0 && ascii_iswhite(lead_end[middle_len - 1]))
- --middle_len;
+ // Don't count trailing white space for middle_len
+ while (middle_len > 0 && ascii_iswhite(lead_end[middle_len - 1])) {
+ middle_len--;
+ }
- /* Find the end-comment string */
- while (*p && p[-1] != ':') /* find end of end flags */
- ++p;
+ // Find the end-comment string
+ while (*p && p[-1] != ':') { // find end of end flags
+ p++;
+ }
end_len = copy_option_part(&p, lead_end, COM_MAX_LEN, ",");
- /* Skip white space before the cursor */
+ // Skip white space before the cursor
i = curwin->w_cursor.col;
while (--i >= 0 && ascii_iswhite(line[i]))
;
i++;
- /* Skip to before the middle leader */
+ // Skip to before the middle leader
i -= middle_len;
- /* Check some expected things before we go on */
+ // Check some expected things before we go on
if (i >= 0 && lead_end[end_len - 1] == end_comment_pending) {
- /* Backspace over all the stuff we want to replace */
+ // Backspace over all the stuff we want to replace
backspace_until_column(i);
- /*
- * Insert the end-comment string, except for the last
- * character, which will get inserted as normal later.
- */
+ // Insert the end-comment string, except for the last
+ // character, which will get inserted as normal later.
ins_bytes_len(lead_end, end_len - 1);
}
}
@@ -5603,10 +5619,11 @@ insertchar (
AppendCharToRedobuff(c);
} else {
ins_char(c);
- if (flags & INSCHAR_CTRLV)
+ if (flags & INSCHAR_CTRLV) {
redo_literal(c);
- else
+ } else {
AppendCharToRedobuff(c);
+ }
}
}
}
@@ -6888,8 +6905,9 @@ static void mb_replace_pop_ins(int cc)
for (i = 1; i < n; ++i)
buf[i] = replace_pop();
ins_bytes_len(buf, n);
- } else
+ } else {
ins_char(cc);
+ }
if (enc_utf8)
/* Handle composing chars. */
@@ -7999,9 +8017,9 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
Insstart_orig.col = curwin->w_cursor.col;
}
- if (State & VREPLACE_FLAG)
+ if (State & VREPLACE_FLAG) {
ins_char(' ');
- else {
+ } else {
ins_str((char_u *)" ");
if ((State & REPLACE_FLAG))
replace_push(NUL);
@@ -8479,6 +8497,7 @@ static bool ins_tab(void)
} else { // otherwise use "tabstop"
temp = (int)curbuf->b_p_ts;
}
+
temp -= get_nolist_virtcol() % temp;
/*
@@ -8488,12 +8507,13 @@ static bool ins_tab(void)
*/
ins_char(' ');
while (--temp > 0) {
- if (State & VREPLACE_FLAG)
+ if (State & VREPLACE_FLAG) {
ins_char(' ');
- else {
+ } else {
ins_str((char_u *)" ");
- if (State & REPLACE_FLAG) /* no char replaced */
+ if (State & REPLACE_FLAG) { // no char replaced
replace_push(NUL);
+ }
}
}
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index e39f36957c..a556f0bbbe 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -422,6 +422,7 @@ static struct vimvar {
VV(VV_TYPE_BOOL, "t_bool", VAR_NUMBER, VV_RO),
VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO),
VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO),
+ VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO),
};
#undef VV
@@ -433,11 +434,14 @@ static struct vimvar {
#define vv_str vv_di.di_tv.vval.v_string
#define vv_list vv_di.di_tv.vval.v_list
#define vv_dict vv_di.di_tv.vval.v_dict
+#define vv_partial vv_di.di_tv.vval.v_partial
#define vv_tv vv_di.di_tv
/// Variable used for v:
static ScopeDictDictItem vimvars_var;
+static partial_T *vvlua_partial;
+
/// v: hashtab
#define vimvarht vimvardict.dv_hashtab
@@ -639,6 +643,13 @@ void eval_init(void)
set_vim_var_nr(VV_ECHOSPACE, sc_col - 1);
+ vimvars[VV_LUA].vv_type = VAR_PARTIAL;
+ vvlua_partial = xcalloc(1, sizeof(partial_T));
+ vimvars[VV_LUA].vv_partial = vvlua_partial;
+ // this value shouldn't be printed, but if it is, do not crash
+ vvlua_partial->pt_name = xmallocz(0);
+ vvlua_partial->pt_refcount++;
+
set_reg_var(0); // default for v:register is not 0 but '"'
}
@@ -1313,12 +1324,25 @@ int call_vim_function(
{
int doesrange;
int ret;
+ int len = (int)STRLEN(func);
+ partial_T *pt = NULL;
+
+ if (len >= 6 && !memcmp(func, "v:lua.", 6)) {
+ func += 6;
+ len = check_luafunc_name((const char *)func, false);
+ if (len == 0) {
+ ret = FAIL;
+ goto fail;
+ }
+ pt = vvlua_partial;
+ }
rettv->v_type = VAR_UNKNOWN; // tv_clear() uses this.
- ret = call_func(func, (int)STRLEN(func), rettv, argc, argv, NULL,
+ ret = call_func(func, len, rettv, argc, argv, NULL,
curwin->w_cursor.lnum, curwin->w_cursor.lnum,
- &doesrange, true, NULL, NULL);
+ &doesrange, true, pt, NULL);
+fail:
if (ret == FAIL) {
tv_clear(rettv);
}
@@ -1504,6 +1528,116 @@ void ex_const(exarg_T *eap)
ex_let_const(eap, true);
}
+// Get a list of lines from a HERE document. The here document is a list of
+// lines surrounded by a marker.
+// cmd << {marker}
+// {line1}
+// {line2}
+// ....
+// {marker}
+//
+// The {marker} is a string. If the optional 'trim' word is supplied before the
+// marker, then the leading indentation before the lines (matching the
+// indentation in the 'cmd' line) is stripped.
+// Returns a List with {lines} or NULL.
+static list_T *
+heredoc_get(exarg_T *eap, char_u *cmd)
+{
+ char_u *marker;
+ char_u *p;
+ int marker_indent_len = 0;
+ int text_indent_len = 0;
+ char_u *text_indent = NULL;
+
+ if (eap->getline == NULL) {
+ EMSG(_("E991: cannot use =<< here"));
+ return NULL;
+ }
+
+ // Check for the optional 'trim' word before the marker
+ cmd = skipwhite(cmd);
+ if (STRNCMP(cmd, "trim", 4) == 0
+ && (cmd[4] == NUL || ascii_iswhite(cmd[4]))) {
+ cmd = skipwhite(cmd + 4);
+
+ // Trim the indentation from all the lines in the here document.
+ // The amount of indentation trimmed is the same as the indentation of
+ // the first line after the :let command line. To find the end marker
+ // the indent of the :let command line is trimmed.
+ p = *eap->cmdlinep;
+ while (ascii_iswhite(*p)) {
+ p++;
+ marker_indent_len++;
+ }
+ text_indent_len = -1;
+ }
+
+ // The marker is the next word.
+ if (*cmd != NUL && *cmd != '"') {
+ marker = skipwhite(cmd);
+ p = skiptowhite(marker);
+ if (*skipwhite(p) != NUL && *skipwhite(p) != '"') {
+ EMSG(_(e_trailing));
+ return NULL;
+ }
+ *p = NUL;
+ if (islower(*marker)) {
+ EMSG(_("E221: Marker cannot start with lower case letter"));
+ return NULL;
+ }
+ } else {
+ EMSG(_("E172: Missing marker"));
+ return NULL;
+ }
+
+ list_T *l = tv_list_alloc(0);
+ for (;;) {
+ int mi = 0;
+ int ti = 0;
+
+ char_u *theline = eap->getline(NUL, eap->cookie, 0, false);
+ if (theline == NULL) {
+ EMSG2(_("E990: Missing end marker '%s'"), marker);
+ break;
+ }
+
+ // with "trim": skip the indent matching the :let line to find the
+ // marker
+ if (marker_indent_len > 0
+ && STRNCMP(theline, *eap->cmdlinep, marker_indent_len) == 0) {
+ mi = marker_indent_len;
+ }
+ if (STRCMP(marker, theline + mi) == 0) {
+ xfree(theline);
+ break;
+ }
+ if (text_indent_len == -1 && *theline != NUL) {
+ // set the text indent from the first line.
+ p = theline;
+ text_indent_len = 0;
+ while (ascii_iswhite(*p)) {
+ p++;
+ text_indent_len++;
+ }
+ text_indent = vim_strnsave(theline, text_indent_len);
+ }
+ // with "trim": skip the indent matching the first line
+ if (text_indent != NULL) {
+ for (ti = 0; ti < text_indent_len; ti++) {
+ if (theline[ti] != text_indent[ti]) {
+ break;
+ }
+ }
+ }
+
+ tv_list_append_string(l, (char *)(theline + ti), -1);
+ xfree(theline);
+ }
+ xfree(text_indent);
+
+ return l;
+}
+
// ":let" list all variable values
// ":let var1 var2" list variable values
// ":let var = expr" assignment command.
@@ -1560,6 +1694,19 @@ static void ex_let_const(exarg_T *eap, const bool is_const)
list_vim_vars(&first);
}
eap->nextcmd = check_nextcmd(arg);
+ } else if (expr[0] == '=' && expr[1] == '<' && expr[2] == '<') {
+ // HERE document
+ list_T *l = heredoc_get(eap, expr + 3);
+ if (l != NULL) {
+ tv_list_set_ret(&rettv, l);
+ if (!eap->skip) {
+ op[0] = '=';
+ op[1] = NUL;
+ (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count,
+ is_const, op);
+ }
+ tv_clear(&rettv);
+ }
} else {
op[0] = '=';
op[1] = NUL;
@@ -2339,6 +2486,13 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,
}
}
+ if (lp->ll_di != NULL && tv_is_luafunc(&lp->ll_di->di_tv)
+ && len == -1 && rettv == NULL) {
+ tv_clear(&var1);
+ EMSG2(e_illvar, "v:['lua']");
+ return NULL;
+ }
+
if (lp->ll_di == NULL) {
// Can't add "v:" or "a:" variable.
if (lp->ll_dict == &vimvardict
@@ -2718,7 +2872,7 @@ void set_context_for_expression(expand_T *xp, char_u *arg, cmdidx_T cmdidx)
int c;
char_u *p;
- if (cmdidx == CMD_let) {
+ if (cmdidx == CMD_let || cmdidx == CMD_const) {
xp->xp_context = EXPAND_USER_VARS;
if (vim_strpbrk(arg, (char_u *)"\"'+-*/%.=!?~|&$([<>,#") == NULL) {
/* ":let var1 var2 ...": find last space. */
@@ -4576,7 +4730,7 @@ eval_index(
if (evaluate) {
n1 = 0;
- if (!empty1 && rettv->v_type != VAR_DICT) {
+ if (!empty1 && rettv->v_type != VAR_DICT && !tv_is_luafunc(rettv)) {
n1 = tv_get_number(&var1);
tv_clear(&var1);
}
@@ -4700,7 +4854,7 @@ eval_index(
if (len == -1) {
tv_clear(&var1);
}
- if (item == NULL) {
+ if (item == NULL || tv_is_luafunc(&item->di_tv)) {
return FAIL;
}
@@ -5746,13 +5900,13 @@ static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate)
goto failret;
}
item = tv_dict_item_alloc((const char *)key);
- tv_clear(&tvkey);
item->di_tv = tv;
item->di_tv.v_lock = 0;
if (tv_dict_add(d, item) == FAIL) {
tv_dict_item_free(item);
}
}
+ tv_clear(&tvkey);
if (**arg == '}')
break;
@@ -6211,7 +6365,7 @@ static char_u *deref_func_name(const char *name, int *lenp,
*/
static int
get_func_tv(
- char_u *name, // name of the function
+ const char_u *name, // name of the function
int len, // length of "name"
typval_T *rettv,
char_u **arg, // argument, pointing to the '('
@@ -6467,7 +6621,15 @@ call_func(
rettv->vval.v_number = 0;
error = ERROR_UNKNOWN;
- if (!builtin_function((const char *)rfname, -1)) {
+ if (partial == vvlua_partial) {
+ if (len > 0) {
+ error = ERROR_NONE;
+ executor_call_lua((const char *)funcname, len,
+ argvars, argcount, rettv);
+ } else {
+ error = ERROR_UNKNOWN;
+ }
+ } else if (!builtin_function((const char *)rfname, -1)) {
// User defined function.
if (partial != NULL && partial->pt_func != NULL) {
fp = partial->pt_func;
@@ -6584,14 +6746,14 @@ call_func(
///
/// @param ermsg must be passed without translation (use N_() instead of _()).
/// @param name function name
-static void emsg_funcname(char *ermsg, char_u *name)
+static void emsg_funcname(char *ermsg, const char_u *name)
{
char_u *p;
if (*name == K_SPECIAL) {
p = concat_str((char_u *)"<SNR>", name + 3);
} else {
- p = name;
+ p = (char_u *)name;
}
EMSG2(_(ermsg), p);
@@ -6947,7 +7109,8 @@ static void assert_error(garray_T *gap)
(const char *)gap->ga_data, (ptrdiff_t)gap->ga_len);
}
-static void assert_equal_common(typval_T *argvars, assert_type_T atype)
+static int assert_equal_common(typval_T *argvars, assert_type_T atype)
+ FUNC_ATTR_NONNULL_ALL
{
garray_T ga;
@@ -6958,13 +7121,70 @@ static void assert_equal_common(typval_T *argvars, assert_type_T atype)
&argvars[0], &argvars[1], atype);
assert_error(&ga);
ga_clear(&ga);
+ return 1;
+ }
+ return 0;
+}
+
+static int assert_equalfile(typval_T *argvars)
+ FUNC_ATTR_NONNULL_ALL
+{
+ char buf1[NUMBUFLEN];
+ char buf2[NUMBUFLEN];
+ const char *const fname1 = tv_get_string_buf_chk(&argvars[0], buf1);
+ const char *const fname2 = tv_get_string_buf_chk(&argvars[1], buf2);
+ garray_T ga;
+
+ if (fname1 == NULL || fname2 == NULL) {
+ return 0;
+ }
+
+ IObuff[0] = NUL;
+ FILE *const fd1 = os_fopen(fname1, READBIN);
+ if (fd1 == NULL) {
+ snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname1);
+ } else {
+ FILE *const fd2 = os_fopen(fname2, READBIN);
+ if (fd2 == NULL) {
+ fclose(fd1);
+ snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname2);
+ } else {
+ for (int64_t count = 0; ; count++) {
+ const int c1 = fgetc(fd1);
+ const int c2 = fgetc(fd2);
+ if (c1 == EOF) {
+ if (c2 != EOF) {
+ STRCPY(IObuff, "first file is shorter");
+ }
+ break;
+ } else if (c2 == EOF) {
+ STRCPY(IObuff, "second file is shorter");
+ break;
+ } else if (c1 != c2) {
+ snprintf((char *)IObuff, IOSIZE,
+ "difference at byte %" PRId64, count);
+ break;
+ }
+ }
+ fclose(fd1);
+ fclose(fd2);
+ }
+ }
+ if (IObuff[0] != NUL) {
+ prepare_assert_error(&ga);
+ ga_concat(&ga, IObuff);
+ assert_error(&ga);
+ ga_clear(&ga);
+ return 1;
}
+ return 0;
}
static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
const char *const cmd = tv_get_string_chk(&argvars[0]);
garray_T ga;
+ int ret = 0;
called_vim_beep = false;
suppress_errthrow = true;
@@ -6976,22 +7196,30 @@ static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr)
ga_concat(&ga, (const char_u *)cmd);
assert_error(&ga);
ga_clear(&ga);
+ ret = 1;
}
suppress_errthrow = false;
emsg_on_display = false;
+ rettv->vval.v_number = ret;
}
// "assert_equal(expected, actual[, msg])" function
static void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- assert_equal_common(argvars, ASSERT_EQUAL);
+ rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL);
+}
+
+// "assert_equalfile(fname-one, fname-two)" function
+static void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = assert_equalfile(argvars);
}
// "assert_notequal(expected, actual[, msg])" function
static void f_assert_notequal(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- assert_equal_common(argvars, ASSERT_NOTEQUAL);
+ rettv->vval.v_number = assert_equal_common(argvars, ASSERT_NOTEQUAL);
}
/// "assert_report(msg)
@@ -7003,34 +7231,21 @@ static void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr)
ga_concat(&ga, (const char_u *)tv_get_string(&argvars[0]));
assert_error(&ga);
ga_clear(&ga);
+ rettv->vval.v_number = 1;
}
/// "assert_exception(string[, msg])" function
static void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- garray_T ga;
-
- const char *const error = tv_get_string_chk(&argvars[0]);
- if (vimvars[VV_EXCEPTION].vv_str == NULL) {
- prepare_assert_error(&ga);
- ga_concat(&ga, (char_u *)"v:exception is not set");
- assert_error(&ga);
- ga_clear(&ga);
- } else if (error != NULL
- && strstr((char *)vimvars[VV_EXCEPTION].vv_str, error) == NULL) {
- prepare_assert_error(&ga);
- fill_assert_error(&ga, &argvars[1], NULL, &argvars[0],
- &vimvars[VV_EXCEPTION].vv_tv, ASSERT_OTHER);
- assert_error(&ga);
- ga_clear(&ga);
- }
+ rettv->vval.v_number = assert_exception(argvars);
}
-/// "assert_fails(cmd [, error])" function
+/// "assert_fails(cmd [, error [, msg]])" function
static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
const char *const cmd = tv_get_string_chk(&argvars[0]);
garray_T ga;
+ int ret = 0;
int save_trylevel = trylevel;
// trylevel must be zero for a ":throw" command to be considered failed
@@ -7043,9 +7258,17 @@ static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (!called_emsg) {
prepare_assert_error(&ga);
ga_concat(&ga, (const char_u *)"command did not fail: ");
- ga_concat(&ga, (const char_u *)cmd);
+ if (argvars[1].v_type != VAR_UNKNOWN
+ && argvars[2].v_type != VAR_UNKNOWN) {
+ char *const tofree = encode_tv2echo(&argvars[2], NULL);
+ ga_concat(&ga, (char_u *)tofree);
+ xfree(tofree);
+ } else {
+ ga_concat(&ga, (const char_u *)cmd);
+ }
assert_error(&ga);
ga_clear(&ga);
+ ret = 1;
} else if (argvars[1].v_type != VAR_UNKNOWN) {
char buf[NUMBUFLEN];
const char *const error = tv_get_string_buf_chk(&argvars[1], buf);
@@ -7057,6 +7280,7 @@ static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr)
&vimvars[VV_ERRMSG].vv_tv, ASSERT_OTHER);
assert_error(&ga);
ga_clear(&ga);
+ ret = 1;
}
}
@@ -7066,9 +7290,11 @@ static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr)
emsg_silent = false;
emsg_on_display = false;
set_vim_var_string(VV_ERRMSG, NULL, 0);
+ rettv->vval.v_number = ret;
}
-void assert_inrange(typval_T *argvars)
+static int assert_inrange(typval_T *argvars)
+ FUNC_ATTR_NONNULL_ALL
{
bool error = false;
const varnumber_T lower = tv_get_number_chk(&argvars[0], &error);
@@ -7076,7 +7302,7 @@ void assert_inrange(typval_T *argvars)
const varnumber_T actual = tv_get_number_chk(&argvars[2], &error);
if (error) {
- return;
+ return 0;
}
if (actual < lower || actual > upper) {
garray_T ga;
@@ -7090,11 +7316,14 @@ void assert_inrange(typval_T *argvars)
ASSERT_INRANGE);
assert_error(&ga);
ga_clear(&ga);
+ return 1;
}
+ return 0;
}
// Common for assert_true() and assert_false().
-static void assert_bool(typval_T *argvars, bool is_true)
+static int assert_bool(typval_T *argvars, bool is_true)
+ FUNC_ATTR_NONNULL_ALL
{
bool error = false;
garray_T ga;
@@ -7113,16 +7342,43 @@ static void assert_bool(typval_T *argvars, bool is_true)
NULL, &argvars[0], ASSERT_OTHER);
assert_error(&ga);
ga_clear(&ga);
+ return 1;
+ }
+ return 0;
+}
+
+static int assert_exception(typval_T *argvars)
+ FUNC_ATTR_NONNULL_ALL
+{
+ garray_T ga;
+
+ const char *const error = tv_get_string_chk(&argvars[0]);
+ if (vimvars[VV_EXCEPTION].vv_str == NULL) {
+ prepare_assert_error(&ga);
+ ga_concat(&ga, (char_u *)"v:exception is not set");
+ assert_error(&ga);
+ ga_clear(&ga);
+ return 1;
+ } else if (error != NULL
+ && strstr((char *)vimvars[VV_EXCEPTION].vv_str, error) == NULL) {
+ prepare_assert_error(&ga);
+ fill_assert_error(&ga, &argvars[1], NULL, &argvars[0],
+ &vimvars[VV_EXCEPTION].vv_tv, ASSERT_OTHER);
+ assert_error(&ga);
+ ga_clear(&ga);
+ return 1;
}
+ return 0;
}
// "assert_false(actual[, msg])" function
static void f_assert_false(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- assert_bool(argvars, false);
+ rettv->vval.v_number = assert_bool(argvars, false);
}
-static void assert_match_common(typval_T *argvars, assert_type_T atype)
+static int assert_match_common(typval_T *argvars, assert_type_T atype)
+ FUNC_ATTR_NONNULL_ALL
{
char buf1[NUMBUFLEN];
char buf2[NUMBUFLEN];
@@ -7138,31 +7394,33 @@ static void assert_match_common(typval_T *argvars, assert_type_T atype)
fill_assert_error(&ga, &argvars[2], NULL, &argvars[0], &argvars[1], atype);
assert_error(&ga);
ga_clear(&ga);
+ return 1;
}
+ return 0;
}
/// "assert_inrange(lower, upper[, msg])" function
static void f_assert_inrange(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- assert_inrange(argvars);
+ rettv->vval.v_number = assert_inrange(argvars);
}
/// "assert_match(pattern, actual[, msg])" function
static void f_assert_match(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- assert_match_common(argvars, ASSERT_MATCH);
+ rettv->vval.v_number = assert_match_common(argvars, ASSERT_MATCH);
}
/// "assert_notmatch(pattern, actual[, msg])" function
static void f_assert_notmatch(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- assert_match_common(argvars, ASSERT_NOTMATCH);
+ rettv->vval.v_number = assert_match_common(argvars, ASSERT_NOTMATCH);
}
// "assert_true(actual[, msg])" function
static void f_assert_true(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- assert_bool(argvars, true);
+ rettv->vval.v_number = assert_bool(argvars, true);
}
/*
@@ -8458,18 +8716,25 @@ static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
tv_dict_alloc_ret(rettv);
- for (int i = 0; ; i++) {
- // TODO(justinmk): use os_copyfullenv from #7202 ?
- char *envname = os_getenvname_at_index((size_t)i);
- if (envname == NULL) {
- break;
- }
- const char *value = os_getenv(envname);
+ size_t env_size = os_get_fullenv_size();
+ char **env = xmalloc(sizeof(*env) * (env_size + 1));
+ env[env_size] = NULL;
+
+ os_copy_fullenv(env, env_size);
+
+ for (size_t i = 0; i < env_size; i++) {
+ const char * str = env[i];
+ const char * const end = strchr(str + (str[0] == '=' ? 1 : 0),
+ '=');
+ assert(end != NULL);
+ ptrdiff_t len = end - str;
+ assert(len > 0);
+ const char * value = str + len + 1;
tv_dict_add_str(rettv->vval.v_dict,
- (char *)envname, STRLEN((char *)envname),
- value == NULL ? "" : value);
- xfree(envname);
+ str, len,
+ value);
}
+ os_free_fullenv(env);
}
/*
@@ -8492,7 +8757,7 @@ static void f_getenv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (p == NULL) {
rettv->v_type = VAR_SPECIAL;
- rettv->vval.v_number = kSpecialVarNull;
+ rettv->vval.v_special = kSpecialVarNull;
return;
}
rettv->vval.v_string = p;
@@ -8546,7 +8811,7 @@ typedef struct {
const listitem_T *li;
} GetListLineCookie;
-static char_u *get_list_line(int c, void *cookie, int indent)
+static char_u *get_list_line(int c, void *cookie, int indent, bool do_concat)
{
GetListLineCookie *const p = (GetListLineCookie *)cookie;
@@ -11245,6 +11510,9 @@ static void f_glob2regpat(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
static const char *const has_list[] = {
+#if defined(BSD) && !defined(__APPLE__)
+ "bsd",
+#endif
#ifdef UNIX
"unix",
#endif
@@ -12316,6 +12584,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
bool executable = true;
char **argv = tv_to_argv(&argvars[0], NULL, &executable);
+ char **env = NULL;
if (!argv) {
rettv->vval.v_number = executable ? 0 : -1;
return; // Did error message in tv_to_argv.
@@ -12333,6 +12602,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
bool detach = false;
bool rpc = false;
bool pty = false;
+ bool clear_env = false;
CallbackReader on_stdout = CALLBACK_READER_INIT,
on_stderr = CALLBACK_READER_INIT;
Callback on_exit = CALLBACK_NONE;
@@ -12343,6 +12613,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
detach = tv_dict_get_number(job_opts, "detach") != 0;
rpc = tv_dict_get_number(job_opts, "rpc") != 0;
pty = tv_dict_get_number(job_opts, "pty") != 0;
+ clear_env = tv_dict_get_number(job_opts, "clear_env") != 0;
if (pty && rpc) {
EMSG2(_(e_invarg2), "job cannot have both 'pty' and 'rpc' options set");
shell_free_argv(argv);
@@ -12359,6 +12630,45 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
}
+ dictitem_T *job_env = tv_dict_find(job_opts, S_LEN("env"));
+ if (job_env) {
+ if (job_env->di_tv.v_type != VAR_DICT) {
+ EMSG2(_(e_invarg2), "env");
+ shell_free_argv(argv);
+ return;
+ }
+
+ size_t custom_env_size = (size_t)tv_dict_len(job_env->di_tv.vval.v_dict);
+ size_t i = 0;
+ size_t env_size = 0;
+
+ if (clear_env) {
+ // + 1 for last null entry
+ env = xmalloc((custom_env_size + 1) * sizeof(*env));
+ env_size = 0;
+ } else {
+ env_size = os_get_fullenv_size();
+
+ env = xmalloc((custom_env_size + env_size + 1) * sizeof(*env));
+
+ os_copy_fullenv(env, env_size);
+ i = env_size;
+ }
+ assert(env); // env must be allocated at this point
+
+ TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, {
+ const char *str = tv_get_string(&var->di_tv);
+ assert(str);
+ size_t len = STRLEN(var->di_key) + strlen(str) + strlen("=") + 1;
+ env[i] = xmalloc(len);
+ snprintf(env[i], len, "%s=%s", (char *)var->di_key, str);
+ i++;
+ });
+
+ // must be null terminated
+ env[env_size + custom_env_size] = NULL;
+ }
+
if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) {
shell_free_argv(argv);
@@ -12376,8 +12686,8 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty,
- rpc, detach, cwd, width, height, term_name,
- &rettv->vval.v_number);
+ rpc, detach, cwd, width, height,
+ term_name, env, &rettv->vval.v_number);
if (chan) {
channel_create_event(chan, NULL);
}
@@ -14398,6 +14708,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
long time_limit = 0;
int options = SEARCH_KEEP;
int subpatnum;
+ searchit_arg_T sia;
const char *const pat = tv_get_string(&argvars[0]);
dir = get_search_arg(&argvars[1], flagsp); // May set p_ws.
@@ -14445,8 +14756,11 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
}
pos = save_cursor = curwin->w_cursor;
+ memset(&sia, 0, sizeof(sia));
+ sia.sa_stop_lnum = (linenr_T)lnum_stop;
+ sia.sa_tm = &tm;
subpatnum = searchit(curwin, curbuf, &pos, NULL, dir, (char_u *)pat, 1,
- options, RE_SEARCH, (linenr_T)lnum_stop, &tm, NULL);
+ options, RE_SEARCH, &sia);
if (subpatnum != FAIL) {
if (flags & SP_SUBPAT)
retval = subpatnum;
@@ -14668,7 +14982,7 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT,
CALLBACK_READER_INIT, CALLBACK_NONE,
- false, true, false, NULL, 0, 0, NULL,
+ false, true, false, NULL, 0, 0, NULL, NULL,
&rettv->vval.v_number);
if (chan) {
channel_create_event(chan, NULL);
@@ -15018,8 +15332,13 @@ do_searchpair(
clearpos(&foundpos);
pat = pat3;
for (;; ) {
+ searchit_arg_T sia;
+ memset(&sia, 0, sizeof(sia));
+ sia.sa_stop_lnum = lnum_stop;
+ sia.sa_tm = &tm;
+
n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L,
- options, RE_SEARCH, lnum_stop, &tm, NULL);
+ options, RE_SEARCH, &sia);
if (n == FAIL || (firstpos.lnum != 0 && equalpos(pos, firstpos))) {
// didn't find it or found the first match again: FAIL
break;
@@ -15406,7 +15725,7 @@ static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
else
set_last_csearch(PTR2CHAR(csearch),
- csearch, MB_PTR2LEN(csearch));
+ csearch, utfc_ptr2len(csearch));
}
di = tv_dict_find(d, S_LEN("forward"));
@@ -15441,7 +15760,7 @@ static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
const char *name = tv_get_string_buf(&argvars[0], namebuf);
if (argvars[1].v_type == VAR_SPECIAL
- && argvars[1].vval.v_number == kSpecialVarNull) {
+ && argvars[1].vval.v_special == kSpecialVarNull) {
os_unsetenv(name);
} else {
os_setenv(name, tv_get_string_buf(&argvars[1], valbuf), 1);
@@ -18050,7 +18369,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit,
true, false, false, cwd,
term_width, curwin->w_height_inner,
- xstrdup("xterm-256color"),
+ xstrdup("xterm-256color"), NULL,
&rettv->vval.v_number);
if (rettv->vval.v_number <= 0) {
return;
@@ -18350,6 +18669,7 @@ static void timer_due_cb(TimeWatcher *tw, void *data)
timer_T *timer = (timer_T *)data;
int save_did_emsg = did_emsg;
int save_called_emsg = called_emsg;
+ const bool save_ex_pressedreturn = get_pressedreturn();
if (timer->stopped || timer->paused) {
return;
@@ -18378,6 +18698,7 @@ static void timer_due_cb(TimeWatcher *tw, void *data)
}
did_emsg = save_did_emsg;
called_emsg = save_called_emsg;
+ set_pressedreturn(save_ex_pressedreturn);
if (timer->emsg_count >= 3) {
timer_stop(timer);
@@ -18747,8 +19068,9 @@ static void f_visualmode(typval_T *argvars, typval_T *rettv, FunPtr fptr)
*/
static void f_wildmenumode(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- if (wild_menu_showing)
+ if (wild_menu_showing || ((State & CMDLINE) && pum_visible())) {
rettv->vval.v_number = 1;
+ }
}
/// "win_findbuf()" function
@@ -19937,6 +20259,26 @@ static void check_vars(const char *name, size_t len)
}
}
+/// check if special v:lua value for calling lua functions
+static bool tv_is_luafunc(typval_T *tv)
+{
+ return tv->v_type == VAR_PARTIAL && tv->vval.v_partial == vvlua_partial;
+}
+
+/// check the function name after "v:lua."
+static int check_luafunc_name(const char *str, bool paren)
+{
+ const char *p = str;
+ while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.') {
+ p++;
+ }
+ if (*p != (paren ? '(' : NUL)) {
+ return 0;
+ } else {
+ return (int)(p-str);
+ }
+}
+
/// Handle expr[expr], expr[expr:expr] subscript and .name lookup.
/// Also handle function call with Funcref variable: func(expr)
/// Can all be combined: dict.func(expr)[idx]['func'](expr)
@@ -19950,9 +20292,30 @@ handle_subscript(
{
int ret = OK;
dict_T *selfdict = NULL;
- char_u *s;
+ const char_u *s;
int len;
typval_T functv;
+ int slen = 0;
+ bool lua = false;
+
+ if (tv_is_luafunc(rettv)) {
+ if (**arg != '.') {
+ tv_clear(rettv);
+ ret = FAIL;
+ } else {
+ (*arg)++;
+
+ lua = true;
+ s = (char_u *)(*arg);
+ slen = check_luafunc_name(*arg, true);
+ if (slen == 0) {
+ tv_clear(rettv);
+ ret = FAIL;
+ }
+ (*arg) += slen;
+ }
+ }
+
while (ret == OK
&& (**arg == '['
@@ -19969,14 +20332,16 @@ handle_subscript(
// Invoke the function. Recursive!
if (functv.v_type == VAR_PARTIAL) {
pt = functv.vval.v_partial;
- s = partial_name(pt);
+ if (!lua) {
+ s = partial_name(pt);
+ }
} else {
s = functv.vval.v_string;
}
} else {
s = (char_u *)"";
}
- ret = get_func_tv(s, (int)STRLEN(s), rettv, (char_u **)arg,
+ ret = get_func_tv(s, lua ? slen : (int)STRLEN(s), rettv, (char_u **)arg,
curwin->w_cursor.lnum, curwin->w_cursor.lnum,
&len, evaluate, pt, selfdict);
@@ -20211,7 +20576,7 @@ static hashtab_T *get_funccal_local_ht(void)
return &get_funccal()->l_vars.dv_hashtab;
}
-/// Find the dict and hashtable used for a variable
+/// Finds the dict (g:, l:, s:, …) and hashtable used for a variable.
///
/// @param[in] name Variable name, possibly with scope prefix.
/// @param[in] name_len Variable name length.
@@ -21077,7 +21442,6 @@ void ex_function(exarg_T *eap)
bool overwrite = false;
int indent;
int nesting;
- char_u *skip_until = NULL;
dictitem_T *v;
funcdict_T fudi;
static int func_nr = 0; /* number for nameless function */
@@ -21085,8 +21449,13 @@ void ex_function(exarg_T *eap)
hashtab_T *ht;
int todo;
hashitem_T *hi;
- int sourcing_lnum_off;
+ linenr_T sourcing_lnum_off;
+ linenr_T sourcing_lnum_top;
+ bool is_heredoc = false;
+ char_u *skip_until = NULL;
+ char_u *heredoc_trimmed = NULL;
bool show_block = false;
+ bool do_concat = true;
/*
* ":function" without argument: list functions.
@@ -21333,15 +21702,17 @@ void ex_function(exarg_T *eap)
cmdline_row = msg_row;
}
+ // Save the starting line number.
+ sourcing_lnum_top = sourcing_lnum;
+
indent = 2;
nesting = 0;
for (;; ) {
if (KeyTyped) {
- msg_scroll = TRUE;
- saved_wait_return = FALSE;
+ msg_scroll = true;
+ saved_wait_return = false;
}
- need_wait_return = FALSE;
- sourcing_lnum_off = sourcing_lnum;
+ need_wait_return = false;
if (line_arg != NULL) {
/* Use eap->arg, split up in parts by line breaks. */
@@ -21356,9 +21727,9 @@ void ex_function(exarg_T *eap)
} else {
xfree(line_to_free);
if (eap->getline == NULL) {
- theline = getcmdline(':', 0L, indent);
+ theline = getcmdline(':', 0L, indent, do_concat);
} else {
- theline = eap->getline(':', eap->cookie, indent);
+ theline = eap->getline(':', eap->cookie, indent, do_concat);
}
line_to_free = theline;
}
@@ -21374,17 +21745,37 @@ void ex_function(exarg_T *eap)
ui_ext_cmdline_block_append((size_t)indent, (const char *)theline);
}
- /* Detect line continuation: sourcing_lnum increased more than one. */
- if (sourcing_lnum > sourcing_lnum_off + 1)
- sourcing_lnum_off = sourcing_lnum - sourcing_lnum_off - 1;
- else
+ // Detect line continuation: sourcing_lnum increased more than one.
+ sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie);
+ if (sourcing_lnum < sourcing_lnum_off) {
+ sourcing_lnum_off -= sourcing_lnum;
+ } else {
sourcing_lnum_off = 0;
+ }
if (skip_until != NULL) {
- /* between ":append" and "." and between ":python <<EOF" and "EOF"
- * don't check for ":endfunc". */
- if (STRCMP(theline, skip_until) == 0) {
- XFREE_CLEAR(skip_until);
+ // Don't check for ":endfunc" between
+ // * ":append" and "."
+ // * ":python <<EOF" and "EOF"
+ // * ":let {var-name} =<< [trim] {marker}" and "{marker}"
+ if (heredoc_trimmed == NULL
+ || (is_heredoc && skipwhite(theline) == theline)
+ || STRNCMP(theline, heredoc_trimmed,
+ STRLEN(heredoc_trimmed)) == 0) {
+ if (heredoc_trimmed == NULL) {
+ p = theline;
+ } else if (is_heredoc) {
+ p = skipwhite(theline) == theline
+ ? theline : theline + STRLEN(heredoc_trimmed);
+ } else {
+ p = theline + STRLEN(heredoc_trimmed);
+ }
+ if (STRCMP(p, skip_until) == 0) {
+ XFREE_CLEAR(skip_until);
+ XFREE_CLEAR(heredoc_trimmed);
+ do_concat = true;
+ is_heredoc = false;
+ }
}
} else {
/* skip ':' and blanks*/
@@ -21483,6 +21874,34 @@ void ex_function(exarg_T *eap)
else
skip_until = vim_strsave(p);
}
+
+ // Check for ":let v =<< [trim] EOF"
+ // and ":let [a, b] =<< [trim] EOF"
+ arg = skipwhite(skiptowhite(p));
+ if (*arg == '[') {
+ arg = vim_strchr(arg, ']');
+ }
+ if (arg != NULL) {
+ arg = skipwhite(skiptowhite(arg));
+ if (arg[0] == '='
+ && arg[1] == '<'
+ && arg[2] =='<'
+ && (p[0] == 'l'
+ && p[1] == 'e'
+ && (!ASCII_ISALNUM(p[2])
+ || (p[2] == 't' && !ASCII_ISALNUM(p[3]))))) {
+ p = skipwhite(arg + 3);
+ if (STRNCMP(p, "trim", 4) == 0) {
+ // Ignore leading white space.
+ p = skipwhite(p + 4);
+ heredoc_trimmed =
+ vim_strnsave(theline, (int)(skipwhite(theline) - theline));
+ }
+ skip_until = vim_strnsave(p, (int)(skiptowhite(p) - p));
+ do_concat = false;
+ is_heredoc = true;
+ }
+ }
}
/* Add the line to the function. */
@@ -21652,7 +22071,8 @@ void ex_function(exarg_T *eap)
fp->uf_flags = flags;
fp->uf_calls = 0;
fp->uf_script_ctx = current_sctx;
- fp->uf_script_ctx.sc_lnum += sourcing_lnum - newlines.ga_len - 1;
+ fp->uf_script_ctx.sc_lnum += sourcing_lnum_top;
+
goto ret_free;
erret:
@@ -21669,7 +22089,7 @@ ret_free:
if (show_block) {
ui_ext_cmdline_block_leave();
}
-}
+} // NOLINT(readability/fn_size)
/// Get a function name, translating "<SID>" and "<SNR>".
/// Also handles a Funcref in a List or Dictionary.
@@ -21753,8 +22173,19 @@ trans_function_name(
*pp = (char_u *)end;
} else if (lv.ll_tv->v_type == VAR_PARTIAL
&& lv.ll_tv->vval.v_partial != NULL) {
- name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial));
- *pp = (char_u *)end;
+ if (lv.ll_tv->vval.v_partial == vvlua_partial && *end == '.') {
+ len = check_luafunc_name((const char *)end+1, true);
+ if (len == 0) {
+ EMSG2(e_invexpr2, "v:lua");
+ goto theend;
+ }
+ name = xmallocz(len);
+ memcpy(name, end+1, len);
+ *pp = (char_u *)end+1+len;
+ } else {
+ name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial));
+ *pp = (char_u *)end;
+ }
if (partial != NULL) {
*partial = lv.ll_tv->vval.v_partial;
}
@@ -22172,7 +22603,7 @@ void func_dump_profile(FILE *fd)
.channel_id = 0,
};
char_u *p = get_scriptname(last_set, &should_free);
- fprintf(fd, " Defined: %s line %" PRIdLINENR "\n",
+ fprintf(fd, " Defined: %s:%" PRIdLINENR "\n",
p, fp->uf_script_ctx.sc_lnum);
if (should_free) {
xfree(p);
@@ -23305,7 +23736,7 @@ char_u *get_return_cmd(void *rettv)
* Called by do_cmdline() to get the next line.
* Returns allocated string, or NULL for end of function.
*/
-char_u *get_func_line(int c, void *cookie, int indent)
+char_u *get_func_line(int c, void *cookie, int indent, bool do_concat)
{
funccall_T *fcp = (funccall_T *)cookie;
ufunc_T *fp = fcp->func;
@@ -23831,22 +24262,47 @@ repeat:
* - for second :e: before the current fname
* - otherwise: The last '.'
*/
- if (src[*usedlen + 1] == 'e' && *fnamep > tail)
+ const bool is_second_e = *fnamep > tail;
+ if (src[*usedlen + 1] == 'e' && is_second_e) {
s = *fnamep - 2;
- else
+ } else {
s = *fnamep + *fnamelen - 1;
- for (; s > tail; --s)
- if (s[0] == '.')
+ }
+
+ for (; s > tail; s--) {
+ if (s[0] == '.') {
break;
- if (src[*usedlen + 1] == 'e') { /* :e */
- if (s > tail) {
- *fnamelen += (size_t)(*fnamep - (s + 1));
- *fnamep = s + 1;
- } else if (*fnamep <= tail)
+ }
+ }
+ if (src[*usedlen + 1] == 'e') {
+ if (s > tail || (0 && is_second_e && s == tail)) {
+ // we stopped at a '.' (so anchor to &'.' + 1)
+ char_u *newstart = s + 1;
+ size_t distance_stepped_back = *fnamep - newstart;
+ *fnamelen += distance_stepped_back;
+ *fnamep = newstart;
+ } else if (*fnamep <= tail) {
*fnamelen = 0;
- } else { /* :r */
- if (s > tail) /* remove one extension */
+ }
+ } else {
+ // :r - Remove one extension
+ //
+ // Ensure that `s` doesn't go before `*fnamep`,
+ // since then we're taking too many roots:
+ //
+ // "path/to/this.file.ext" :e:e:r:r
+ // ^ ^-------- *fnamep
+ // +------------- tail
+ //
+ // Also ensure `s` doesn't go before `tail`,
+ // since then we're taking too many roots again:
+ //
+ // "path/to/this.file.ext" :r:r:r
+ // ^ ^------------- tail
+ // +--------------------- *fnamep
+ if (s > MAX(tail, *fnamep)) {
*fnamelen = (size_t)(s - *fnamep);
+ }
}
*usedlen += 2;
}
@@ -23952,8 +24408,8 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub,
/* Skip empty match except for first match. */
if (regmatch.startp[0] == regmatch.endp[0]) {
if (zero_width == regmatch.startp[0]) {
- /* avoid getting stuck on a match with an empty string */
- int i = MB_PTR2LEN(tail);
+ // avoid getting stuck on a match with an empty string
+ int i = utfc_ptr2len(tail);
memmove((char_u *)ga.ga_data + ga.ga_len, tail, (size_t)i);
ga.ga_len += i;
tail += i;
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index e099de831a..2aa08e2074 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -117,6 +117,7 @@ typedef enum {
VV_TYPE_BOOL,
VV_ECHOSPACE,
VV_EXITING,
+ VV_LUA,
} VimVarIndex;
/// All recognized msgpack types
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index ab5ff57c2f..0ae250e626 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -28,8 +28,9 @@ return {
asin={args=1, func="float_op_wrapper", data="&asin"}, -- WJMc
assert_beeps={args={1, 2}},
assert_equal={args={2, 3}},
+ assert_equalfile={args=2},
assert_exception={args={1, 2}},
- assert_fails={args={1, 2}},
+ assert_fails={args={1, 3}},
assert_false={args={1, 2}},
assert_inrange={args={3, 4}},
assert_match={args={2, 3}},
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index 106b8f6eed..72ee45a03a 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -1200,6 +1200,7 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key,
typval_T rettv;
+ dict->dv_refcount++;
QUEUE *w;
QUEUE_FOREACH(w, &dict->watchers) {
DictWatcher *watcher = tv_dict_watcher_node_data(w);
@@ -1211,6 +1212,7 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key,
tv_clear(&rettv);
}
}
+ tv_dict_unref(dict);
for (size_t i = 1; i < ARRAY_SIZE(argv); i++) {
tv_clear(argv + i);
diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c
index 63efee59a8..37b9f73ba4 100644
--- a/src/nvim/event/libuv_process.c
+++ b/src/nvim/event/libuv_process.c
@@ -41,7 +41,7 @@ int libuv_process_spawn(LibuvProcess *uvproc)
#endif
uvproc->uvopts.exit_cb = exit_cb;
uvproc->uvopts.cwd = proc->cwd;
- uvproc->uvopts.env = NULL; // Inherits the parent (nvim) env.
+ uvproc->uvopts.env = proc->env;
uvproc->uvopts.stdio = uvproc->uvstdio;
uvproc->uvopts.stdio_count = 3;
uvproc->uvstdio[0].flags = UV_IGNORE;
diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c
index a01bbc9888..c2be472acd 100644
--- a/src/nvim/event/loop.c
+++ b/src/nvim/event/loop.c
@@ -162,10 +162,6 @@ size_t loop_size(Loop *loop)
return rv;
}
-void loop_dummy_event(void **argv)
-{
-}
-
static void async_cb(uv_async_t *handle)
{
Loop *l = handle->loop->data;
diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h
index ef9d953ab7..b677b80bfe 100644
--- a/src/nvim/event/process.h
+++ b/src/nvim/event/process.h
@@ -23,6 +23,7 @@ struct process {
uint64_t stopped_time; // process_stop() timestamp
const char *cwd;
char **argv;
+ char **env;
Stream in, out, err;
process_exit_cb cb;
internal_process_cb internal_exit_cb, internal_close_cb;
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 16487ce447..0c3b467612 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -39,6 +39,7 @@
#include "nvim/buffer_updates.h"
#include "nvim/main.h"
#include "nvim/mark.h"
+#include "nvim/mark_extended.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/message.h"
@@ -658,10 +659,10 @@ void ex_sort(exarg_T *eap)
deleted = (long)(count - (lnum - eap->line2));
if (deleted > 0) {
mark_adjust(eap->line2 - deleted, eap->line2, (long)MAXLNUM, -deleted,
- false);
+ false, kExtmarkUndo);
msgmore(-deleted);
} else if (deleted < 0) {
- mark_adjust(eap->line2, MAXLNUM, -deleted, 0L, false);
+ mark_adjust(eap->line2, MAXLNUM, -deleted, 0L, false, kExtmarkUndo);
}
if (change_occurred || deleted != 0) {
changed_lines(eap->line1, 0, eap->line2 + 1, -deleted, true);
@@ -874,10 +875,12 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
* their final destination at the new text position -- webb
*/
last_line = curbuf->b_ml.ml_line_count;
- mark_adjust_nofold(line1, line2, last_line - line2, 0L, true);
+ mark_adjust_nofold(line1, line2, last_line - line2, 0L, true, kExtmarkNoUndo);
+ extmark_adjust(curbuf, line1, line2, last_line - line2, 0L, kExtmarkNoUndo,
+ true);
changed_lines(last_line - num_lines + 1, 0, last_line + 1, num_lines, false);
if (dest >= line2) {
- mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, false);
+ mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, false, kExtmarkNoUndo);
FOR_ALL_TAB_WINDOWS(tab, win) {
if (win->w_buffer == curbuf) {
foldMoveRange(&win->w_folds, line1, line2, dest);
@@ -886,7 +889,8 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
curbuf->b_op_start.lnum = dest - num_lines + 1;
curbuf->b_op_end.lnum = dest;
} else {
- mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, false);
+ mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, false,
+ kExtmarkNoUndo);
FOR_ALL_TAB_WINDOWS(tab, win) {
if (win->w_buffer == curbuf) {
foldMoveRange(&win->w_folds, dest + 1, line1 - 1, line2);
@@ -897,7 +901,9 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
}
curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
mark_adjust_nofold(last_line - num_lines + 1, last_line,
- -(last_line - dest - extra), 0L, true);
+ -(last_line - dest - extra), 0L, true, kExtmarkNoUndo);
+
+ u_extmark_move(curbuf, line1, line2, last_line, dest, num_lines, extra);
changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra, false);
// send update regarding the new lines that were added
@@ -1281,12 +1287,14 @@ static void do_filter(
if (cmdmod.keepmarks || vim_strchr(p_cpo, CPO_REMMARK) == NULL) {
if (read_linecount >= linecount) {
// move all marks from old lines to new lines
- mark_adjust(line1, line2, linecount, 0L, false);
+ mark_adjust(line1, line2, linecount, 0L, false, kExtmarkUndo);
} else {
// move marks from old lines to new lines, delete marks
// that are in deleted lines
- mark_adjust(line1, line1 + read_linecount - 1, linecount, 0L, false);
- mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0L, false);
+ mark_adjust(line1, line1 + read_linecount - 1, linecount, 0L, false,
+ kExtmarkUndo);
+ mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0L, false,
+ kExtmarkUndo);
}
}
@@ -1579,7 +1587,7 @@ int rename_buffer(char_u *new_fname)
xfname = curbuf->b_fname;
curbuf->b_ffname = NULL;
curbuf->b_sfname = NULL;
- if (setfname(curbuf, new_fname, NULL, TRUE) == FAIL) {
+ if (setfname(curbuf, new_fname, NULL, true) == FAIL) {
curbuf->b_ffname = fname;
curbuf->b_sfname = sfname;
return FAIL;
@@ -2767,7 +2775,7 @@ void ex_append(exarg_T *eap)
State = CMDLINE;
theline = eap->getline(
eap->cstack->cs_looplevel > 0 ? -1 :
- NUL, eap->cookie, indent);
+ NUL, eap->cookie, indent, true);
State = save_State;
}
lines_left = Rows - 1;
@@ -3214,6 +3222,189 @@ static char_u *sub_parse_flags(char_u *cmd, subflags_T *subflags,
return cmd;
}
+static void extmark_move_regmatch_single(lpos_T startpos,
+ lpos_T endpos,
+ linenr_T lnum,
+ int sublen)
+{
+ colnr_T mincol;
+ colnr_T endcol;
+ colnr_T col_amount;
+
+ mincol = startpos.col + 1;
+ endcol = endpos.col + 1;
+
+ // There are cases such as :s/^/x/ where this happens
+ // a delete is simply not required.
+ if (mincol + 1 <= endcol) {
+ extmark_col_adjust_delete(curbuf,
+ lnum, mincol + 1, endcol, kExtmarkUndo, 0);
+ }
+
+ // Insert, sublen seems to be the value we need but + 1...
+ col_amount = sublen - 1;
+ extmark_col_adjust(curbuf, lnum, mincol, 0, col_amount, kExtmarkUndo);
+}
+
+static void extmark_move_regmatch_multi(ExtmarkSubMulti s, long i)
+{
+ colnr_T mincol;
+ linenr_T u_lnum;
+ mincol = s.startpos.col + 1;
+
+ linenr_T n_u_lnum = s.lnum + s.endpos.lnum - s.startpos.lnum;
+ colnr_T n_after_newline_in_pat = s.endpos.col;
+ colnr_T n_before_newline_in_pat = mincol - s.cm_start.col;
+ long n_after_newline_in_sub;
+ if (!s.newline_in_sub) {
+ n_after_newline_in_sub = s.cm_end.col - s.cm_start.col;
+ } else {
+ n_after_newline_in_sub = s.cm_end.col;
+ }
+
+ if (s.newline_in_pat && !s.newline_in_sub) {
+ // -- Delete Pattern --
+ // 1. Move marks in the pattern
+ mincol = s.startpos.col + 1;
+ u_lnum = n_u_lnum;
+ assert(n_u_lnum == u_lnum);
+ extmark_copy_and_place(curbuf,
+ s.lnum, mincol,
+ u_lnum, n_after_newline_in_pat,
+ s.lnum, mincol,
+ kExtmarkUndo, true, NULL);
+ // 2. Move marks on last newline
+ mincol = mincol - n_before_newline_in_pat;
+ extmark_col_adjust(curbuf,
+ u_lnum,
+ n_after_newline_in_pat + 1,
+ -s.newline_in_pat,
+ mincol - n_after_newline_in_pat,
+ kExtmarkUndo);
+ // Take care of the lines after
+ extmark_adjust(curbuf,
+ u_lnum,
+ u_lnum,
+ MAXLNUM,
+ -s.newline_in_pat,
+ kExtmarkUndo,
+ false);
+ // 1. first insert the text in the substitutaion
+ extmark_col_adjust(curbuf,
+ s.lnum,
+ mincol + 1,
+ s.newline_in_sub,
+ n_after_newline_in_sub,
+ kExtmarkUndo);
+
+ } else {
+ // The data in sub_obj is as if the substituons above had already taken
+ // place. For our extmarks they haven't as we work from the bottom of the
+ // buffer up. Readjust the data.
+ n_u_lnum = s.lnum + s.endpos.lnum - s.startpos.lnum;
+ n_u_lnum = n_u_lnum - s.lnum_added;
+
+ // adjusted = L - (i-1)N
+ // where L = lnum value, N= lnum_added and i = iteration
+ linenr_T a_l_lnum = s.cm_start.lnum - ((i -1) * s.lnum_added);
+ linenr_T a_u_lnum = a_l_lnum + s.endpos.lnum;
+ assert(s.startpos.lnum == 0);
+
+ mincol = s.startpos.col + 1;
+ u_lnum = n_u_lnum;
+
+ if (!s.newline_in_pat && s.newline_in_sub) {
+ // -- Delete Pattern --
+ // 1. Move marks in the pattern
+ extmark_col_adjust_delete(curbuf,
+ a_l_lnum,
+ mincol + 1,
+ s.endpos.col + 1,
+ kExtmarkUndo,
+ s.eol);
+
+ extmark_adjust(curbuf,
+ a_u_lnum + 1,
+ MAXLNUM,
+ (long)s.newline_in_sub,
+ 0,
+ kExtmarkUndo,
+ false);
+ // 3. Insert
+ extmark_col_adjust(curbuf,
+ a_l_lnum,
+ mincol,
+ s.newline_in_sub,
+ (long)-mincol + 1 + n_after_newline_in_sub,
+ kExtmarkUndo);
+ } else if (s.newline_in_pat && s.newline_in_sub) {
+ if (s.lnum_added >= 0) {
+ linenr_T u_col = n_after_newline_in_pat == 0
+ ? 1 : n_after_newline_in_pat;
+ extmark_copy_and_place(curbuf,
+ a_l_lnum, mincol,
+ a_u_lnum, u_col,
+ a_l_lnum, mincol,
+ kExtmarkUndo, true, NULL);
+ // 2. Move marks on last newline
+ mincol = mincol - (colnr_T)n_before_newline_in_pat;
+ extmark_col_adjust(curbuf,
+ a_u_lnum,
+ (colnr_T)(n_after_newline_in_pat + 1),
+ -s.newline_in_pat,
+ mincol - n_after_newline_in_pat,
+ kExtmarkUndo);
+ // TODO(timeyyy): nothing to do here if lnum_added = 0
+ extmark_adjust(curbuf,
+ a_u_lnum + 1,
+ MAXLNUM,
+ (long)s.lnum_added,
+ 0,
+ kExtmarkUndo,
+ false);
+
+ extmark_col_adjust(curbuf,
+ a_l_lnum,
+ mincol + 1,
+ s.newline_in_sub,
+ (long)-mincol + n_after_newline_in_sub,
+ kExtmarkUndo);
+ } else {
+ mincol = s.startpos.col + 1;
+ a_l_lnum = s.startpos.lnum + 1;
+ a_u_lnum = s.endpos.lnum + 1;
+ extmark_copy_and_place(curbuf,
+ a_l_lnum, mincol,
+ a_u_lnum, n_after_newline_in_pat,
+ a_l_lnum, mincol,
+ kExtmarkUndo, true, NULL);
+ // 2. Move marks on last newline
+ mincol = mincol - (colnr_T)n_before_newline_in_pat;
+ extmark_col_adjust(curbuf,
+ a_u_lnum,
+ (colnr_T)(n_after_newline_in_pat + 1),
+ -s.newline_in_pat,
+ mincol - n_after_newline_in_pat,
+ kExtmarkUndo);
+ extmark_adjust(curbuf,
+ a_u_lnum,
+ a_u_lnum,
+ MAXLNUM,
+ s.lnum_added,
+ kExtmarkUndo,
+ false);
+ // 3. Insert
+ extmark_col_adjust(curbuf,
+ a_l_lnum,
+ mincol + 1,
+ s.newline_in_sub,
+ (long)-mincol + n_after_newline_in_sub,
+ kExtmarkUndo);
+ }
+ }
+ }
+}
+
/// Perform a substitution from line eap->line1 to line eap->line2 using the
/// command pointed to by eap->arg which should be of the form:
///
@@ -3260,6 +3451,17 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
int save_ma = 0;
int save_b_changed = curbuf->b_changed;
bool preview = (State & CMDPREVIEW);
+ extmark_sub_multi_vec_t extmark_sub_multi = KV_INITIAL_VALUE;
+ extmark_sub_single_vec_t extmark_sub_single = KV_INITIAL_VALUE;
+ linenr_T no_of_lines_changed = 0;
+ linenr_T newline_in_pat = 0;
+ linenr_T newline_in_sub = 0;
+
+ // inccommand tests fail without this check
+ if (!preview) {
+ // Required for Undo to work for extmarks.
+ u_save_cursor();
+ }
if (!global_busy) {
sub_nsubs = 0;
@@ -3418,6 +3620,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
// Check for a match on each line.
// If preview: limit to max('cmdwinheight', viewport).
linenr_T line2 = eap->line2;
+
for (linenr_T lnum = eap->line1;
lnum <= line2 && !got_quit && !aborting()
&& (!preview || preview_lines.lines_needed <= (linenr_T)p_cwh
@@ -3524,6 +3727,11 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
// Note: If not first match on a line, column can't be known here
current_match.start.lnum = sub_firstlnum;
+ // Match might be after the last line for "\n\zs" matching at
+ // the end of the last line.
+ if (lnum > curbuf->b_ml.ml_line_count) {
+ break;
+ }
if (sub_firstline == NULL) {
sub_firstline = vim_strsave(ml_get(sub_firstlnum));
}
@@ -3630,7 +3838,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
for (; i <= (long)ec; ++i)
msg_putchar('^');
- resp = getexmodeline('?', NULL, 0);
+ resp = getexmodeline('?', NULL, 0, true);
if (resp != NULL) {
typed = *resp;
xfree(resp);
@@ -3871,6 +4079,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
ADJUST_SUB_FIRSTLNUM();
+
// Now the trick is to replace CTRL-M chars with a real line
// break. This would make it impossible to insert a CTRL-M in
// the text. The line break can be avoided by preceding the
@@ -3885,7 +4094,9 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
*p1 = NUL; // truncate up to the CR
ml_append(lnum - 1, new_start,
(colnr_T)(p1 - new_start + 1), false);
- mark_adjust(lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false);
+ mark_adjust(lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false,
+ kExtmarkNOOP);
+
if (subflags.do_ask) {
appended_lines(lnum - 1, 1L);
} else {
@@ -3912,6 +4123,44 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
current_match.end.lnum = lnum;
}
+ // Adjust extmarks, by delete and then insert
+ if (!preview) {
+ newline_in_pat = (regmatch.endpos[0].lnum
+ - regmatch.startpos[0].lnum);
+ newline_in_sub = current_match.end.lnum - current_match.start.lnum;
+ if (newline_in_pat || newline_in_sub) {
+ ExtmarkSubMulti sub_multi;
+ no_of_lines_changed = newline_in_sub - newline_in_pat;
+
+ sub_multi.newline_in_pat = newline_in_pat;
+ sub_multi.newline_in_sub = newline_in_sub;
+ sub_multi.lnum = lnum;
+ sub_multi.lnum_added = no_of_lines_changed;
+ sub_multi.cm_start = current_match.start;
+ sub_multi.cm_end = current_match.end;
+
+ sub_multi.startpos = regmatch.startpos[0];
+ sub_multi.endpos = regmatch.endpos[0];
+ sub_multi.eol = extmark_eol_col(curbuf, lnum);
+
+ kv_push(extmark_sub_multi, sub_multi);
+ // Collect information required for moving extmarks WITHOUT \n, \r
+ } else {
+ no_of_lines_changed = 0;
+
+ if (regmatch.startpos[0].col != -1) {
+ ExtmarkSubSingle sub_single;
+ sub_single.sublen = sublen;
+ sub_single.lnum = lnum;
+ sub_single.startpos = regmatch.startpos[0];
+ sub_single.endpos = regmatch.endpos[0];
+
+ kv_push(extmark_sub_single, sub_single);
+ }
+ }
+ }
+
+
// 4. If subflags.do_all is set, find next match.
// Prevent endless loop with patterns that match empty
// strings, e.g. :s/$/pat/g or :s/[a-z]* /(&)/g.
@@ -3978,7 +4227,7 @@ skip:
ml_delete(lnum, false);
}
mark_adjust(lnum, lnum + nmatch_tl - 1,
- (long)MAXLNUM, -nmatch_tl, false);
+ (long)MAXLNUM, -nmatch_tl, false, kExtmarkNOOP);
if (subflags.do_ask) {
deleted_lines(lnum, nmatch_tl);
}
@@ -4154,6 +4403,35 @@ skip:
}
}
}
+ if (newline_in_pat || newline_in_sub) {
+ long n = (long)kv_size(extmark_sub_multi);
+ ExtmarkSubMulti sub_multi;
+ if (no_of_lines_changed < 0) {
+ for (i = 0; i < n; i++) {
+ sub_multi = kv_A(extmark_sub_multi, i);
+ extmark_move_regmatch_multi(sub_multi, i);
+ }
+ } else {
+ // Move extmarks in reverse order to avoid moving marks we just moved...
+ for (i = 0; i < n; i++) {
+ sub_multi = kv_Z(extmark_sub_multi, i);
+ extmark_move_regmatch_multi(sub_multi, n - i);
+ }
+ }
+ kv_destroy(extmark_sub_multi);
+ } else {
+ long n = (long)kv_size(extmark_sub_single);
+ ExtmarkSubSingle sub_single;
+ for (i = 0; i < n; i++) {
+ sub_single = kv_Z(extmark_sub_single, i);
+ extmark_move_regmatch_single(sub_single.startpos,
+ sub_single.endpos,
+ sub_single.lnum,
+ sub_single.sublen);
+ }
+
+ kv_destroy(extmark_sub_single);
+ }
kv_destroy(preview_lines.subresults);
@@ -4739,11 +5017,19 @@ int find_help_tags(const char_u *arg, int *num_matches, char_u ***matches,
if (STRNICMP(arg, "expr-", 5) == 0) {
// When the string starting with "expr-" and containing '?' and matches
- // the table, it is taken literally. Otherwise '?' is recognized as a
- // wildcard.
+ // the table, it is taken literally (but ~ is escaped). Otherwise '?'
+ // is recognized as a wildcard.
for (i = (int)ARRAY_SIZE(expr_table); --i >= 0; ) {
if (STRCMP(arg + 5, expr_table[i]) == 0) {
- STRCPY(d, arg);
+ for (int si = 0, di = 0; ; si++) {
+ if (arg[si] == '~') {
+ d[di++] = '\\';
+ }
+ d[di++] = arg[si];
+ if (arg[si] == NUL) {
+ break;
+ }
+ }
break;
}
}
@@ -4897,7 +5183,7 @@ int find_help_tags(const char_u *arg, int *num_matches, char_u ***matches,
*matches = NULL;
*num_matches = 0;
- int flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE;
+ int flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE | TAG_NO_TAGFUNC;
if (keep_lang) {
flags |= TAG_KEEP_LANG;
}
@@ -5023,7 +5309,7 @@ void fix_help_buffer(void)
copy_option_part(&p, NameBuff, MAXPATHL, ",");
char_u *const rt = (char_u *)vim_getenv("VIMRUNTIME");
if (rt != NULL
- && path_full_compare(rt, NameBuff, false) != kEqualFiles) {
+ && path_full_compare(rt, NameBuff, false, true) != kEqualFiles) {
int fcount;
char_u **fnames;
char_u *s;
@@ -5233,7 +5519,7 @@ static void helptags_one(char_u *const dir, const char_u *const ext,
ga_init(&ga, (int)sizeof(char_u *), 100);
if (add_help_tags
|| path_full_compare((char_u *)"$VIMRUNTIME/doc",
- dir, false) == kEqualFiles) {
+ dir, false, true) == kEqualFiles) {
s = xmalloc(18 + STRLEN(tagfname));
sprintf((char *)s, "help-tags\t%s\t1\n", tagfname);
GA_APPEND(char_u *, &ga, s);
@@ -5527,6 +5813,7 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr,
// We keep a special-purpose buffer around, but don't assume it exists.
buf_T *preview_buf = bufnr ? buflist_findnr(bufnr) : 0;
+ cmdmod.split = 0; // disable :leftabove/botright modifiers
cmdmod.tab = 0; // disable :tab modifier
cmdmod.noswapfile = true; // disable swap for preview buffer
// disable file info message
@@ -5573,6 +5860,9 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr,
highest_num_line = kv_last(lines.subresults).end.lnum;
col_width = log10(highest_num_line) + 1 + 3;
}
+ } else {
+ // Failed to split the window, don't show 'inccommand' preview.
+ preview_buf = NULL;
}
char *str = NULL; // construct the line to show in here
@@ -5585,7 +5875,7 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr,
for (size_t matchidx = 0; matchidx < lines.subresults.size; matchidx++) {
SubResult match = lines.subresults.items[matchidx];
- if (split && preview_buf) {
+ if (preview_buf) {
lpos_T p_start = { 0, match.start.col }; // match starts here in preview
lpos_T p_end = { 0, match.end.col }; // ... and ends here
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index 8c0d22809f..f7aa8a994a 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -323,6 +323,12 @@ return {
func='ex_abclear',
},
{
+ command='cabove',
+ flags=bit.bor(RANGE, TRLBAR),
+ addr_type=ADDR_OTHER ,
+ func='ex_cbelow',
+ },
+ {
command='caddbuffer',
flags=bit.bor(RANGE, NOTADR, WORD1, TRLBAR),
addr_type=ADDR_LINES,
@@ -359,6 +365,12 @@ return {
func='ex_cbuffer',
},
{
+ command='cbelow',
+ flags=bit.bor(RANGE, TRLBAR),
+ addr_type=ADDR_OTHER ,
+ func='ex_cbelow',
+ },
+ {
command='cbottom',
flags=bit.bor(TRLBAR),
addr_type=ADDR_LINES,
@@ -758,7 +770,7 @@ return {
},
{
command='digraphs',
- flags=bit.bor(EXTRA, TRLBAR, CMDWIN),
+ flags=bit.bor(BANG, EXTRA, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
func='ex_digraphs',
},
@@ -1273,6 +1285,12 @@ return {
func='ex_last',
},
{
+ command='labove',
+ flags=bit.bor(RANGE, TRLBAR),
+ addr_type=ADDR_OTHER ,
+ func='ex_cbelow',
+ },
+ {
command='language',
flags=bit.bor(EXTRA, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
@@ -1309,6 +1327,12 @@ return {
func='ex_cbuffer',
},
{
+ command='lbelow',
+ flags=bit.bor(RANGE, TRLBAR),
+ addr_type=ADDR_OTHER ,
+ func='ex_cbelow',
+ },
+ {
command='lbottom',
flags=bit.bor(TRLBAR),
addr_type=ADDR_LINES,
@@ -1632,7 +1656,7 @@ return {
command='marks',
flags=bit.bor(EXTRA, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
- func='do_marks',
+ func='ex_marks',
},
{
command='match',
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index 813d1a9b0b..496aecfb27 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -97,10 +97,11 @@ typedef struct sn_prl_S {
struct source_cookie {
FILE *fp; ///< opened file for sourcing
char_u *nextline; ///< if not NULL: line that was read ahead
+ linenr_T sourcing_lnum; ///< line number of the source file
int finished; ///< ":finish" used
#if defined(USE_CRNL)
int fileformat; ///< EOL_UNKNOWN, EOL_UNIX or EOL_DOS
- bool error; ///< true if LF found after CR-LF
+ bool error; ///< true if LF found after CR-LF
#endif
linenr_T breakpoint; ///< next line with breakpoint or zero
char_u *fname; ///< name of sourced file
@@ -1743,7 +1744,7 @@ static bool editing_arg_idx(win_T *win)
&& (win->w_buffer->b_ffname == NULL
|| !(path_full_compare(
alist_name(&WARGLIST(win)[win->w_arg_idx]),
- win->w_buffer->b_ffname, true) & kEqualFiles))));
+ win->w_buffer->b_ffname, true, true) & kEqualFiles))));
}
/// Check if window "win" is editing the w_arg_idx file in its argument list.
@@ -1761,7 +1762,7 @@ void check_arg_idx(win_T *win)
&& (win->w_buffer->b_fnum == GARGLIST[GARGCOUNT - 1].ae_fnum
|| (win->w_buffer->b_ffname != NULL
&& (path_full_compare(alist_name(&GARGLIST[GARGCOUNT - 1]),
- win->w_buffer->b_ffname, true)
+ win->w_buffer->b_ffname, true, true)
& kEqualFiles)))) {
arg_had_last = true;
}
@@ -3014,11 +3015,78 @@ static FILE *fopen_noinh_readbin(char *filename)
return fdopen(fd_tmp, READBIN);
}
+typedef struct {
+ char_u *buf;
+ size_t offset;
+} GetStrLineCookie;
-/// Read the file "fname" and execute its lines as EX commands.
+/// Get one full line from a sourced string (in-memory, no file).
+/// Called by do_cmdline() when it's called from do_source_str().
+///
+/// @return pointer to allocated line, or NULL for end-of-file or
+/// some error.
+static char_u *get_str_line(int c, void *cookie, int indent, bool do_concat)
+{
+ GetStrLineCookie *p = cookie;
+ size_t i = p->offset;
+ if (strlen((char *)p->buf) <= p->offset) {
+ return NULL;
+ }
+ while (!(p->buf[i] == '\n' || p->buf[i] == '\0')) {
+ i++;
+ }
+ char buf[2046];
+ char *dst;
+ dst = xstpncpy(buf, (char *)p->buf + p->offset, i - p->offset);
+ if ((uint32_t)(dst - buf) != i - p->offset) {
+ smsg(_(":source error parsing command %s"), p->buf);
+ return NULL;
+ }
+ buf[i - p->offset] = '\0';
+ p->offset = i + 1;
+ return (char_u *)xstrdup(buf);
+}
+
+/// Executes lines in `src` as Ex commands.
+///
+/// @see do_source()
+int do_source_str(const char *cmd, const char *traceback_name)
+{
+ char_u *save_sourcing_name = sourcing_name;
+ linenr_T save_sourcing_lnum = sourcing_lnum;
+ char_u sourcing_name_buf[256];
+ if (save_sourcing_name == NULL) {
+ sourcing_name = (char_u *)traceback_name;
+ } else {
+ snprintf((char *)sourcing_name_buf, sizeof(sourcing_name_buf),
+ "%s called at %s:%"PRIdLINENR, traceback_name, save_sourcing_name,
+ save_sourcing_lnum);
+ sourcing_name = sourcing_name_buf;
+ }
+ sourcing_lnum = 0;
+
+ GetStrLineCookie cookie = {
+ .buf = (char_u *)cmd,
+ .offset = 0,
+ };
+ const sctx_T save_current_sctx = current_sctx;
+ current_sctx.sc_sid = SID_STR;
+ current_sctx.sc_seq = 0;
+ current_sctx.sc_lnum = save_sourcing_lnum;
+ int retval = do_cmdline(NULL, get_str_line, (void *)&cookie,
+ DOCMD_VERBOSE | DOCMD_NOWAIT | DOCMD_REPEAT);
+ current_sctx = save_current_sctx;
+ sourcing_lnum = save_sourcing_lnum;
+ sourcing_name = save_sourcing_name;
+ return retval;
+}
+
+/// Reads the file `fname` and executes its lines as Ex commands.
///
/// This function may be called recursively!
///
+/// @see do_source_str
+///
/// @param fname
/// @param check_other check for .vimrc and _vimrc
/// @param is_vimrc DOSO_ value
@@ -3124,6 +3192,7 @@ int do_source(char_u *fname, int check_other, int is_vimrc)
#endif
cookie.nextline = NULL;
+ cookie.sourcing_lnum = 0;
cookie.finished = false;
// Check if this script has a breakpoint.
@@ -3218,7 +3287,7 @@ int do_source(char_u *fname, int check_other, int is_vimrc)
cookie.conv.vc_type = CONV_NONE; // no conversion
// Read the first line so we can check for a UTF-8 BOM.
- firstline = getsourceline(0, (void *)&cookie, 0);
+ firstline = getsourceline(0, (void *)&cookie, 0, true);
if (firstline != NULL && STRLEN(firstline) >= 3 && firstline[0] == 0xef
&& firstline[1] == 0xbb && firstline[2] == 0xbf) {
// Found BOM; setup conversion, skip over BOM and recode the line.
@@ -3358,6 +3427,8 @@ char_u *get_scriptname(LastSet last_set, bool *should_free)
_("API client (channel id %" PRIu64 ")"),
last_set.channel_id);
return IObuff;
+ case SID_STR:
+ return (char_u *)_("anonymous :source");
default:
*should_free = true;
return home_replace_save(NULL,
@@ -3375,13 +3446,20 @@ void free_scriptnames(void)
}
# endif
+linenr_T get_sourced_lnum(LineGetter fgetline, void *cookie)
+{
+ return fgetline == getsourceline
+ ? ((struct source_cookie *)cookie)->sourcing_lnum
+ : sourcing_lnum;
+}
+
/// Get one full line from a sourced file.
/// Called by do_cmdline() when it's called from do_source().
///
/// @return pointer to the line in allocated memory, or NULL for end-of-file or
/// some error.
-char_u *getsourceline(int c, void *cookie, int indent)
+char_u *getsourceline(int c, void *cookie, int indent, bool do_concat)
{
struct source_cookie *sp = (struct source_cookie *)cookie;
char_u *line;
@@ -3395,6 +3473,8 @@ char_u *getsourceline(int c, void *cookie, int indent)
if (do_profiling == PROF_YES) {
script_line_end();
}
+ // Set the current sourcing line number.
+ sourcing_lnum = sp->sourcing_lnum + 1;
// Get current line. If there is a read-ahead line, use it, otherwise get
// one now.
if (sp->finished) {
@@ -3404,7 +3484,7 @@ char_u *getsourceline(int c, void *cookie, int indent)
} else {
line = sp->nextline;
sp->nextline = NULL;
- sourcing_lnum++;
+ sp->sourcing_lnum++;
}
if (line != NULL && do_profiling == PROF_YES) {
script_line_start();
@@ -3412,9 +3492,9 @@ char_u *getsourceline(int c, void *cookie, int indent)
// Only concatenate lines starting with a \ when 'cpoptions' doesn't
// contain the 'C' flag.
- if (line != NULL && (vim_strchr(p_cpo, CPO_CONCAT) == NULL)) {
+ if (line != NULL && do_concat && (vim_strchr(p_cpo, CPO_CONCAT) == NULL)) {
// compensate for the one line read-ahead
- sourcing_lnum--;
+ sp->sourcing_lnum--;
// Get the next line and concatenate it when it starts with a
// backslash. We always need to read the next line, keep it in
@@ -3492,7 +3572,7 @@ static char_u *get_one_sourceline(struct source_cookie *sp)
ga_init(&ga, 1, 250);
// Loop until there is a finished line (or end-of-file).
- sourcing_lnum++;
+ sp->sourcing_lnum++;
for (;; ) {
// make room to read at least 120 (more) characters
ga_grow(&ga, 120);
@@ -3559,7 +3639,7 @@ retry:
// len&c parities (is faster than ((len-c)%2 == 0)) -- Acevedo
for (c = len - 2; c >= 0 && buf[c] == Ctrl_V; c--) {}
if ((len & 1) != (c & 1)) { // escaped NL, read more
- sourcing_lnum++;
+ sp->sourcing_lnum++;
continue;
}
diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h
index 42ba1060e9..3a9fd01dd9 100644
--- a/src/nvim/ex_cmds_defs.h
+++ b/src/nvim/ex_cmds_defs.h
@@ -88,7 +88,7 @@ typedef struct exarg exarg_T;
typedef void (*ex_func_T)(exarg_T *eap);
-typedef char_u *(*LineGetter)(int, void *, int);
+typedef char_u *(*LineGetter)(int, void *, int, bool);
/// Structure for command definition.
typedef struct cmdname {
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 72d39adb3e..30c1373445 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -77,7 +77,7 @@
#include "nvim/api/private/helpers.h"
static int quitmore = 0;
-static int ex_pressedreturn = FALSE;
+static bool ex_pressedreturn = false;
/// Whether ":lcd" or ":tcd" was produced for a session.
static int did_lcd;
@@ -117,11 +117,11 @@ typedef struct {
* reads more lines that may come from the while/for loop.
*/
struct loop_cookie {
- garray_T *lines_gap; /* growarray with line info */
- int current_line; /* last read line from growarray */
- int repeating; /* TRUE when looping a second time */
- /* When "repeating" is FALSE use "getline" and "cookie" to get lines */
- char_u *(*getline)(int, void *, int);
+ garray_T *lines_gap; // growarray with line info
+ int current_line; // last read line from growarray
+ int repeating; // TRUE when looping a second time
+ // When "repeating" is FALSE use "getline" and "cookie" to get lines
+ char_u *(*getline)(int, void *, int, bool);
void *cookie;
};
@@ -140,6 +140,31 @@ struct dbg_stuff {
except_T *current_exception;
};
+typedef struct {
+ // parsed results
+ exarg_T *eap;
+ char_u *parsed_upto; // local we've parsed up to so far
+ char_u *cmd; // start of command
+ char_u *after_modifier;
+
+ // errors
+ char_u *errormsg;
+
+ // globals that need to be updated
+ cmdmod_T cmdmod;
+ int sandbox;
+ int msg_silent;
+ int emsg_silent;
+ bool ex_pressedreturn;
+ long p_verbose;
+
+ // other side-effects
+ bool set_eventignore;
+ long verbose_save;
+ int save_msg_silent;
+ int did_esilent;
+ bool did_sandbox;
+} parse_state_T;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_docmd.c.generated.h"
@@ -271,25 +296,23 @@ int do_cmdline_cmd(const char *cmd)
DOCMD_NOWAIT|DOCMD_KEYTYPED);
}
-/*
- * do_cmdline(): execute one Ex command line
- *
- * 1. Execute "cmdline" when it is not NULL.
- * If "cmdline" is NULL, or more lines are needed, fgetline() is used.
- * 2. Split up in parts separated with '|'.
- *
- * This function can be called recursively!
- *
- * flags:
- * DOCMD_VERBOSE - The command will be included in the error message.
- * DOCMD_NOWAIT - Don't call wait_return() and friends.
- * DOCMD_REPEAT - Repeat execution until fgetline() returns NULL.
- * DOCMD_KEYTYPED - Don't reset KeyTyped.
- * DOCMD_EXCRESET - Reset the exception environment (used for debugging).
- * DOCMD_KEEPLINE - Store first typed line (for repeating with ".").
- *
- * return FAIL if cmdline could not be executed, OK otherwise
- */
+/// do_cmdline(): execute one Ex command line
+///
+/// 1. Execute "cmdline" when it is not NULL.
+/// If "cmdline" is NULL, or more lines are needed, fgetline() is used.
+/// 2. Split up in parts separated with '|'.
+///
+/// This function can be called recursively!
+///
+/// flags:
+/// DOCMD_VERBOSE - The command will be included in the error message.
+/// DOCMD_NOWAIT - Don't call wait_return() and friends.
+/// DOCMD_REPEAT - Repeat execution until fgetline() returns NULL.
+/// DOCMD_KEYTYPED - Don't reset KeyTyped.
+/// DOCMD_EXCRESET - Reset the exception environment (used for debugging).
+/// DOCMD_KEEPLINE - Store first typed line (for repeating with ".").
+///
+/// @return FAIL if cmdline could not be executed, OK otherwise
int do_cmdline(char_u *cmdline, LineGetter fgetline,
void *cookie, /* argument for fgetline() */
int flags)
@@ -313,8 +336,8 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
struct msglist **saved_msg_list = NULL;
struct msglist *private_msg_list;
- /* "fgetline" and "cookie" passed to do_one_cmd() */
- char_u *(*cmd_getline)(int, void *, int);
+ // "fgetline" and "cookie" passed to do_one_cmd()
+ char_u *(*cmd_getline)(int, void *, int, bool);
void *cmd_cookie;
struct loop_cookie cmd_loop_cookie;
void *real_cookie;
@@ -396,13 +419,12 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
// If force_abort is set, we cancel everything.
did_emsg = false;
- /*
- * KeyTyped is only set when calling vgetc(). Reset it here when not
- * calling vgetc() (sourced command lines).
- */
+ // KeyTyped is only set when calling vgetc(). Reset it here when not
+ // calling vgetc() (sourced command lines).
if (!(flags & DOCMD_KEYTYPED)
- && !getline_equal(fgetline, cookie, getexline))
+ && !getline_equal(fgetline, cookie, getexline)) {
KeyTyped = false;
+ }
/*
* Continue executing command lines:
@@ -507,17 +529,20 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
* Need to set msg_didout for the first line after an ":if",
* otherwise the ":if" will be overwritten.
*/
- if (count == 1 && getline_equal(fgetline, cookie, getexline))
- msg_didout = TRUE;
- if (fgetline == NULL || (next_cmdline = fgetline(':', cookie,
- cstack.cs_idx <
- 0 ? 0 : (cstack.cs_idx + 1) * 2
- )) == NULL) {
- /* Don't call wait_return for aborted command line. The NULL
- * returned for the end of a sourced file or executed function
- * doesn't do this. */
- if (KeyTyped && !(flags & DOCMD_REPEAT))
- need_wait_return = FALSE;
+ if (count == 1 && getline_equal(fgetline, cookie, getexline)) {
+ msg_didout = true;
+ }
+ if (fgetline == NULL
+ || (next_cmdline = fgetline(':', cookie,
+ cstack.cs_idx <
+ 0 ? 0 : (cstack.cs_idx + 1) * 2,
+ true)) == NULL) {
+ // Don't call wait_return for aborted command line. The NULL
+ // returned for the end of a sourced file or executed function
+ // doesn't do this.
+ if (KeyTyped && !(flags & DOCMD_REPEAT)) {
+ need_wait_return = false;
+ }
retval = FAIL;
break;
}
@@ -951,7 +976,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
/*
* Obtain a line when inside a ":while" or ":for" loop.
*/
-static char_u *get_loop_line(int c, void *cookie, int indent)
+static char_u *get_loop_line(int c, void *cookie, int indent, bool do_concat)
{
struct loop_cookie *cp = (struct loop_cookie *)cookie;
wcmd_T *wp;
@@ -961,11 +986,12 @@ static char_u *get_loop_line(int c, void *cookie, int indent)
if (cp->repeating)
return NULL; /* trying to read past ":endwhile"/":endfor" */
- /* First time inside the ":while"/":for": get line normally. */
- if (cp->getline == NULL)
- line = getcmdline(c, 0L, indent);
- else
- line = cp->getline(c, cp->cookie, indent);
+ // First time inside the ":while"/":for": get line normally.
+ if (cp->getline == NULL) {
+ line = getcmdline(c, 0L, indent, do_concat);
+ } else {
+ line = cp->getline(c, cp->cookie, indent, do_concat);
+ }
if (line != NULL) {
store_loop_line(cp->lines_gap, line);
++cp->current_line;
@@ -1197,69 +1223,74 @@ static void get_wincmd_addr_type(char_u *arg, exarg_T *eap)
}
}
-/*
- * Execute one Ex command.
- *
- * If 'sourcing' is TRUE, the command will be included in the error message.
- *
- * 1. skip comment lines and leading space
- * 2. handle command modifiers
- * 3. skip over the range to find the command
- * 4. parse the range
- * 5. parse the command
- * 6. parse arguments
- * 7. switch on command name
- *
- * Note: "fgetline" can be NULL.
- *
- * This function may be called recursively!
- */
-static char_u * do_one_cmd(char_u **cmdlinep,
- int flags,
- struct condstack *cstack,
- LineGetter fgetline,
- void *cookie /* argument for fgetline() */
- )
+/// Skip colons and trailing whitespace, returning a pointer to the first
+/// non-colon, non-whitespace character.
+//
+/// @param skipleadingwhite Skip leading whitespace too
+static char_u *skip_colon_white(const char_u *p, bool skipleadingwhite)
{
- char_u *p;
- linenr_T lnum;
- long n;
- char_u *errormsg = NULL; /* error message */
- exarg_T ea; /* Ex command arguments */
- long verbose_save = -1;
- int save_msg_scroll = msg_scroll;
- int save_msg_silent = -1;
- int did_esilent = 0;
- int did_sandbox = FALSE;
- cmdmod_T save_cmdmod;
- const int save_reg_executing = reg_executing;
- char_u *cmd;
- int address_count = 1;
+ if (skipleadingwhite) {
+ p = skipwhite(p);
+ }
- memset(&ea, 0, sizeof(ea));
- ea.line1 = 1;
- ea.line2 = 1;
- ex_nesting_level++;
+ while (*p == ':') {
+ p = skipwhite(p + 1);
+ }
- /* When the last file has not been edited :q has to be typed twice. */
- if (quitmore
- /* avoid that a function call in 'statusline' does this */
- && !getline_equal(fgetline, cookie, get_func_line)
- /* avoid that an autocommand, e.g. QuitPre, does this */
- && !getline_equal(fgetline, cookie, getnextac)
- )
- --quitmore;
+ return (char_u *)p;
+}
- /*
- * Reset browse, confirm, etc.. They are restored when returning, for
- * recursive calls.
- */
- save_cmdmod = cmdmod;
- memset(&cmdmod, 0, sizeof(cmdmod));
+static void parse_state_to_global(const parse_state_T *parse_state)
+{
+ cmdmod = parse_state->cmdmod;
+ sandbox = parse_state->sandbox;
+ msg_silent = parse_state->msg_silent;
+ emsg_silent = parse_state->emsg_silent;
+ ex_pressedreturn = parse_state->ex_pressedreturn;
+ p_verbose = parse_state->p_verbose;
- /* "#!anything" is handled like a comment. */
- if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!')
- goto doend;
+ if (parse_state->set_eventignore) {
+ set_string_option_direct(
+ (char_u *)"ei", -1, (char_u *)"all", OPT_FREE, SID_NONE);
+ }
+}
+
+static void parse_state_from_global(parse_state_T *parse_state)
+{
+ memset(parse_state, 0, sizeof(*parse_state));
+ parse_state->cmdmod = cmdmod;
+ parse_state->sandbox = sandbox;
+ parse_state->msg_silent = msg_silent;
+ parse_state->emsg_silent = emsg_silent;
+ parse_state->ex_pressedreturn = ex_pressedreturn;
+ parse_state->p_verbose = p_verbose;
+}
+
+//
+// Parse one Ex command.
+//
+// This has no side-effects, except for modifying parameters
+// passed in by pointer.
+//
+// The `out` should be zeroed, and its `ea` member initialised,
+// before calling this function.
+//
+static bool parse_one_cmd(
+ char_u **cmdlinep,
+ parse_state_T *const out,
+ LineGetter fgetline,
+ void *fgetline_cookie)
+{
+ exarg_T ea = {
+ .line1 = 1,
+ .line2 = 1,
+ };
+ *out->eap = ea;
+
+ // "#!anything" is handled like a comment.
+ if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!') {
+ return false;
+ }
/*
* Repeat until no more command modifiers are found.
@@ -1269,70 +1300,76 @@ static char_u * do_one_cmd(char_u **cmdlinep,
/*
* 1. Skip comment lines and leading white space and colons.
*/
- while (*ea.cmd == ' ' || *ea.cmd == '\t' || *ea.cmd == ':')
- ++ea.cmd;
+ while (*ea.cmd == ' '
+ || *ea.cmd == '\t'
+ || *ea.cmd == ':') {
+ ea.cmd++;
+ }
- /* in ex mode, an empty line works like :+ */
+ // in ex mode, an empty line works like :+
if (*ea.cmd == NUL && exmode_active
- && (getline_equal(fgetline, cookie, getexmodeline)
- || getline_equal(fgetline, cookie, getexline))
+ && (getline_equal(fgetline, fgetline_cookie, getexmodeline)
+ || getline_equal(fgetline, fgetline_cookie, getexline))
&& curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) {
ea.cmd = (char_u *)"+";
- ex_pressedreturn = TRUE;
+ out->ex_pressedreturn = true;
}
- /* ignore comment and empty lines */
- if (*ea.cmd == '"')
- goto doend;
+ // ignore comment and empty lines
+ if (*ea.cmd == '"') {
+ return false;
+ }
if (*ea.cmd == NUL) {
- ex_pressedreturn = TRUE;
- goto doend;
+ out->ex_pressedreturn = true;
+ return false;
}
/*
* 2. Handle command modifiers.
*/
- p = skip_range(ea.cmd, NULL);
+ char_u *p = skip_range(ea.cmd, NULL);
switch (*p) {
- /* When adding an entry, also modify cmd_exists(). */
+ // When adding an entry, also modify cmd_exists().
case 'a': if (!checkforcmd(&ea.cmd, "aboveleft", 3))
break;
- cmdmod.split |= WSP_ABOVE;
+ out->cmdmod.split |= WSP_ABOVE;
continue;
case 'b': if (checkforcmd(&ea.cmd, "belowright", 3)) {
- cmdmod.split |= WSP_BELOW;
+ out->cmdmod.split |= WSP_BELOW;
continue;
}
if (checkforcmd(&ea.cmd, "browse", 3)) {
- cmdmod.browse = true;
+ out->cmdmod.browse = true;
continue;
}
- if (!checkforcmd(&ea.cmd, "botright", 2))
+ if (!checkforcmd(&ea.cmd, "botright", 2)) {
break;
- cmdmod.split |= WSP_BOT;
+ }
+ out->cmdmod.split |= WSP_BOT;
continue;
case 'c': if (!checkforcmd(&ea.cmd, "confirm", 4))
break;
- cmdmod.confirm = true;
+ out->cmdmod.confirm = true;
continue;
case 'k': if (checkforcmd(&ea.cmd, "keepmarks", 3)) {
- cmdmod.keepmarks = true;
+ out->cmdmod.keepmarks = true;
continue;
}
if (checkforcmd(&ea.cmd, "keepalt", 5)) {
- cmdmod.keepalt = true;
+ out->cmdmod.keepalt = true;
continue;
}
if (checkforcmd(&ea.cmd, "keeppatterns", 5)) {
- cmdmod.keeppatterns = true;
+ out->cmdmod.keeppatterns = true;
continue;
}
- if (!checkforcmd(&ea.cmd, "keepjumps", 5))
+ if (!checkforcmd(&ea.cmd, "keepjumps", 5)) {
break;
- cmdmod.keepjumps = true;
+ }
+ out->cmdmod.keepjumps = true;
continue;
case 'f': { // only accept ":filter {pat} cmd"
@@ -1342,7 +1379,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
break;
}
if (*p == '!') {
- cmdmod.filter_force = true;
+ out->cmdmod.filter_force = true;
p = skipwhite(p + 1);
if (*p == NUL || ends_excmd(*p)) {
break;
@@ -1352,134 +1389,217 @@ static char_u * do_one_cmd(char_u **cmdlinep,
if (p == NULL || *p == NUL) {
break;
}
- cmdmod.filter_regmatch.regprog = vim_regcomp(reg_pat, RE_MAGIC);
- if (cmdmod.filter_regmatch.regprog == NULL) {
+ out->cmdmod.filter_regmatch.regprog = vim_regcomp(reg_pat, RE_MAGIC);
+ if (out->cmdmod.filter_regmatch.regprog == NULL) {
break;
}
ea.cmd = p;
continue;
}
- /* ":hide" and ":hide | cmd" are not modifiers */
+ // ":hide" and ":hide | cmd" are not modifiers
case 'h': if (p != ea.cmd || !checkforcmd(&p, "hide", 3)
|| *p == NUL || ends_excmd(*p))
break;
ea.cmd = p;
- cmdmod.hide = true;
+ out->cmdmod.hide = true;
continue;
case 'l': if (checkforcmd(&ea.cmd, "lockmarks", 3)) {
- cmdmod.lockmarks = true;
+ out->cmdmod.lockmarks = true;
continue;
}
- if (!checkforcmd(&ea.cmd, "leftabove", 5))
+ if (!checkforcmd(&ea.cmd, "leftabove", 5)) {
break;
- cmdmod.split |= WSP_ABOVE;
+ }
+ out->cmdmod.split |= WSP_ABOVE;
continue;
case 'n':
if (checkforcmd(&ea.cmd, "noautocmd", 3)) {
- if (cmdmod.save_ei == NULL) {
- /* Set 'eventignore' to "all". Restore the
- * existing option value later. */
- cmdmod.save_ei = vim_strsave(p_ei);
- set_string_option_direct(
- (char_u *)"ei", -1, (char_u *)"all", OPT_FREE, SID_NONE);
+ if (out->cmdmod.save_ei == NULL) {
+ // Set 'eventignore' to "all". Restore the
+ // existing option value later.
+ out->cmdmod.save_ei = vim_strsave(p_ei);
+ out->set_eventignore = true;
}
continue;
}
if (!checkforcmd(&ea.cmd, "noswapfile", 3)) {
break;
}
- cmdmod.noswapfile = true;
+ out->cmdmod.noswapfile = true;
continue;
case 'r': if (!checkforcmd(&ea.cmd, "rightbelow", 6))
break;
- cmdmod.split |= WSP_BELOW;
+ out->cmdmod.split |= WSP_BELOW;
continue;
case 's': if (checkforcmd(&ea.cmd, "sandbox", 3)) {
- if (!did_sandbox)
- ++sandbox;
- did_sandbox = TRUE;
+ if (!out->did_sandbox) {
+ out->sandbox++;
+ }
+ out->did_sandbox = true;
continue;
}
- if (!checkforcmd(&ea.cmd, "silent", 3))
+ if (!checkforcmd(&ea.cmd, "silent", 3)) {
break;
- if (save_msg_silent == -1)
- save_msg_silent = msg_silent;
- ++msg_silent;
+ }
+ if (out->save_msg_silent == -1) {
+ out->save_msg_silent = out->msg_silent;
+ }
+ out->msg_silent++;
if (*ea.cmd == '!' && !ascii_iswhite(ea.cmd[-1])) {
- /* ":silent!", but not "silent !cmd" */
+ // ":silent!", but not "silent !cmd"
ea.cmd = skipwhite(ea.cmd + 1);
- ++emsg_silent;
- ++did_esilent;
+ out->emsg_silent++;
+ out->did_esilent++;
}
continue;
case 't': if (checkforcmd(&p, "tab", 3)) {
- long tabnr = get_address(&ea, &ea.cmd, ADDR_TABS, ea.skip, false, 1);
+ long tabnr = get_address(
+ &ea, &ea.cmd, ADDR_TABS, ea.skip, false, 1);
+
if (tabnr == MAXLNUM) {
- cmdmod.tab = tabpage_index(curtab) + 1;
+ out->cmdmod.tab = tabpage_index(curtab) + 1;
} else {
if (tabnr < 0 || tabnr > LAST_TAB_NR) {
- errormsg = (char_u *)_(e_invrange);
- goto doend;
+ out->errormsg = (char_u *)_(e_invrange);
+ return false;
}
- cmdmod.tab = tabnr + 1;
+ out->cmdmod.tab = tabnr + 1;
}
ea.cmd = p;
continue;
}
- if (!checkforcmd(&ea.cmd, "topleft", 2))
+ if (!checkforcmd(&ea.cmd, "topleft", 2)) {
break;
- cmdmod.split |= WSP_TOP;
+ }
+ out->cmdmod.split |= WSP_TOP;
continue;
case 'u': if (!checkforcmd(&ea.cmd, "unsilent", 3))
break;
- if (save_msg_silent == -1)
- save_msg_silent = msg_silent;
- msg_silent = 0;
+ if (out->save_msg_silent == -1) {
+ out->save_msg_silent = out->msg_silent;
+ }
+ out->msg_silent = 0;
continue;
case 'v': if (checkforcmd(&ea.cmd, "vertical", 4)) {
- cmdmod.split |= WSP_VERT;
+ out->cmdmod.split |= WSP_VERT;
continue;
}
if (!checkforcmd(&p, "verbose", 4))
break;
- if (verbose_save < 0)
- verbose_save = p_verbose;
- if (ascii_isdigit(*ea.cmd))
- p_verbose = atoi((char *)ea.cmd);
- else
- p_verbose = 1;
+ if (out->verbose_save < 0) {
+ out->verbose_save = out->p_verbose;
+ }
+ if (ascii_isdigit(*ea.cmd)) {
+ out->p_verbose = atoi((char *)ea.cmd);
+ } else {
+ out->p_verbose = 1;
+ }
ea.cmd = p;
continue;
}
break;
}
- char_u *after_modifier = ea.cmd;
-
- ea.skip = (did_emsg
- || got_int
- || current_exception
- || (cstack->cs_idx >= 0
- && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE)));
+ out->after_modifier = ea.cmd;
// 3. Skip over the range to find the command. Let "p" point to after it.
//
// We need the command to know what kind of range it uses.
- cmd = ea.cmd;
+ out->cmd = ea.cmd;
ea.cmd = skip_range(ea.cmd, NULL);
if (*ea.cmd == '*') {
ea.cmd = skipwhite(ea.cmd + 1);
}
- p = find_command(&ea, NULL);
+ out->parsed_upto = find_command(&ea, NULL);
+
+ *out->eap = ea;
+
+ return true;
+}
+
+/*
+ * Execute one Ex command.
+ *
+ * If 'sourcing' is TRUE, the command will be included in the error message.
+ *
+ * 1. skip comment lines and leading space
+ * 2. handle command modifiers
+ * 3. skip over the range to find the command
+ * 4. parse the range
+ * 5. parse the command
+ * 6. parse arguments
+ * 7. switch on command name
+ *
+ * Note: "fgetline" can be NULL.
+ *
+ * This function may be called recursively!
+ */
+static char_u * do_one_cmd(char_u **cmdlinep,
+ int flags,
+ struct condstack *cstack,
+ LineGetter fgetline,
+ void *cookie /* argument for fgetline() */
+ )
+{
+ char_u *p;
+ linenr_T lnum;
+ long n;
+ char_u *errormsg = NULL; // error message
+ exarg_T ea;
+ int save_msg_scroll = msg_scroll;
+ parse_state_T parsed;
+ cmdmod_T save_cmdmod;
+ const int save_reg_executing = reg_executing;
+
+ ex_nesting_level++;
+
+ /* When the last file has not been edited :q has to be typed twice. */
+ if (quitmore
+ /* avoid that a function call in 'statusline' does this */
+ && !getline_equal(fgetline, cookie, get_func_line)
+ /* avoid that an autocommand, e.g. QuitPre, does this */
+ && !getline_equal(fgetline, cookie, getnextac)
+ )
+ --quitmore;
+
+ /*
+ * Reset browse, confirm, etc.. They are restored when returning, for
+ * recursive calls.
+ */
+ save_cmdmod = cmdmod;
+ memset(&cmdmod, 0, sizeof(cmdmod));
+
+ parse_state_from_global(&parsed);
+ parsed.eap = &ea;
+ parsed.verbose_save = -1;
+ parsed.save_msg_silent = -1;
+ parsed.did_esilent = 0;
+ parsed.did_sandbox = false;
+ bool parse_success = parse_one_cmd(cmdlinep, &parsed, fgetline, cookie);
+ parse_state_to_global(&parsed);
+
+ // Update locals from parse_one_cmd()
+ errormsg = parsed.errormsg;
+ p = parsed.parsed_upto;
+
+ if (!parse_success) {
+ goto doend;
+ }
+
+ ea.skip = (did_emsg
+ || got_int
+ || current_exception
+ || (cstack->cs_idx >= 0
+ && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE)));
// Count this line for profiling if skip is TRUE.
if (do_profiling == PROF_YES
@@ -1550,148 +1670,9 @@ static char_u * do_one_cmd(char_u **cmdlinep,
}
}
- /* repeat for all ',' or ';' separated addresses */
- ea.cmd = cmd;
- for (;; ) {
- ea.line1 = ea.line2;
- switch (ea.addr_type) {
- case ADDR_LINES:
- // default is current line number
- ea.line2 = curwin->w_cursor.lnum;
- break;
- case ADDR_WINDOWS:
- ea.line2 = CURRENT_WIN_NR;
- break;
- case ADDR_ARGUMENTS:
- ea.line2 = curwin->w_arg_idx + 1;
- if (ea.line2 > ARGCOUNT) {
- ea.line2 = ARGCOUNT;
- }
- break;
- case ADDR_LOADED_BUFFERS:
- case ADDR_BUFFERS:
- ea.line2 = curbuf->b_fnum;
- break;
- case ADDR_TABS:
- ea.line2 = CURRENT_TAB_NR;
- break;
- case ADDR_TABS_RELATIVE:
- ea.line2 = 1;
- break;
- case ADDR_QUICKFIX:
- ea.line2 = qf_get_cur_valid_idx(&ea);
- break;
- }
- ea.cmd = skipwhite(ea.cmd);
- lnum = get_address(&ea, &ea.cmd, ea.addr_type, ea.skip,
- ea.addr_count == 0, address_count++);
- if (ea.cmd == NULL) { // error detected
- goto doend;
- }
- if (lnum == MAXLNUM) {
- if (*ea.cmd == '%') { /* '%' - all lines */
- ++ea.cmd;
- switch (ea.addr_type) {
- case ADDR_LINES:
- ea.line1 = 1;
- ea.line2 = curbuf->b_ml.ml_line_count;
- break;
- case ADDR_LOADED_BUFFERS: {
- buf_T *buf = firstbuf;
- while (buf->b_next != NULL && buf->b_ml.ml_mfp == NULL) {
- buf = buf->b_next;
- }
- ea.line1 = buf->b_fnum;
- buf = lastbuf;
- while (buf->b_prev != NULL && buf->b_ml.ml_mfp == NULL) {
- buf = buf->b_prev;
- }
- ea.line2 = buf->b_fnum;
- break;
- }
- case ADDR_BUFFERS:
- ea.line1 = firstbuf->b_fnum;
- ea.line2 = lastbuf->b_fnum;
- break;
- case ADDR_WINDOWS:
- case ADDR_TABS:
- if (IS_USER_CMDIDX(ea.cmdidx)) {
- ea.line1 = 1;
- ea.line2 =
- ea.addr_type == ADDR_WINDOWS ? LAST_WIN_NR : LAST_TAB_NR;
- } else {
- // there is no Vim command which uses '%' and
- // ADDR_WINDOWS or ADDR_TABS
- errormsg = (char_u *)_(e_invrange);
- goto doend;
- }
- break;
- case ADDR_TABS_RELATIVE:
- errormsg = (char_u *)_(e_invrange);
- goto doend;
- break;
- case ADDR_ARGUMENTS:
- if (ARGCOUNT == 0) {
- ea.line1 = ea.line2 = 0;
- } else {
- ea.line1 = 1;
- ea.line2 = ARGCOUNT;
- }
- break;
- case ADDR_QUICKFIX:
- ea.line1 = 1;
- ea.line2 = qf_get_size(&ea);
- if (ea.line2 == 0) {
- ea.line2 = 1;
- }
- break;
- }
- ++ea.addr_count;
- }
- /* '*' - visual area */
- else if (*ea.cmd == '*') {
- pos_T *fp;
-
- if (ea.addr_type != ADDR_LINES) {
- errormsg = (char_u *)_(e_invrange);
- goto doend;
- }
-
- ++ea.cmd;
- if (!ea.skip) {
- fp = getmark('<', FALSE);
- if (check_mark(fp) == FAIL)
- goto doend;
- ea.line1 = fp->lnum;
- fp = getmark('>', FALSE);
- if (check_mark(fp) == FAIL)
- goto doend;
- ea.line2 = fp->lnum;
- ++ea.addr_count;
- }
- }
- } else
- ea.line2 = lnum;
- ea.addr_count++;
-
- if (*ea.cmd == ';') {
- if (!ea.skip) {
- curwin->w_cursor.lnum = ea.line2;
- // don't leave the cursor on an illegal line or column
- check_cursor();
- }
- } else if (*ea.cmd != ',') {
- break;
- }
- ea.cmd++;
- }
-
- /* One address given: set start and end lines */
- if (ea.addr_count == 1) {
- ea.line1 = ea.line2;
- /* ... but only implicit: really no address given */
- if (lnum == MAXLNUM)
- ea.addr_count = 0;
+ ea.cmd = parsed.cmd;
+ if (parse_cmd_address(&ea, &errormsg) == FAIL) {
+ goto doend;
}
/*
@@ -1701,9 +1682,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
/*
* Skip ':' and any white space
*/
- ea.cmd = skipwhite(ea.cmd);
- while (*ea.cmd == ':')
- ea.cmd = skipwhite(ea.cmd + 1);
+ ea.cmd = skip_colon_white(ea.cmd, true);
/*
* If we got a line, but no command, then go to the line.
@@ -1772,8 +1751,8 @@ static char_u * do_one_cmd(char_u **cmdlinep,
if (!(flags & DOCMD_VERBOSE)) {
// If the modifier was parsed OK the error must be in the following
// command
- if (after_modifier != NULL) {
- append_command(after_modifier);
+ if (parsed.after_modifier != NULL) {
+ append_command(parsed.after_modifier);
} else {
append_command(*cmdlinep);
}
@@ -2144,6 +2123,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
case CMD_browse:
case CMD_call:
case CMD_confirm:
+ case CMD_const:
case CMD_delfunction:
case CMD_djump:
case CMD_dlist:
@@ -2168,6 +2148,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
case CMD_leftabove:
case CMD_let:
case CMD_lockmarks:
+ case CMD_lockvar:
case CMD_lua:
case CMD_match:
case CMD_mzscheme:
@@ -2196,6 +2177,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
case CMD_tilde:
case CMD_topleft:
case CMD_unlet:
+ case CMD_unlockvar:
case CMD_verbose:
case CMD_vertical:
case CMD_wincmd:
@@ -2241,12 +2223,12 @@ static char_u * do_one_cmd(char_u **cmdlinep,
// The :try command saves the emsg_silent flag, reset it here when
// ":silent! try" was used, it should only apply to :try itself.
- if (ea.cmdidx == CMD_try && did_esilent > 0) {
- emsg_silent -= did_esilent;
+ if (ea.cmdidx == CMD_try && parsed.did_esilent > 0) {
+ emsg_silent -= parsed.did_esilent;
if (emsg_silent < 0) {
emsg_silent = 0;
}
- did_esilent = 0;
+ parsed.did_esilent = 0;
}
// 7. Execute the command.
@@ -2312,8 +2294,9 @@ doend:
? cmdnames[(int)ea.cmdidx].cmd_name
: (char_u *)NULL);
- if (verbose_save >= 0)
- p_verbose = verbose_save;
+ if (parsed.verbose_save >= 0) {
+ p_verbose = parsed.verbose_save;
+ }
if (cmdmod.save_ei != NULL) {
/* Restore 'eventignore' to the value before ":noautocmd". */
set_string_option_direct((char_u *)"ei", -1, cmdmod.save_ei,
@@ -2328,16 +2311,18 @@ doend:
cmdmod = save_cmdmod;
reg_executing = save_reg_executing;
- if (save_msg_silent != -1) {
- /* messages could be enabled for a serious error, need to check if the
- * counters don't become negative */
- if (!did_emsg || msg_silent > save_msg_silent)
- msg_silent = save_msg_silent;
- emsg_silent -= did_esilent;
- if (emsg_silent < 0)
+ if (parsed.save_msg_silent != -1) {
+ // messages could be enabled for a serious error, need to check if the
+ // counters don't become negative
+ if (!did_emsg || msg_silent > parsed.save_msg_silent) {
+ msg_silent = parsed.save_msg_silent;
+ }
+ emsg_silent -= parsed.did_esilent;
+ if (emsg_silent < 0) {
emsg_silent = 0;
- /* Restore msg_scroll, it's set by file I/O commands, even when no
- * message is actually displayed. */
+ }
+ // Restore msg_scroll, it's set by file I/O commands, even when no
+ // message is actually displayed.
msg_scroll = save_msg_scroll;
/* "silent reg" or "silent echo x" inside "redir" leaves msg_col
@@ -2346,8 +2331,9 @@ doend:
msg_col = 0;
}
- if (did_sandbox)
- --sandbox;
+ if (parsed.did_sandbox) {
+ sandbox--;
+ }
if (ea.nextcmd && *ea.nextcmd == NUL) /* not really a next command */
ea.nextcmd = NULL;
@@ -2357,6 +2343,160 @@ doend:
return ea.nextcmd;
}
+// Parse the address range, if any, in "eap".
+// Return FAIL and set "errormsg" or return OK.
+int parse_cmd_address(exarg_T *eap, char_u **errormsg)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int address_count = 1;
+ linenr_T lnum;
+
+ // Repeat for all ',' or ';' separated addresses.
+ for (;;) {
+ eap->line1 = eap->line2;
+ switch (eap->addr_type) {
+ case ADDR_LINES:
+ // default is current line number
+ eap->line2 = curwin->w_cursor.lnum;
+ break;
+ case ADDR_WINDOWS:
+ eap->line2 = CURRENT_WIN_NR;
+ break;
+ case ADDR_ARGUMENTS:
+ eap->line2 = curwin->w_arg_idx + 1;
+ if (eap->line2 > ARGCOUNT) {
+ eap->line2 = ARGCOUNT;
+ }
+ break;
+ case ADDR_LOADED_BUFFERS:
+ case ADDR_BUFFERS:
+ eap->line2 = curbuf->b_fnum;
+ break;
+ case ADDR_TABS:
+ eap->line2 = CURRENT_TAB_NR;
+ break;
+ case ADDR_TABS_RELATIVE:
+ eap->line2 = 1;
+ break;
+ case ADDR_QUICKFIX:
+ eap->line2 = qf_get_cur_valid_idx(eap);
+ break;
+ }
+ eap->cmd = skipwhite(eap->cmd);
+ lnum = get_address(eap, &eap->cmd, eap->addr_type, eap->skip,
+ eap->addr_count == 0, address_count++);
+ if (eap->cmd == NULL) { // error detected
+ return FAIL;
+ }
+ if (lnum == MAXLNUM) {
+ if (*eap->cmd == '%') { // '%' - all lines
+ eap->cmd++;
+ switch (eap->addr_type) {
+ case ADDR_LINES:
+ eap->line1 = 1;
+ eap->line2 = curbuf->b_ml.ml_line_count;
+ break;
+ case ADDR_LOADED_BUFFERS: {
+ buf_T *buf = firstbuf;
+
+ while (buf->b_next != NULL && buf->b_ml.ml_mfp == NULL) {
+ buf = buf->b_next;
+ }
+ eap->line1 = buf->b_fnum;
+ buf = lastbuf;
+ while (buf->b_prev != NULL && buf->b_ml.ml_mfp == NULL) {
+ buf = buf->b_prev;
+ }
+ eap->line2 = buf->b_fnum;
+ break;
+ }
+ case ADDR_BUFFERS:
+ eap->line1 = firstbuf->b_fnum;
+ eap->line2 = lastbuf->b_fnum;
+ break;
+ case ADDR_WINDOWS:
+ case ADDR_TABS:
+ if (IS_USER_CMDIDX(eap->cmdidx)) {
+ eap->line1 = 1;
+ eap->line2 = eap->addr_type == ADDR_WINDOWS
+ ? LAST_WIN_NR : LAST_TAB_NR;
+ } else {
+ // there is no Vim command which uses '%' and
+ // ADDR_WINDOWS or ADDR_TABS
+ *errormsg = (char_u *)_(e_invrange);
+ return FAIL;
+ }
+ break;
+ case ADDR_TABS_RELATIVE:
+ *errormsg = (char_u *)_(e_invrange);
+ return FAIL;
+ case ADDR_ARGUMENTS:
+ if (ARGCOUNT == 0) {
+ eap->line1 = eap->line2 = 0;
+ } else {
+ eap->line1 = 1;
+ eap->line2 = ARGCOUNT;
+ }
+ break;
+ case ADDR_QUICKFIX:
+ eap->line1 = 1;
+ eap->line2 = qf_get_size(eap);
+ if (eap->line2 == 0) {
+ eap->line2 = 1;
+ }
+ break;
+ }
+ eap->addr_count++;
+ } else if (*eap->cmd == '*') {
+ // '*' - visual area
+ if (eap->addr_type != ADDR_LINES) {
+ *errormsg = (char_u *)_(e_invrange);
+ return FAIL;
+ }
+
+ eap->cmd++;
+ if (!eap->skip) {
+ pos_T *fp = getmark('<', false);
+ if (check_mark(fp) == FAIL) {
+ return FAIL;
+ }
+ eap->line1 = fp->lnum;
+ fp = getmark('>', false);
+ if (check_mark(fp) == FAIL) {
+ return FAIL;
+ }
+ eap->line2 = fp->lnum;
+ eap->addr_count++;
+ }
+ }
+ } else {
+ eap->line2 = lnum;
+ }
+ eap->addr_count++;
+
+ if (*eap->cmd == ';') {
+ if (!eap->skip) {
+ curwin->w_cursor.lnum = eap->line2;
+ // don't leave the cursor on an illegal line or column
+ check_cursor();
+ }
+ } else if (*eap->cmd != ',') {
+ break;
+ }
+ eap->cmd++;
+ }
+
+ // One address given: set start and end lines.
+ if (eap->addr_count == 1) {
+ eap->line1 = eap->line2;
+ // ... but only implicit: really no address given
+ if (lnum == MAXLNUM) {
+ eap->addr_count = 0;
+ }
+ }
+ return OK;
+}
+
/*
* Check for an Ex command with optional tail.
* If there is a match advance "pp" to the argument and return TRUE.
@@ -3297,6 +3437,7 @@ const char * set_one_cmd_context(
case CMD_syntax:
set_context_in_syntax_cmd(xp, arg);
break;
+ case CMD_const:
case CMD_let:
case CMD_if:
case CMD_elseif:
@@ -3537,15 +3678,13 @@ const char * set_one_cmd_context(
return NULL;
}
-/*
- * skip a range specifier of the form: addr [,addr] [;addr] ..
- *
- * Backslashed delimiters after / or ? will be skipped, and commands will
- * not be expanded between /'s and ?'s or after "'".
- *
- * Also skip white space and ":" characters.
- * Returns the "cmd" pointer advanced to beyond the range.
- */
+// Skip a range specifier of the form: addr [,addr] [;addr] ..
+//
+// Backslashed delimiters after / or ? will be skipped, and commands will
+// not be expanded between /'s and ?'s or after "'".
+//
+// Also skip white space and ":" characters.
+// Returns the "cmd" pointer advanced to beyond the range.
char_u *skip_range(
const char_u *cmd,
int *ctx // pointer to xp_context or NULL
@@ -3576,9 +3715,8 @@ char_u *skip_range(
++cmd;
}
- /* Skip ":" and white space. */
- while (*cmd == ':')
- cmd = skipwhite(cmd + 1);
+ // Skip ":" and white space.
+ cmd = skip_colon_white(cmd, false);
return (char_u *)cmd;
}
@@ -3746,8 +3884,7 @@ static linenr_T get_address(exarg_T *eap,
curwin->w_cursor.col = 0;
}
searchcmdlen = 0;
- if (!do_search(NULL, c, cmd, 1L,
- SEARCH_HIS | SEARCH_MSG, NULL, NULL)) {
+ if (!do_search(NULL, c, cmd, 1L, SEARCH_HIS | SEARCH_MSG, NULL)) {
curwin->w_cursor = pos;
cmd = NULL;
goto error;
@@ -3784,8 +3921,7 @@ static linenr_T get_address(exarg_T *eap,
pos.coladd = 0;
if (searchit(curwin, curbuf, &pos, NULL,
*cmd == '?' ? BACKWARD : FORWARD,
- (char_u *)"", 1L, SEARCH_MSG,
- i, (linenr_T)0, NULL, NULL) != FAIL) {
+ (char_u *)"", 1L, SEARCH_MSG, i, NULL) != FAIL) {
lnum = pos.lnum;
} else {
cmd = NULL;
@@ -4998,9 +5134,11 @@ static void uc_list(char_u *name, size_t name_len)
ucmd_T *cmd;
int len;
uint32_t a;
- garray_T *gap;
- gap = &curbuf->b_ucmds;
+ // In cmdwin, the alternative buffer should be used.
+ garray_T *gap = (cmdwin_type != 0 && get_cmdline_type() == NUL)
+ ? &prevwin->w_buffer->b_ucmds
+ : &curbuf->b_ucmds;
for (;; ) {
for (i = 0; i < gap->ga_len; ++i) {
cmd = USER_CMD_GA(gap, i);
@@ -5849,13 +5987,21 @@ char_u *get_user_cmd_addr_type(expand_T *xp, int idx)
/*
* Function given to ExpandGeneric() to obtain the list of user command names.
*/
-char_u *get_user_commands(expand_T *xp, int idx)
+char_u *get_user_commands(expand_T *xp FUNC_ATTR_UNUSED, int idx)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- if (idx < curbuf->b_ucmds.ga_len)
- return USER_CMD_GA(&curbuf->b_ucmds, idx)->uc_name;
- idx -= curbuf->b_ucmds.ga_len;
- if (idx < ucmds.ga_len)
+ // In cmdwin, the alternative buffer should be used.
+ const buf_T *const buf = (cmdwin_type != 0 && get_cmdline_type() == NUL)
+ ? prevwin->w_buffer
+ : curbuf;
+
+ if (idx < buf->b_ucmds.ga_len) {
+ return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
+ }
+ idx -= buf->b_ucmds.ga_len;
+ if (idx < ucmds.ga_len) {
return USER_CMD(idx)->uc_name;
+ }
return NULL;
}
@@ -6066,9 +6212,11 @@ static bool before_quit_autocmds(win_T *wp, bool quit_all, int forceit)
if (quit_all
|| (check_more(false, forceit) == OK && only_one_window())) {
apply_autocmds(EVENT_EXITPRE, NULL, NULL, false, curbuf);
- // Refuse to quit when locked or when the buffer in the last window is
- // being closed (can only happen in autocommands).
- if (curbuf_locked()
+ // Refuse to quit when locked or when the window was closed or the
+ // buffer in the last window is being closed (can only happen in
+ // autocommands).
+ if (!win_valid(wp)
+ || curbuf_locked()
|| (curbuf->b_nwindows == 1 && curbuf->b_locked > 0)) {
return true;
}
@@ -6708,17 +6856,18 @@ static void ex_preserve(exarg_T *eap)
/// ":recover".
static void ex_recover(exarg_T *eap)
{
- /* Set recoverymode right away to avoid the ATTENTION prompt. */
- recoverymode = TRUE;
+ // Set recoverymode right away to avoid the ATTENTION prompt.
+ recoverymode = true;
if (!check_changed(curbuf, (p_awa ? CCGD_AW : 0)
| CCGD_MULTWIN
| (eap->forceit ? CCGD_FORCEIT : 0)
| CCGD_EXCMD)
&& (*eap->arg == NUL
- || setfname(curbuf, eap->arg, NULL, TRUE) == OK))
- ml_recover();
- recoverymode = FALSE;
+ || setfname(curbuf, eap->arg, NULL, true) == OK)) {
+ ml_recover(true);
+ }
+ recoverymode = false;
}
/*
@@ -8188,6 +8337,7 @@ static void ex_normal(exarg_T *eap)
int save_insertmode = p_im;
int save_finish_op = finish_op;
long save_opcount = opcount;
+ const int save_reg_executing = reg_executing;
char_u *arg = NULL;
int l;
char_u *p;
@@ -8282,7 +8432,8 @@ static void ex_normal(exarg_T *eap)
p_im = save_insertmode;
finish_op = save_finish_op;
opcount = save_opcount;
- msg_didout |= save_msg_didout; /* don't reset msg_didout now */
+ reg_executing = save_reg_executing;
+ msg_didout |= save_msg_didout; // don't reset msg_didout now
/* Restore the state (needed when called from a function executed for
* 'indentexpr'). Update the mouse and cursor, they may have changed. */
@@ -9553,7 +9704,7 @@ put_view(
*/
if ((*flagp & SSOP_FOLDS)
&& wp->w_buffer->b_ffname != NULL
- && (*wp->w_buffer->b_p_bt == NUL || bt_help(wp->w_buffer))
+ && (bt_normal(wp->w_buffer) || bt_help(wp->w_buffer))
) {
if (put_folds(fd, wp) == FAIL)
return FAIL;
@@ -10010,10 +10161,11 @@ static void ex_setfiletype(exarg_T *eap)
static void ex_digraphs(exarg_T *eap)
{
- if (*eap->arg != NUL)
+ if (*eap->arg != NUL) {
putdigraph(eap->arg);
- else
- listdigraphs();
+ } else {
+ listdigraphs(eap->forceit);
+ }
}
static void ex_set(exarg_T *eap)
@@ -10131,6 +10283,28 @@ static void ex_folddo(exarg_T *eap)
ml_clearmarked(); // clear rest of the marks
}
+// Returns true if the supplied Ex cmdidx is for a location list command
+// instead of a quickfix command.
+bool is_loclist_cmd(int cmdidx)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (cmdidx < 0 || cmdidx >= CMD_SIZE) {
+ return false;
+ }
+ return cmdnames[cmdidx].cmd_name[0] == 'l';
+}
+
+bool get_pressedreturn(void)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return ex_pressedreturn;
+}
+
+void set_pressedreturn(bool val)
+{
+ ex_pressedreturn = val;
+}
+
static void ex_terminal(exarg_T *eap)
{
char ex_cmd[1024];
@@ -10178,10 +10352,13 @@ bool cmd_can_preview(char_u *cmd)
return false;
}
+ // Ignore additional colons at the start...
+ cmd = skip_colon_white(cmd, true);
+
// Ignore any leading modifiers (:keeppatterns, :verbose, etc.)
for (int len = modifier_len(cmd); len != 0; len = modifier_len(cmd)) {
cmd += len;
- cmd = skipwhite(cmd);
+ cmd = skip_colon_white(cmd, true);
}
exarg_T ea;
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 3f7bc45c2e..8065d764b9 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -878,7 +878,9 @@ static int command_line_execute(VimState *state, int key)
}
if (s->c == cedit_key || s->c == K_CMDWIN) {
- if (ex_normal_busy == 0 && got_int == false) {
+ // TODO(vim): why is ex_normal_busy checked here?
+ if ((s->c == K_CMDWIN || ex_normal_busy == 0)
+ && got_int == false) {
// Open a window to edit the command line (and history).
s->c = open_cmdwin();
s->some_key_typed = true;
@@ -1075,7 +1077,7 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match)
int found = searchit(curwin, curbuf, &t, NULL,
next_match ? FORWARD : BACKWARD,
pat, s->count, search_flags,
- RE_SEARCH, 0, NULL, NULL);
+ RE_SEARCH, NULL);
emsg_off--;
ui_busy_stop();
if (found) {
@@ -1818,6 +1820,7 @@ static int command_line_changed(CommandLineState *s)
if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) {
pos_T end_pos;
proftime_T tm;
+ searchit_arg_T sia;
// if there is a character waiting, search and redraw later
if (char_avail()) {
@@ -1844,8 +1847,10 @@ static int command_line_changed(CommandLineState *s)
if (!p_hls) {
search_flags += SEARCH_KEEP;
}
+ memset(&sia, 0, sizeof(sia));
+ sia.sa_tm = &tm;
i = do_search(NULL, s->firstc, ccline.cmdbuff, s->count,
- search_flags, &tm, NULL);
+ search_flags, &sia);
emsg_off--;
// if interrupted while searching, behave like it failed
if (got_int) {
@@ -1924,7 +1929,9 @@ static int command_line_changed(CommandLineState *s)
// - Immediately undo the effects.
State |= CMDPREVIEW;
emsg_silent++; // Block error reporting as the command may be incomplete
+ msg_silent++; // Block messages, namely ones that prompt
do_cmdline(ccline.cmdbuff, NULL, NULL, DOCMD_KEEPLINE|DOCMD_NOWAIT);
+ msg_silent--; // Unblock messages
emsg_silent--; // Unblock error reporting
// Restore the window "view".
@@ -1991,7 +1998,8 @@ char_u *
getcmdline (
int firstc,
long count, // only used for incremental search
- int indent // indent for inside conditionals
+ int indent, // indent for inside conditionals
+ bool do_concat // unused
)
{
// Be prepared for situations where cmdline can be invoked recursively.
@@ -2167,17 +2175,18 @@ static void correct_screencol(int idx, int cells, int *col)
* Get an Ex command line for the ":" command.
*/
char_u *
-getexline (
- int c, /* normally ':', NUL for ":append" */
+getexline(
+ int c, // normally ':', NUL for ":append"
void *cookie,
- int indent /* indent for inside conditionals */
+ int indent, // indent for inside conditionals
+ bool do_concat
)
{
/* When executing a register, remove ':' that's in front of each line. */
if (exec_from_reg && vpeekc() == ':')
(void)vgetc();
- return getcmdline(c, 1L, indent);
+ return getcmdline(c, 1L, indent, do_concat);
}
/*
@@ -2187,11 +2196,12 @@ getexline (
* Returns a string in allocated memory or NULL.
*/
char_u *
-getexmodeline (
- int promptc, /* normally ':', NUL for ":append" and '?' for
- :s prompt */
+getexmodeline(
+ int promptc, // normally ':', NUL for ":append" and '?'
+ // for :s prompt
void *cookie,
- int indent /* indent for inside conditionals */
+ int indent, // indent for inside conditionals
+ bool do_concat
)
{
garray_T line_ga;
@@ -2320,7 +2330,7 @@ redraw:
} while (++vcol % 8);
p++;
} else {
- len = MB_PTR2LEN(p);
+ len = utfc_ptr2len(p);
msg_outtrans_len(p, len);
vcol += ptr2cells(p);
p += len;
@@ -5000,19 +5010,24 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file,
hashtab_T found_ht;
hash_init(&found_ht);
for (s = path; ; s = e) {
+ e = vim_strchr(s, ENV_SEPCHAR);
+ if (e == NULL) {
+ e = s + STRLEN(s);
+ }
+
if (*s == NUL) {
if (did_curdir) {
break;
}
// Find directories in the current directory, path is empty.
did_curdir = true;
- } else if (*s == '.') {
+ flags |= EW_DIR;
+ } else if (STRNCMP(s, ".", e - s) == 0) {
did_curdir = true;
- }
-
- e = vim_strchr(s, ENV_SEPCHAR);
- if (e == NULL) {
- e = s + STRLEN(s);
+ flags |= EW_DIR;
+ } else {
+ // Do not match directories inside a $PATH item.
+ flags &= ~EW_DIR;
}
l = (size_t)(e - s);
@@ -6065,12 +6080,9 @@ static int open_cmdwin(void)
set_bufref(&old_curbuf, curbuf);
- /* Save current window sizes. */
+ // Save current window sizes.
win_size_save(&winsizes);
- /* Don't execute autocommands while creating the window. */
- block_autocmds();
-
// When using completion in Insert mode with <C-R>=<C-F> one can open the
// command line window, but we don't want the popup menu then.
pum_undisplay(true);
@@ -6079,10 +6091,9 @@ static int open_cmdwin(void)
cmdmod.tab = 0;
cmdmod.noswapfile = 1;
- /* Create a window for the command-line buffer. */
+ // Create a window for the command-line buffer.
if (win_split((int)p_cwh, WSP_BOT) == FAIL) {
beep_flush();
- unblock_autocmds();
return K_IGNORE;
}
cmdwin_type = get_cmdline_type();
@@ -6097,13 +6108,11 @@ static int open_cmdwin(void)
curbuf->b_p_ma = true;
curwin->w_p_fen = false;
- // Do execute autocommands for setting the filetype (load syntax).
- unblock_autocmds();
- // But don't allow switching to another buffer.
+ // Don't allow switching to another buffer.
curbuf_lock++;
- /* Showing the prompt may have set need_wait_return, reset it. */
- need_wait_return = FALSE;
+ // Showing the prompt may have set need_wait_return, reset it.
+ need_wait_return = false;
const int histtype = hist_char2type(cmdwin_type);
if (histtype == HIST_CMD || histtype == HIST_DEBUG) {
@@ -6115,11 +6124,11 @@ static int open_cmdwin(void)
}
curbuf_lock--;
- /* Reset 'textwidth' after setting 'filetype' (the Vim filetype plugin
- * sets 'textwidth' to 78). */
+ // Reset 'textwidth' after setting 'filetype' (the Vim filetype plugin
+ // sets 'textwidth' to 78).
curbuf->b_p_tw = 0;
- /* Fill the buffer with the history. */
+ // Fill the buffer with the history.
init_history();
if (hislen > 0 && histtype != HIST_INVALID) {
i = hisidx[histtype];
@@ -6160,9 +6169,10 @@ static int open_cmdwin(void)
// Trigger CmdwinEnter autocommands.
typestr[0] = (char_u)cmdwin_type;
typestr[1] = NUL;
- apply_autocmds(EVENT_CMDWINENTER, typestr, typestr, FALSE, curbuf);
- if (restart_edit != 0) /* autocmd with ":startinsert" */
+ apply_autocmds(EVENT_CMDWINENTER, typestr, typestr, false, curbuf);
+ if (restart_edit != 0) { // autocmd with ":startinsert"
stuffcharReadbuff(K_NOP);
+ }
i = RedrawingDisabled;
RedrawingDisabled = 0;
@@ -6179,10 +6189,10 @@ static int open_cmdwin(void)
const bool save_KeyTyped = KeyTyped;
- /* Trigger CmdwinLeave autocommands. */
- apply_autocmds(EVENT_CMDWINLEAVE, typestr, typestr, FALSE, curbuf);
+ // Trigger CmdwinLeave autocommands.
+ apply_autocmds(EVENT_CMDWINLEAVE, typestr, typestr, false, curbuf);
- /* Restore KeyTyped in case it is modified by autocommands */
+ // Restore KeyTyped in case it is modified by autocommands
KeyTyped = save_KeyTyped;
// Restore the command line info.
@@ -6241,9 +6251,7 @@ static int open_cmdwin(void)
}
}
- /* Don't execute autocommands while deleting the window. */
- block_autocmds();
- // Avoid command-line window first character being concealed
+ // Avoid command-line window first character being concealed.
curwin->w_p_cole = 0;
wp = curwin;
set_bufref(&bufref, curbuf);
@@ -6256,10 +6264,8 @@ static int open_cmdwin(void)
close_buffer(NULL, bufref.br_buf, DOBUF_WIPE, false);
}
- /* Restore window sizes. */
+ // Restore window sizes.
win_size_restore(&winsizes);
-
- unblock_autocmds();
}
ga_clear(&winsizes);
@@ -6308,7 +6314,7 @@ char *script_get(exarg_T *const eap, size_t *const lenp)
for (;;) {
char *const theline = (char *)eap->getline(
eap->cstack->cs_looplevel > 0 ? -1 :
- NUL, eap->cookie, 0);
+ NUL, eap->cookie, 0, true);
if (theline == NULL || strcmp(end_pattern, theline) == 0) {
xfree(theline);
diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h
index 051564fbe1..99d5a7786d 100644
--- a/src/nvim/ex_getln.h
+++ b/src/nvim/ex_getln.h
@@ -27,6 +27,8 @@
#define WILD_ESCAPE 0x80
#define WILD_ICASE 0x100
#define WILD_ALLLINKS 0x200
+#define WILD_IGNORE_COMPLETESLASH 0x400
+#define WILD_NOERROR 0x800 // sets EW_NOERROR
/// Present history tables
typedef enum {
diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c
index ad6a481bc5..47272df2f0 100644
--- a/src/nvim/file_search.c
+++ b/src/nvim/file_search.c
@@ -1102,8 +1102,8 @@ static bool ff_wc_equal(char_u *s1, char_u *s2)
prev2 = prev1;
prev1 = c1;
- i += MB_PTR2LEN(s1 + i);
- j += MB_PTR2LEN(s2 + j);
+ i += utfc_ptr2len(s1 + i);
+ j += utfc_ptr2len(s2 + j);
}
return s1[i] == s2[j];
}
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index 4cf42b41e9..f518e59acc 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -2650,6 +2650,7 @@ buf_write(
*/
if (!(append && *p_pm == NUL) && !filtering && perm >= 0 && dobackup) {
FileInfo file_info;
+ const bool no_prepend_dot = false;
if ((bkc & BKC_YES) || append) { /* "yes" */
backup_copy = TRUE;
@@ -2737,6 +2738,7 @@ buf_write(
int some_error = false;
char_u *dirp;
char_u *rootname;
+ char_u *p;
/*
* Try to make the backup in each directory in the 'bdir' option.
@@ -2756,6 +2758,17 @@ buf_write(
* Isolate one directory name, using an entry in 'bdir'.
*/
(void)copy_option_part(&dirp, IObuff, IOSIZE, ",");
+ p = IObuff + STRLEN(IObuff);
+ if (after_pathsep((char *)IObuff, (char *)p) && p[-1] == p[-2]) {
+ // Ends with '//', Use Full path
+ if ((p = (char_u *)make_percent_swname((char *)IObuff, (char *)fname))
+ != NULL) {
+ backup = (char_u *)modname((char *)p, (char *)backup_ext,
+ no_prepend_dot);
+ xfree(p);
+ }
+ }
+
rootname = get_file_in_dir(fname, IObuff);
if (rootname == NULL) {
some_error = TRUE; /* out of memory */
@@ -2764,10 +2777,14 @@ buf_write(
FileInfo file_info_new;
{
- /*
- * Make backup file name.
- */
- backup = (char_u *)modname((char *)rootname, (char *)backup_ext, FALSE);
+ //
+ // Make the backup file name.
+ //
+ if (backup == NULL) {
+ backup = (char_u *)modname((char *)rootname, (char *)backup_ext,
+ no_prepend_dot);
+ }
+
if (backup == NULL) {
xfree(rootname);
some_error = TRUE; /* out of memory */
@@ -2893,12 +2910,26 @@ nobackup:
* Isolate one directory name and make the backup file name.
*/
(void)copy_option_part(&dirp, IObuff, IOSIZE, ",");
- rootname = get_file_in_dir(fname, IObuff);
- if (rootname == NULL)
- backup = NULL;
- else {
- backup = (char_u *)modname((char *)rootname, (char *)backup_ext, FALSE);
- xfree(rootname);
+ p = IObuff + STRLEN(IObuff);
+ if (after_pathsep((char *)IObuff, (char *)p) && p[-1] == p[-2]) {
+ // path ends with '//', use full path
+ if ((p = (char_u *)make_percent_swname((char *)IObuff, (char *)fname))
+ != NULL) {
+ backup = (char_u *)modname((char *)p, (char *)backup_ext,
+ no_prepend_dot);
+ xfree(p);
+ }
+ }
+
+ if (backup == NULL) {
+ rootname = get_file_in_dir(fname, IObuff);
+ if (rootname == NULL) {
+ backup = NULL;
+ } else {
+ backup = (char_u *)modname((char *)rootname, (char *)backup_ext,
+ no_prepend_dot);
+ xfree(rootname);
+ }
}
if (backup != NULL) {
@@ -3700,8 +3731,9 @@ static int set_rw_fname(char_u *fname, char_u *sfname)
return FAIL;
}
- if (setfname(curbuf, fname, sfname, FALSE) == OK)
+ if (setfname(curbuf, fname, sfname, false) == OK) {
curbuf->b_flags |= BF_NOTEDITED;
+ }
/* ....and a new named one is created */
apply_autocmds(EVENT_BUFNEW, NULL, NULL, FALSE, curbuf);
@@ -4834,7 +4866,7 @@ buf_check_timestamp(
if (buf->terminal
|| buf->b_ffname == NULL
|| buf->b_ml.ml_mfp == NULL
- || *buf->b_p_bt != NUL
+ || !bt_normal(buf)
|| buf->b_saving
|| busy
)
@@ -7100,12 +7132,10 @@ auto_next_pat(
}
}
-/*
- * Get next autocommand command.
- * Called by do_cmdline() to get the next line for ":if".
- * Returns allocated string, or NULL for end of autocommands.
- */
-char_u *getnextac(int c, void *cookie, int indent)
+/// Get next autocommand command.
+/// Called by do_cmdline() to get the next line for ":if".
+/// @return allocated string, or NULL for end of autocommands.
+char_u *getnextac(int c, void *cookie, int indent, bool do_concat)
{
AutoPatCmd *acp = (AutoPatCmd *)cookie;
char_u *retval;
diff --git a/src/nvim/fold.c b/src/nvim/fold.c
index 5ce953e626..b193b4005c 100644
--- a/src/nvim/fold.c
+++ b/src/nvim/fold.c
@@ -771,6 +771,11 @@ void foldUpdate(win_T *wp, linenr_T top, linenr_T bot)
return;
}
+ if (need_diff_redraw) {
+ // will update later
+ return;
+ }
+
// Mark all folds from top to bot as maybe-small.
fold_T *fp;
(void)foldFind(&wp->w_folds, top, &fp);
diff --git a/src/nvim/generators/gen_char_blob.lua b/src/nvim/generators/gen_char_blob.lua
index 1702add2e4..a7dad50d48 100644
--- a/src/nvim/generators/gen_char_blob.lua
+++ b/src/nvim/generators/gen_char_blob.lua
@@ -1,49 +1,59 @@
if arg[1] == '--help' then
print('Usage:')
- print(' gencharblob.lua source target varname')
+ print(' '..arg[0]..' target source varname [source varname]...')
print('')
print('Generates C file with big uint8_t blob.')
print('Blob will be stored in a static const array named varname.')
os.exit()
end
-assert(#arg == 3)
+assert(#arg >= 3 and (#arg - 1) % 2 == 0)
-local source_file = arg[1]
-local target_file = arg[2]
-local varname = arg[3]
-
-local source = io.open(source_file, 'r')
+local target_file = arg[1] or error('Need a target file')
local target = io.open(target_file, 'w')
target:write('#include <stdint.h>\n\n')
-target:write(('static const uint8_t %s[] = {\n'):format(varname))
-
-local num_bytes = 0
-local MAX_NUM_BYTES = 15 -- 78 / 5: maximum number of bytes on one line
-target:write(' ')
-
-local increase_num_bytes
-increase_num_bytes = function()
- num_bytes = num_bytes + 1
- if num_bytes == MAX_NUM_BYTES then
- num_bytes = 0
- target:write('\n ')
+
+local varnames = {}
+for argi = 2, #arg, 2 do
+ local source_file = arg[argi]
+ local varname = arg[argi + 1]
+ if varnames[varname] then
+ error(string.format("varname %q is already specified for file %q", varname, varnames[varname]))
end
-end
+ varnames[varname] = source_file
+
+ local source = io.open(source_file, 'r')
+ or error(string.format("source_file %q doesn't exist", source_file))
+
+ target:write(('static const uint8_t %s[] = {\n'):format(varname))
-for line in source:lines() do
- for i = 1,string.len(line) do
- local byte = string.byte(line, i)
- assert(byte ~= 0)
- target:write(string.format(' %3u,', byte))
+ local num_bytes = 0
+ local MAX_NUM_BYTES = 15 -- 78 / 5: maximum number of bytes on one line
+ target:write(' ')
+
+ local increase_num_bytes
+ increase_num_bytes = function()
+ num_bytes = num_bytes + 1
+ if num_bytes == MAX_NUM_BYTES then
+ num_bytes = 0
+ target:write('\n ')
+ end
+ end
+
+ for line in source:lines() do
+ for i = 1, string.len(line) do
+ local byte = line:byte(i)
+ assert(byte ~= 0)
+ target:write(string.format(' %3u,', byte))
+ increase_num_bytes()
+ end
+ target:write(string.format(' %3u,', string.byte('\n', 1)))
increase_num_bytes()
end
- target:write(string.format(' %3u,', string.byte('\n', 1)))
- increase_num_bytes()
-end
-target:write(' 0};\n')
+ target:write(' 0};\n')
+ source:close()
+end
-source:close()
target:close()
diff --git a/src/nvim/generators/gen_declarations.lua b/src/nvim/generators/gen_declarations.lua
index ad44613f42..0782c8115d 100755
--- a/src/nvim/generators/gen_declarations.lua
+++ b/src/nvim/generators/gen_declarations.lua
@@ -207,9 +207,7 @@ preproc_f:close()
local header = [[
-#ifndef DEFINE_FUNC_ATTRIBUTES
-# define DEFINE_FUNC_ATTRIBUTES
-#endif
+#define DEFINE_FUNC_ATTRIBUTES
#include "nvim/func_attr.h"
#undef DEFINE_FUNC_ATTRIBUTES
]]
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 2469bb5baa..c038977127 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -1165,12 +1165,12 @@ void free_typebuf(void)
if (typebuf.tb_buf == typebuf_init) {
internal_error("Free typebuf 1");
} else {
- xfree(typebuf.tb_buf);
+ XFREE_CLEAR(typebuf.tb_buf);
}
if (typebuf.tb_noremap == noremapbuf_init) {
internal_error("Free typebuf 2");
} else {
- xfree(typebuf.tb_noremap);
+ XFREE_CLEAR(typebuf.tb_noremap);
}
}
@@ -1833,13 +1833,13 @@ static int vgetorpeek(int advance)
char_u *p1 = mp->m_keys;
char_u *p2 = (char_u *)mb_unescape((const char **)&p1);
- if (has_mbyte && p2 != NULL && MB_BYTE2LEN(c1) > MB_PTR2LEN(p2))
+ if (p2 != NULL && MB_BYTE2LEN(c1) > utfc_ptr2len(p2)) {
mlen = 0;
- /*
- * Check an entry whether it matches.
- * - Full match: mlen == keylen
- * - Partly match: mlen == typebuf.tb_len
- */
+ }
+
+ // Check an entry whether it matches.
+ // - Full match: mlen == keylen
+ // - Partly match: mlen == typebuf.tb_len
keylen = mp->m_keylen;
if (mlen == keylen
|| (mlen == typebuf.tb_len
@@ -2409,7 +2409,6 @@ int inchar(
did_outofmem_msg = FALSE; /* display out of memory message (again) */
did_swapwrite_msg = FALSE; /* display swap file write error again */
}
- undo_off = FALSE; /* restart undo now */
// Get a character from a script file if there is one.
// If interrupted: Stop reading script files, close them all.
@@ -4419,7 +4418,7 @@ mapblock_T *get_maphash(int index, buf_T *buf)
}
/// Get command argument for <Cmd> key
-char_u * getcmdkeycmd(int promptc, void *cookie, int indent)
+char_u * getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat)
{
garray_T line_ga;
int c1 = -1, c2;
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 5237c621f9..172c190df2 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -331,6 +331,7 @@ EXTERN int garbage_collect_at_exit INIT(= false);
#define SID_NONE -6 // don't set scriptID
#define SID_LUA -7 // for Lua scripts/chunks
#define SID_API_CLIENT -8 // for API clients
+#define SID_STR -9 // for sourcing a string
// Script CTX being sourced or was sourced to define the current function.
EXTERN sctx_T current_sctx INIT(= { 0 COMMA 0 COMMA 0 });
@@ -400,11 +401,6 @@ EXTERN bool mouse_past_eol INIT(= false); /* mouse right of line */
EXTERN int mouse_dragging INIT(= 0); /* extending Visual area with
mouse dragging */
-/* Value set from 'diffopt'. */
-EXTERN int diff_context INIT(= 6); /* context for folds */
-EXTERN int diff_foldcolumn INIT(= 2); /* 'foldcolumn' for diff mode */
-EXTERN int diff_need_scrollbind INIT(= FALSE);
-
/* The root of the menu hierarchy. */
EXTERN vimmenu_T *root_menu INIT(= NULL);
/*
@@ -768,7 +764,6 @@ EXTERN int did_outofmem_msg INIT(= false);
// set after out of memory msg
EXTERN int did_swapwrite_msg INIT(= false);
// set after swap write error msg
-EXTERN int undo_off INIT(= false); // undo switched off for now
EXTERN int global_busy INIT(= 0); // set when :global is executing
EXTERN int listcmd_busy INIT(= false); // set when :argdo, :windo or
// :bufdo is executing
@@ -787,7 +782,11 @@ EXTERN int postponed_split_flags INIT(= 0); /* args for win_split() */
EXTERN int postponed_split_tab INIT(= 0); /* cmdmod.tab */
EXTERN int g_do_tagpreview INIT(= 0); /* for tag preview commands:
height of preview window */
-EXTERN int replace_offset INIT(= 0); /* offset for replace_push() */
+EXTERN int g_tag_at_cursor INIT(= false); // whether the tag command comes
+ // from the command line (0) or was
+ // invoked as a normal command (1)
+
+EXTERN int replace_offset INIT(= 0); // offset for replace_push()
EXTERN char_u *escape_chars INIT(= (char_u *)" \t\\\"|");
/* need backslash in cmd line */
diff --git a/src/nvim/hashtab.h b/src/nvim/hashtab.h
index a70a8bea63..19633d455f 100644
--- a/src/nvim/hashtab.h
+++ b/src/nvim/hashtab.h
@@ -81,10 +81,10 @@ typedef struct hashtable_S {
size_t hi##todo_ = hi##ht_->ht_used; \
for (hashitem_T *hi = hi##ht_->ht_array; hi##todo_; hi++) { \
if (!HASHITEM_EMPTY(hi)) { \
+ hi##todo_--; \
{ \
code \
} \
- hi##todo_--; \
} \
} \
} while (0)
diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c
index 093cc4923b..c96f07ed89 100644
--- a/src/nvim/highlight.c
+++ b/src/nvim/highlight.c
@@ -308,23 +308,39 @@ int hl_combine_attr(int char_attr, int prim_attr)
// start with low-priority attribute, and override colors if present below.
HlAttrs new_en = char_aep;
- new_en.cterm_ae_attr |= spell_aep.cterm_ae_attr;
- new_en.rgb_ae_attr |= spell_aep.rgb_ae_attr;
+ if (spell_aep.cterm_ae_attr & HL_NOCOMBINE) {
+ new_en.cterm_ae_attr = spell_aep.cterm_ae_attr;
+ } else {
+ new_en.cterm_ae_attr |= spell_aep.cterm_ae_attr;
+ }
+ if (spell_aep.rgb_ae_attr & HL_NOCOMBINE) {
+ new_en.rgb_ae_attr = spell_aep.rgb_ae_attr;
+ } else {
+ new_en.rgb_ae_attr |= spell_aep.rgb_ae_attr;
+ }
if (spell_aep.cterm_fg_color > 0) {
new_en.cterm_fg_color = spell_aep.cterm_fg_color;
+ new_en.rgb_ae_attr &= ((~HL_FG_INDEXED)
+ | (spell_aep.rgb_ae_attr & HL_FG_INDEXED));
}
if (spell_aep.cterm_bg_color > 0) {
new_en.cterm_bg_color = spell_aep.cterm_bg_color;
+ new_en.rgb_ae_attr &= ((~HL_BG_INDEXED)
+ | (spell_aep.rgb_ae_attr & HL_BG_INDEXED));
}
if (spell_aep.rgb_fg_color >= 0) {
new_en.rgb_fg_color = spell_aep.rgb_fg_color;
+ new_en.rgb_ae_attr &= ((~HL_FG_INDEXED)
+ | (spell_aep.rgb_ae_attr & HL_FG_INDEXED));
}
if (spell_aep.rgb_bg_color >= 0) {
new_en.rgb_bg_color = spell_aep.rgb_bg_color;
+ new_en.rgb_ae_attr &= ((~HL_BG_INDEXED)
+ | (spell_aep.rgb_ae_attr & HL_BG_INDEXED));
}
if (spell_aep.rgb_sp_color >= 0) {
@@ -414,6 +430,7 @@ int hl_blend_attrs(int back_attr, int front_attr, bool *through)
cattrs.cterm_bg_color = fattrs.cterm_bg_color;
cattrs.cterm_fg_color = cterm_blend(ratio, battrs.cterm_fg_color,
fattrs.cterm_bg_color);
+ cattrs.rgb_ae_attr &= ~(HL_FG_INDEXED | HL_BG_INDEXED);
} else {
cattrs = fattrs;
if (ratio >= 50) {
@@ -427,6 +444,8 @@ int hl_blend_attrs(int back_attr, int front_attr, bool *through)
} else {
cattrs.rgb_sp_color = -1;
}
+
+ cattrs.rgb_ae_attr &= ~HL_BG_INDEXED;
}
cattrs.rgb_bg_color = rgb_blend(ratio, battrs.rgb_bg_color,
fattrs.rgb_bg_color);
@@ -603,6 +622,14 @@ Dictionary hlattrs2dict(HlAttrs ae, bool use_rgb)
}
if (use_rgb) {
+ if (mask & HL_FG_INDEXED) {
+ PUT(hl, "fg_indexed", BOOLEAN_OBJ(true));
+ }
+
+ if (mask & HL_BG_INDEXED) {
+ PUT(hl, "bg_indexed", BOOLEAN_OBJ(true));
+ }
+
if (ae.rgb_fg_color != -1) {
PUT(hl, "foreground", INTEGER_OBJ(ae.rgb_fg_color));
}
diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h
index f9c2c03fc6..36f3181674 100644
--- a/src/nvim/highlight_defs.h
+++ b/src/nvim/highlight_defs.h
@@ -18,6 +18,9 @@ typedef enum {
HL_UNDERCURL = 0x10,
HL_STANDOUT = 0x20,
HL_STRIKETHROUGH = 0x40,
+ HL_NOCOMBINE = 0x80,
+ HL_BG_INDEXED = 0x0100,
+ HL_FG_INDEXED = 0x0200,
} HlAttrFlags;
/// Stores a complete highlighting entry, including colors and attributes
diff --git a/src/nvim/lib/kbtree.h b/src/nvim/lib/kbtree.h
index 33aeff1d89..bef37f8ba9 100644
--- a/src/nvim/lib/kbtree.h
+++ b/src/nvim/lib/kbtree.h
@@ -25,6 +25,12 @@
* SUCH DAMAGE.
*/
+// Gotchas
+// -------
+//
+// if you delete from a kbtree while iterating over it you must use
+// kb_del_itr and not kb_del otherwise the iterator might point to freed memory.
+
#ifndef NVIM_LIB_KBTREE_H
#define NVIM_LIB_KBTREE_H
diff --git a/src/nvim/log.c b/src/nvim/log.c
index db36611933..225e40cdb4 100644
--- a/src/nvim/log.c
+++ b/src/nvim/log.c
@@ -93,6 +93,7 @@ static bool log_path_init(void)
void log_init(void)
{
uv_mutex_init(&mutex);
+ log_path_init();
}
void log_lock(void)
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
index 9665655e74..09d1a68898 100644
--- a/src/nvim/lua/converter.c
+++ b/src/nvim/lua/converter.c
@@ -377,6 +377,19 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
nlua_pop_typval_table_processing_end:
break;
}
+ case LUA_TUSERDATA: {
+ nlua_pushref(lstate, nlua_nil_ref);
+ bool is_nil = lua_rawequal(lstate, -2, -1);
+ lua_pop(lstate, 1);
+ if (is_nil) {
+ cur.tv->v_type = VAR_SPECIAL;
+ cur.tv->vval.v_special = kSpecialVarNull;
+ } else {
+ EMSG(_("E5101: Cannot convert given lua type"));
+ ret = false;
+ }
+ break;
+ }
default: {
EMSG(_("E5101: Cannot convert given lua type"));
ret = false;
@@ -401,10 +414,18 @@ nlua_pop_typval_table_processing_end:
return ret;
}
+static bool typval_conv_special = false;
+
#define TYPVAL_ENCODE_ALLOW_SPECIALS true
#define TYPVAL_ENCODE_CONV_NIL(tv) \
- lua_pushnil(lstate)
+ do { \
+ if (typval_conv_special) { \
+ lua_pushnil(lstate); \
+ } else { \
+ nlua_pushref(lstate, nlua_nil_ref); \
+ } \
+ } while (0)
#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \
lua_pushboolean(lstate, (bool)(num))
@@ -439,7 +460,13 @@ nlua_pop_typval_table_processing_end:
lua_createtable(lstate, 0, 0)
#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \
- nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary)
+ do { \
+ if (typval_conv_special) { \
+ nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary); \
+ } else { \
+ lua_createtable(lstate, 0, 0); \
+ } \
+ } while (0)
#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \
do { \
@@ -548,9 +575,11 @@ nlua_pop_typval_table_processing_end:
/// @param[in] tv typval_T to convert.
///
/// @return true in case of success, false otherwise.
-bool nlua_push_typval(lua_State *lstate, typval_T *const tv)
+bool nlua_push_typval(lua_State *lstate, typval_T *const tv, bool special)
{
+ typval_conv_special = special;
const int initial_size = lua_gettop(lstate);
+
if (!lua_checkstack(lstate, initial_size + 2)) {
emsgf(_("E1502: Lua failed to grow stack to %i"), initial_size + 4);
return false;
@@ -708,7 +737,11 @@ void nlua_push_Object(lua_State *lstate, const Object obj, bool special)
{
switch (obj.type) {
case kObjectTypeNil: {
- lua_pushnil(lstate);
+ if (special) {
+ lua_pushnil(lstate);
+ } else {
+ nlua_pushref(lstate, nlua_nil_ref);
+ }
break;
}
case kObjectTypeLuaRef: {
@@ -1142,6 +1175,19 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err)
break;
}
+ case LUA_TUSERDATA: {
+ nlua_pushref(lstate, nlua_nil_ref);
+ bool is_nil = lua_rawequal(lstate, -2, -1);
+ lua_pop(lstate, 1);
+ if (is_nil) {
+ *cur.obj = NIL;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "Cannot convert userdata");
+ }
+ break;
+ }
+
default: {
type_error:
api_set_error(err, kErrorTypeValidation,
@@ -1179,7 +1225,7 @@ GENERATE_INDEX_FUNCTION(Tabpage)
#undef GENERATE_INDEX_FUNCTION
-/// Record some auxilary values in vim module
+/// Record some auxiliary values in vim module
///
/// Assumes that module table is on top of the stack.
///
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index f51aa3c6d4..25f4be1c4d 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -12,6 +12,7 @@
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/vim.h"
+#include "nvim/msgpack_rpc/channel.h"
#include "nvim/vim.h"
#include "nvim/ex_getln.h"
#include "nvim/ex_cmds2.h"
@@ -31,6 +32,7 @@
#include "nvim/lua/executor.h"
#include "nvim/lua/converter.h"
+#include "nvim/lua/treesitter.h"
#include "luv/luv.h"
@@ -46,9 +48,6 @@ typedef struct {
# include "lua/executor.c.generated.h"
#endif
-/// Name of the run code for use in messages
-#define NLUA_EVAL_NAME "<VimL compiled string>"
-
/// Convert lua error into a Vim error message
///
/// @param lstate Lua interpreter state.
@@ -269,12 +268,7 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
#endif
// vim
- const char *code = (char *)&vim_module[0];
- if (luaL_loadbuffer(lstate, code, strlen(code), "@vim.lua")
- || lua_pcall(lstate, 0, LUA_MULTRET, 0)) {
- nlua_error(lstate, _("E5106: Error while creating vim module: %.*s"));
- return 1;
- }
+ lua_newtable(lstate);
// vim.api
nlua_add_api_functions(lstate);
// vim.types, vim.type_idx, vim.val_idx
@@ -294,6 +288,17 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
// in_fast_event
lua_pushcfunction(lstate, &nlua_in_fast_event);
lua_setfield(lstate, -2, "in_fast_event");
+ // call
+ lua_pushcfunction(lstate, &nlua_call);
+ lua_setfield(lstate, -2, "call");
+
+ // rpcrequest
+ lua_pushcfunction(lstate, &nlua_rpcrequest);
+ lua_setfield(lstate, -2, "rpcrequest");
+
+ // rpcnotify
+ lua_pushcfunction(lstate, &nlua_rpcnotify);
+ lua_setfield(lstate, -2, "rpcnotify");
// vim.loop
luv_set_loop(lstate, &main_loop.uv);
@@ -310,7 +315,38 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
lua_setfield(lstate, -2, "luv");
lua_pop(lstate, 3);
+ // vim.NIL
+ lua_newuserdata(lstate, 0);
+ lua_createtable(lstate, 0, 0);
+ lua_pushcfunction(lstate, &nlua_nil_tostring);
+ lua_setfield(lstate, -2, "__tostring");
+ lua_setmetatable(lstate, -2);
+ nlua_nil_ref = nlua_ref(lstate, -1);
+ lua_setfield(lstate, -2, "NIL");
+
+ // internal vim._treesitter... API
+ nlua_add_treesitter(lstate);
+
lua_setglobal(lstate, "vim");
+
+ {
+ const char *code = (char *)&shared_module[0];
+ if (luaL_loadbuffer(lstate, code, strlen(code), "@shared.lua")
+ || lua_pcall(lstate, 0, 0, 0)) {
+ nlua_error(lstate, _("E5106: Error while creating shared module: %.*s"));
+ return 1;
+ }
+ }
+
+ {
+ const char *code = (char *)&vim_module[0];
+ if (luaL_loadbuffer(lstate, code, strlen(code), "@vim.lua")
+ || lua_pcall(lstate, 0, 0, 0)) {
+ nlua_error(lstate, _("E5106: Error while creating vim module: %.*s"));
+ return 1;
+ }
+ }
+
return 0;
}
@@ -371,29 +407,6 @@ static lua_State *nlua_enter(void)
return lstate;
}
-/// Execute lua string
-///
-/// @param[in] str String to execute.
-/// @param[out] ret_tv Location where result will be saved.
-///
-/// @return Result of the execution.
-void executor_exec_lua(const String str, typval_T *const ret_tv)
- FUNC_ATTR_NONNULL_ALL
-{
- lua_State *const lstate = nlua_enter();
-
- if (luaL_loadbuffer(lstate, str.data, str.size, NLUA_EVAL_NAME)) {
- nlua_error(lstate, _("E5104: Error while creating lua chunk: %.*s"));
- return;
- }
- if (lua_pcall(lstate, 0, 1, 0)) {
- nlua_error(lstate, _("E5105: Error while calling lua chunk: %.*s"));
- return;
- }
-
- nlua_pop_typval(lstate, ret_tv);
-}
-
static void nlua_print_event(void **argv)
{
char *str = argv[0];
@@ -534,6 +547,125 @@ int nlua_in_fast_event(lua_State *lstate)
return 1;
}
+int nlua_call(lua_State *lstate)
+{
+ Error err = ERROR_INIT;
+ size_t name_len;
+ const char_u *name = (const char_u *)luaL_checklstring(lstate, 1, &name_len);
+ if (!nlua_is_deferred_safe(lstate)) {
+ return luaL_error(lstate, e_luv_api_disabled, "vimL function");
+ }
+
+ int nargs = lua_gettop(lstate)-1;
+ if (nargs > MAX_FUNC_ARGS) {
+ return luaL_error(lstate, "Function called with too many arguments");
+ }
+
+ typval_T vim_args[MAX_FUNC_ARGS + 1];
+ int i = 0; // also used for freeing the variables
+ for (; i < nargs; i++) {
+ lua_pushvalue(lstate, (int)i+2);
+ if (!nlua_pop_typval(lstate, &vim_args[i])) {
+ api_set_error(&err, kErrorTypeException,
+ "error converting argument %d", i+1);
+ goto free_vim_args;
+ }
+ }
+
+ TRY_WRAP({
+ // TODO(bfredl): this should be simplified in error handling refactor
+ force_abort = false;
+ suppress_errthrow = false;
+ current_exception = NULL;
+ did_emsg = false;
+
+ try_start();
+ typval_T rettv;
+ int dummy;
+ // call_func() retval is deceptive, ignore it. Instead we set `msg_list`
+ // (TRY_WRAP) to capture abort-causing non-exception errors.
+ (void)call_func(name, (int)name_len, &rettv, nargs,
+ vim_args, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum,
+ &dummy, true, NULL, NULL);
+ if (!try_end(&err)) {
+ nlua_push_typval(lstate, &rettv, false);
+ }
+ tv_clear(&rettv);
+ });
+
+free_vim_args:
+ while (i > 0) {
+ tv_clear(&vim_args[--i]);
+ }
+ if (ERROR_SET(&err)) {
+ lua_pushstring(lstate, err.msg);
+ api_clear_error(&err);
+ return lua_error(lstate);
+ }
+ return 1;
+}
+
+static int nlua_rpcrequest(lua_State *lstate)
+{
+ if (!nlua_is_deferred_safe(lstate)) {
+ return luaL_error(lstate, e_luv_api_disabled, "rpcrequest");
+ }
+ return nlua_rpc(lstate, true);
+}
+
+static int nlua_rpcnotify(lua_State *lstate)
+{
+ return nlua_rpc(lstate, false);
+}
+
+static int nlua_rpc(lua_State *lstate, bool request)
+{
+ size_t name_len;
+ uint64_t chan_id = (uint64_t)luaL_checkinteger(lstate, 1);
+ const char *name = luaL_checklstring(lstate, 2, &name_len);
+ int nargs = lua_gettop(lstate)-2;
+ Error err = ERROR_INIT;
+ Array args = ARRAY_DICT_INIT;
+
+ for (int i = 0; i < nargs; i++) {
+ lua_pushvalue(lstate, (int)i+3);
+ ADD(args, nlua_pop_Object(lstate, false, &err));
+ if (ERROR_SET(&err)) {
+ api_free_array(args);
+ goto check_err;
+ }
+ }
+
+ if (request) {
+ Object result = rpc_send_call(chan_id, name, args, &err);
+ if (!ERROR_SET(&err)) {
+ nlua_push_Object(lstate, result, false);
+ api_free_object(result);
+ }
+ } else {
+ if (!rpc_send_event(chan_id, name, args)) {
+ api_set_error(&err, kErrorTypeValidation,
+ "Invalid channel: %"PRIu64, chan_id);
+ }
+ }
+
+check_err:
+ if (ERROR_SET(&err)) {
+ lua_pushstring(lstate, err.msg);
+ api_clear_error(&err);
+ return lua_error(lstate);
+ }
+
+ return request ? 1 : 0;
+}
+
+static int nlua_nil_tostring(lua_State *lstate)
+{
+ lua_pushstring(lstate, "vim.NIL");
+ return 1;
+}
+
+
#ifdef WIN32
/// os.getenv: override os.getenv to maintain coherency. #9681
///
@@ -587,10 +719,6 @@ void executor_eval_lua(const String str, typval_T *const arg,
typval_T *const ret_tv)
FUNC_ATTR_NONNULL_ALL
{
- lua_State *const lstate = nlua_enter();
-
- garray_T str_ga;
- ga_init(&str_ga, 1, 80);
#define EVALHEADER "local _A=select(1,...) return ("
const size_t lcmd_len = sizeof(EVALHEADER) - 1 + str.size + 1;
char *lcmd;
@@ -603,35 +731,76 @@ void executor_eval_lua(const String str, typval_T *const arg,
memcpy(lcmd + sizeof(EVALHEADER) - 1, str.data, str.size);
lcmd[lcmd_len - 1] = ')';
#undef EVALHEADER
- if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) {
- nlua_error(lstate,
- _("E5107: Error while creating lua chunk for luaeval(): %.*s"));
- if (lcmd != (char *)IObuff) {
- xfree(lcmd);
- }
- return;
- }
+ typval_exec_lua(lcmd, lcmd_len, "luaeval()", arg, 1, true, ret_tv);
+
if (lcmd != (char *)IObuff) {
xfree(lcmd);
}
+}
- if (arg->v_type == VAR_UNKNOWN) {
- lua_pushnil(lstate);
+void executor_call_lua(const char *str, size_t len, typval_T *const args,
+ int argcount, typval_T *ret_tv)
+ FUNC_ATTR_NONNULL_ALL
+{
+#define CALLHEADER "return "
+#define CALLSUFFIX "(...)"
+ const size_t lcmd_len = sizeof(CALLHEADER) - 1 + len + sizeof(CALLSUFFIX) - 1;
+ char *lcmd;
+ if (lcmd_len < IOSIZE) {
+ lcmd = (char *)IObuff;
} else {
- nlua_push_typval(lstate, arg);
+ lcmd = xmalloc(lcmd_len);
+ }
+ memcpy(lcmd, CALLHEADER, sizeof(CALLHEADER) - 1);
+ memcpy(lcmd + sizeof(CALLHEADER) - 1, str, len);
+ memcpy(lcmd + sizeof(CALLHEADER) - 1 + len, CALLSUFFIX,
+ sizeof(CALLSUFFIX) - 1);
+#undef CALLHEADER
+#undef CALLSUFFIX
+
+ typval_exec_lua(lcmd, lcmd_len, "v:lua", args, argcount, false, ret_tv);
+
+ if (lcmd != (char *)IObuff) {
+ xfree(lcmd);
}
- if (lua_pcall(lstate, 1, 1, 0)) {
- nlua_error(lstate,
- _("E5108: Error while calling lua chunk for luaeval(): %.*s"));
+}
+
+static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name,
+ typval_T *const args, int argcount, bool special,
+ typval_T *ret_tv)
+{
+ if (check_restricted() || check_secure()) {
+ ret_tv->v_type = VAR_NUMBER;
+ ret_tv->vval.v_number = 0;
return;
}
- nlua_pop_typval(lstate, ret_tv);
+ lua_State *const lstate = nlua_enter();
+ if (luaL_loadbuffer(lstate, lcmd, lcmd_len, name)) {
+ nlua_error(lstate, _("E5107: Error loading lua %.*s"));
+ return;
+ }
+
+ for (int i = 0; i < argcount; i++) {
+ if (args[i].v_type == VAR_UNKNOWN) {
+ lua_pushnil(lstate);
+ } else {
+ nlua_push_typval(lstate, &args[i], special);
+ }
+ }
+ if (lua_pcall(lstate, argcount, ret_tv ? 1 : 0, 0)) {
+ nlua_error(lstate, _("E5108: Error executing lua %.*s"));
+ return;
+ }
+
+ if (ret_tv) {
+ nlua_pop_typval(lstate, ret_tv);
+ }
}
-/// Execute lua string
+/// Execute Lua string
///
-/// Used for nvim_execute_lua().
+/// Used for nvim_exec_lua().
///
/// @param[in] str String to execute.
/// @param[in] args array of ... args
@@ -712,9 +881,8 @@ void ex_lua(exarg_T *const eap)
xfree(code);
return;
}
- typval_T tv = { .v_type = VAR_UNKNOWN };
- executor_exec_lua((String) { .data = code, .size = len }, &tv);
- tv_clear(&tv);
+ typval_exec_lua(code, len, ":lua", NULL, 0, false, NULL);
+
xfree(code);
}
@@ -752,8 +920,8 @@ void ex_luado(exarg_T *const eap)
#undef DOSTART
#undef DOEND
- if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) {
- nlua_error(lstate, _("E5109: Error while creating lua chunk: %.*s"));
+ if (luaL_loadbuffer(lstate, lcmd, lcmd_len, ":luado")) {
+ nlua_error(lstate, _("E5109: Error loading lua: %.*s"));
if (lcmd_len >= IOSIZE) {
xfree(lcmd);
}
@@ -763,7 +931,7 @@ void ex_luado(exarg_T *const eap)
xfree(lcmd);
}
if (lua_pcall(lstate, 0, 1, 0)) {
- nlua_error(lstate, _("E5110: Error while creating lua function: %.*s"));
+ nlua_error(lstate, _("E5110: Error executing lua: %.*s"));
return;
}
for (linenr_T l = eap->line1; l <= eap->line2; l++) {
@@ -774,7 +942,7 @@ void ex_luado(exarg_T *const eap)
lua_pushstring(lstate, (const char *)ml_get_buf(curbuf, l, false));
lua_pushnumber(lstate, (lua_Number)l);
if (lua_pcall(lstate, 2, 1, 0)) {
- nlua_error(lstate, _("E5111: Error while calling lua function: %.*s"));
+ nlua_error(lstate, _("E5111: Error calling lua: %.*s"));
break;
}
if (lua_isstring(lstate, -1)) {
@@ -816,3 +984,27 @@ void ex_luafile(exarg_T *const eap)
return;
}
}
+
+static int create_tslua_parser(lua_State *L)
+{
+ if (lua_gettop(L) < 1 || !lua_isstring(L, 1)) {
+ return luaL_error(L, "string expected");
+ }
+
+ const char *lang_name = lua_tostring(L, 1);
+ return tslua_push_parser(L, lang_name);
+}
+
+static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
+{
+ tslua_init(lstate);
+
+ lua_pushcfunction(lstate, create_tslua_parser);
+ lua_setfield(lstate, -2, "_create_ts_parser");
+
+ lua_pushcfunction(lstate, tslua_register_lang);
+ lua_setfield(lstate, -2, "_ts_add_language");
+
+ lua_pushcfunction(lstate, tslua_inspect_lang);
+ lua_setfield(lstate, -2, "_ts_inspect_language");
+}
diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h
index 8d356a5600..32f66b629c 100644
--- a/src/nvim/lua/executor.h
+++ b/src/nvim/lua/executor.h
@@ -12,6 +12,8 @@
// Generated by msgpack-gen.lua
void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL;
+EXTERN LuaRef nlua_nil_ref INIT(= LUA_NOREF);
+
#define set_api_error(s, err) \
do { \
Error *err_ = (err); \
diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c
new file mode 100644
index 0000000000..d2072402bb
--- /dev/null
+++ b/src/nvim/lua/treesitter.c
@@ -0,0 +1,652 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+// lua bindings for tree-sitter.
+// NB: this file mostly contains a generic lua interface for tree-sitter
+// trees and nodes, and could be broken out as a reusable lua package
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <assert.h>
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include "tree_sitter/api.h"
+
+#include "nvim/lua/treesitter.h"
+#include "nvim/api/private/handle.h"
+#include "nvim/memline.h"
+
+typedef struct {
+ TSParser *parser;
+ TSTree *tree; // internal tree, used for editing/reparsing
+} TSLua_parser;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "lua/treesitter.c.generated.h"
+#endif
+
+static struct luaL_Reg parser_meta[] = {
+ { "__gc", parser_gc },
+ { "__tostring", parser_tostring },
+ { "parse_buf", parser_parse_buf },
+ { "edit", parser_edit },
+ { "tree", parser_tree },
+ { NULL, NULL }
+};
+
+static struct luaL_Reg tree_meta[] = {
+ { "__gc", tree_gc },
+ { "__tostring", tree_tostring },
+ { "root", tree_root },
+ { NULL, NULL }
+};
+
+static struct luaL_Reg node_meta[] = {
+ { "__tostring", node_tostring },
+ { "__eq", node_eq },
+ { "__len", node_child_count },
+ { "range", node_range },
+ { "start", node_start },
+ { "end_", node_end },
+ { "type", node_type },
+ { "symbol", node_symbol },
+ { "named", node_named },
+ { "missing", node_missing },
+ { "has_error", node_has_error },
+ { "sexpr", node_sexpr },
+ { "child_count", node_child_count },
+ { "named_child_count", node_named_child_count },
+ { "child", node_child },
+ { "named_child", node_named_child },
+ { "descendant_for_range", node_descendant_for_range },
+ { "named_descendant_for_range", node_named_descendant_for_range },
+ { "parent", node_parent },
+ { NULL, NULL }
+};
+
+static PMap(cstr_t) *langs;
+
+static void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta)
+{
+ if (luaL_newmetatable(L, tname)) { // [meta]
+ for (size_t i = 0; meta[i].name != NULL; i++) {
+ lua_pushcfunction(L, meta[i].func); // [meta, func]
+ lua_setfield(L, -2, meta[i].name); // [meta]
+ }
+
+ lua_pushvalue(L, -1); // [meta, meta]
+ lua_setfield(L, -2, "__index"); // [meta]
+ }
+ lua_pop(L, 1); // [] (don't use it now)
+}
+
+/// init the tslua library
+///
+/// all global state is stored in the regirstry of the lua_State
+void tslua_init(lua_State *L)
+{
+ langs = pmap_new(cstr_t)();
+
+ // type metatables
+ build_meta(L, "treesitter_parser", parser_meta);
+ build_meta(L, "treesitter_tree", tree_meta);
+ build_meta(L, "treesitter_node", node_meta);
+}
+
+int tslua_register_lang(lua_State *L)
+{
+ if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) {
+ return luaL_error(L, "string expected");
+ }
+
+ const char *path = lua_tostring(L, 1);
+ const char *lang_name = lua_tostring(L, 2);
+
+ if (pmap_has(cstr_t)(langs, lang_name)) {
+ return 0;
+ }
+
+#define BUFSIZE 128
+ char symbol_buf[BUFSIZE];
+ snprintf(symbol_buf, BUFSIZE, "tree_sitter_%s", lang_name);
+#undef BUFSIZE
+
+ uv_lib_t lib;
+ if (uv_dlopen(path, &lib)) {
+ snprintf((char *)IObuff, IOSIZE, "Failed to load parser: uv_dlopen: %s",
+ uv_dlerror(&lib));
+ uv_dlclose(&lib);
+ lua_pushstring(L, (char *)IObuff);
+ return lua_error(L);
+ }
+
+ TSLanguage *(*lang_parser)(void);
+ if (uv_dlsym(&lib, symbol_buf, (void **)&lang_parser)) {
+ snprintf((char *)IObuff, IOSIZE, "Failed to load parser: uv_dlsym: %s",
+ uv_dlerror(&lib));
+ uv_dlclose(&lib);
+ lua_pushstring(L, (char *)IObuff);
+ return lua_error(L);
+ }
+
+ TSLanguage *lang = lang_parser();
+ if (lang == NULL) {
+ return luaL_error(L, "Failed to load parser: internal error");
+ }
+
+ pmap_put(cstr_t)(langs, xstrdup(lang_name), lang);
+
+ lua_pushboolean(L, true);
+ return 1;
+}
+
+int tslua_inspect_lang(lua_State *L)
+{
+ if (lua_gettop(L) < 1 || !lua_isstring(L, 1)) {
+ return luaL_error(L, "string expected");
+ }
+ const char *lang_name = lua_tostring(L, 1);
+
+ TSLanguage *lang = pmap_get(cstr_t)(langs, lang_name);
+ if (!lang) {
+ return luaL_error(L, "no such language: %s", lang_name);
+ }
+
+ lua_createtable(L, 0, 2); // [retval]
+
+ size_t nsymbols = (size_t)ts_language_symbol_count(lang);
+
+ lua_createtable(L, nsymbols-1, 1); // [retval, symbols]
+ for (size_t i = 0; i < nsymbols; i++) {
+ TSSymbolType t = ts_language_symbol_type(lang, i);
+ if (t == TSSymbolTypeAuxiliary) {
+ // not used by the API
+ continue;
+ }
+ lua_createtable(L, 2, 0); // [retval, symbols, elem]
+ lua_pushstring(L, ts_language_symbol_name(lang, i));
+ lua_rawseti(L, -2, 1);
+ lua_pushboolean(L, t == TSSymbolTypeRegular);
+ lua_rawseti(L, -2, 2); // [retval, symbols, elem]
+ lua_rawseti(L, -2, i); // [retval, symbols]
+ }
+
+ lua_setfield(L, -2, "symbols"); // [retval]
+
+ size_t nfields = (size_t)ts_language_field_count(lang);
+ lua_createtable(L, nfields-1, 1); // [retval, fields]
+ for (size_t i = 0; i < nfields; i++) {
+ lua_pushstring(L, ts_language_field_name_for_id(lang, i));
+ lua_rawseti(L, -2, i); // [retval, fields]
+ }
+
+ lua_setfield(L, -2, "fields"); // [retval]
+ return 1;
+}
+
+int tslua_push_parser(lua_State *L, const char *lang_name)
+{
+ TSLanguage *lang = pmap_get(cstr_t)(langs, lang_name);
+ if (!lang) {
+ return luaL_error(L, "no such language: %s", lang_name);
+ }
+
+ TSParser *parser = ts_parser_new();
+ ts_parser_set_language(parser, lang);
+ TSLua_parser *p = lua_newuserdata(L, sizeof(TSLua_parser)); // [udata]
+ p->parser = parser;
+ p->tree = NULL;
+
+ lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_parser"); // [udata, meta]
+ lua_setmetatable(L, -2); // [udata]
+ return 1;
+}
+
+static TSLua_parser *parser_check(lua_State *L)
+{
+ return luaL_checkudata(L, 1, "treesitter_parser");
+}
+
+static int parser_gc(lua_State *L)
+{
+ TSLua_parser *p = parser_check(L);
+ if (!p) {
+ return 0;
+ }
+
+ ts_parser_delete(p->parser);
+ if (p->tree) {
+ ts_tree_delete(p->tree);
+ }
+
+ return 0;
+}
+
+static int parser_tostring(lua_State *L)
+{
+ lua_pushstring(L, "<parser>");
+ return 1;
+}
+
+static const char *input_cb(void *payload, uint32_t byte_index,
+ TSPoint position, uint32_t *bytes_read)
+{
+ buf_T *bp = payload;
+#define BUFSIZE 256
+ static char buf[BUFSIZE];
+
+ if ((linenr_T)position.row >= bp->b_ml.ml_line_count) {
+ *bytes_read = 0;
+ return "";
+ }
+ char_u *line = ml_get_buf(bp, position.row+1, false);
+ size_t len = STRLEN(line);
+ size_t tocopy = MIN(len-position.column, BUFSIZE);
+
+ memcpy(buf, line+position.column, tocopy);
+ // Translate embedded \n to NUL
+ memchrsub(buf, '\n', '\0', tocopy);
+ *bytes_read = (uint32_t)tocopy;
+ if (tocopy < BUFSIZE) {
+ // now add the final \n. If it didn't fit, input_cb will be called again
+ // on the same line with advanced column.
+ buf[tocopy] = '\n';
+ (*bytes_read)++;
+ }
+ return buf;
+#undef BUFSIZE
+}
+
+static int parser_parse_buf(lua_State *L)
+{
+ TSLua_parser *p = parser_check(L);
+ if (!p) {
+ return 0;
+ }
+
+ long bufnr = lua_tointeger(L, 2);
+ void *payload = handle_get_buffer(bufnr);
+ if (!payload) {
+ return luaL_error(L, "invalid buffer handle: %d", bufnr);
+ }
+ TSInput input = { payload, input_cb, TSInputEncodingUTF8 };
+ TSTree *new_tree = ts_parser_parse(p->parser, p->tree, input);
+ if (p->tree) {
+ ts_tree_delete(p->tree);
+ }
+ p->tree = new_tree;
+
+ tslua_push_tree(L, p->tree);
+ return 1;
+}
+
+static int parser_tree(lua_State *L)
+{
+ TSLua_parser *p = parser_check(L);
+ if (!p) {
+ return 0;
+ }
+
+ tslua_push_tree(L, p->tree);
+ return 1;
+}
+
+static int parser_edit(lua_State *L)
+{
+ if (lua_gettop(L) < 10) {
+ lua_pushstring(L, "not enough args to parser:edit()");
+ return lua_error(L);
+ }
+
+ TSLua_parser *p = parser_check(L);
+ if (!p) {
+ return 0;
+ }
+
+ if (!p->tree) {
+ return 0;
+ }
+
+ long start_byte = lua_tointeger(L, 2);
+ long old_end_byte = lua_tointeger(L, 3);
+ long new_end_byte = lua_tointeger(L, 4);
+ TSPoint start_point = { lua_tointeger(L, 5), lua_tointeger(L, 6) };
+ TSPoint old_end_point = { lua_tointeger(L, 7), lua_tointeger(L, 8) };
+ TSPoint new_end_point = { lua_tointeger(L, 9), lua_tointeger(L, 10) };
+
+ TSInputEdit edit = { start_byte, old_end_byte, new_end_byte,
+ start_point, old_end_point, new_end_point };
+
+ ts_tree_edit(p->tree, &edit);
+
+ return 0;
+}
+
+
+// Tree methods
+
+/// push tree interface on lua stack.
+///
+/// This makes a copy of the tree, so ownership of the argument is unaffected.
+void tslua_push_tree(lua_State *L, TSTree *tree)
+{
+ if (tree == NULL) {
+ lua_pushnil(L);
+ return;
+ }
+ TSTree **ud = lua_newuserdata(L, sizeof(TSTree *)); // [udata]
+ *ud = ts_tree_copy(tree);
+ lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_tree"); // [udata, meta]
+ lua_setmetatable(L, -2); // [udata]
+
+ // table used for node wrappers to keep a reference to tree wrapper
+ // NB: in lua 5.3 the uservalue for the node could just be the tree, but
+ // in lua 5.1 the uservalue (fenv) must be a table.
+ lua_createtable(L, 1, 0); // [udata, reftable]
+ lua_pushvalue(L, -2); // [udata, reftable, udata]
+ lua_rawseti(L, -2, 1); // [udata, reftable]
+ lua_setfenv(L, -2); // [udata]
+}
+
+static TSTree *tree_check(lua_State *L)
+{
+ TSTree **ud = luaL_checkudata(L, 1, "treesitter_tree");
+ return *ud;
+}
+
+static int tree_gc(lua_State *L)
+{
+ TSTree *tree = tree_check(L);
+ if (!tree) {
+ return 0;
+ }
+
+ ts_tree_delete(tree);
+ return 0;
+}
+
+static int tree_tostring(lua_State *L)
+{
+ lua_pushstring(L, "<tree>");
+ return 1;
+}
+
+static int tree_root(lua_State *L)
+{
+ TSTree *tree = tree_check(L);
+ if (!tree) {
+ return 0;
+ }
+ TSNode root = ts_tree_root_node(tree);
+ push_node(L, root);
+ return 1;
+}
+
+// Node methods
+
+/// push node interface on lua stack
+///
+/// top of stack must either be the tree this node belongs to or another node
+/// of the same tree! This value is not popped. Can only be called inside a
+/// cfunction with the tslua environment.
+static void push_node(lua_State *L, TSNode node)
+{
+ if (ts_node_is_null(node)) {
+ lua_pushnil(L); // [src, nil]
+ return;
+ }
+ TSNode *ud = lua_newuserdata(L, sizeof(TSNode)); // [src, udata]
+ *ud = node;
+ lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_node"); // [src, udata, meta]
+ lua_setmetatable(L, -2); // [src, udata]
+ lua_getfenv(L, -2); // [src, udata, reftable]
+ lua_setfenv(L, -2); // [src, udata]
+}
+
+static bool node_check(lua_State *L, TSNode *res)
+{
+ TSNode *ud = luaL_checkudata(L, 1, "treesitter_node");
+ if (ud) {
+ *res = *ud;
+ return true;
+ }
+ return false;
+}
+
+
+static int node_tostring(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, &node)) {
+ return 0;
+ }
+ lua_pushstring(L, "<node ");
+ lua_pushstring(L, ts_node_type(node));
+ lua_pushstring(L, ">");
+ lua_concat(L, 3);
+ return 1;
+}
+
+static int node_eq(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, &node)) {
+ return 0;
+ }
+ // This should only be called if both x and y in "x == y" has the
+ // treesitter_node metatable. So it is ok to error out otherwise.
+ TSNode *ud = luaL_checkudata(L, 2, "treesitter_node");
+ if (!ud) {
+ return 0;
+ }
+ TSNode node2 = *ud;
+ lua_pushboolean(L, ts_node_eq(node, node2));
+ return 1;
+}
+
+static int node_range(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, &node)) {
+ return 0;
+ }
+ TSPoint start = ts_node_start_point(node);
+ TSPoint end = ts_node_end_point(node);
+ lua_pushnumber(L, start.row);
+ lua_pushnumber(L, start.column);
+ lua_pushnumber(L, end.row);
+ lua_pushnumber(L, end.column);
+ return 4;
+}
+
+static int node_start(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, &node)) {
+ return 0;
+ }
+ TSPoint start = ts_node_start_point(node);
+ uint32_t start_byte = ts_node_start_byte(node);
+ lua_pushnumber(L, start.row);
+ lua_pushnumber(L, start.column);
+ lua_pushnumber(L, start_byte);
+ return 3;
+}
+
+static int node_end(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, &node)) {
+ return 0;
+ }
+ TSPoint end = ts_node_end_point(node);
+ uint32_t end_byte = ts_node_end_byte(node);
+ lua_pushnumber(L, end.row);
+ lua_pushnumber(L, end.column);
+ lua_pushnumber(L, end_byte);
+ return 3;
+}
+
+static int node_child_count(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, &node)) {
+ return 0;
+ }
+ uint32_t count = ts_node_child_count(node);
+ lua_pushnumber(L, count);
+ return 1;
+}
+
+static int node_named_child_count(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, &node)) {
+ return 0;
+ }
+ uint32_t count = ts_node_named_child_count(node);
+ lua_pushnumber(L, count);
+ return 1;
+}
+
+static int node_type(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, &node)) {
+ return 0;
+ }
+ lua_pushstring(L, ts_node_type(node));
+ return 1;
+}
+
+static int node_symbol(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, &node)) {
+ return 0;
+ }
+ TSSymbol symbol = ts_node_symbol(node);
+ lua_pushnumber(L, symbol);
+ return 1;
+}
+
+static int node_named(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, &node)) {
+ return 0;
+ }
+ lua_pushboolean(L, ts_node_is_named(node));
+ return 1;
+}
+
+static int node_sexpr(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, &node)) {
+ return 0;
+ }
+ char *allocated = ts_node_string(node);
+ lua_pushstring(L, allocated);
+ xfree(allocated);
+ return 1;
+}
+
+static int node_missing(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, &node)) {
+ return 0;
+ }
+ lua_pushboolean(L, ts_node_is_missing(node));
+ return 1;
+}
+
+static int node_has_error(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, &node)) {
+ return 0;
+ }
+ lua_pushboolean(L, ts_node_has_error(node));
+ return 1;
+}
+
+static int node_child(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, &node)) {
+ return 0;
+ }
+ long num = lua_tointeger(L, 2);
+ TSNode child = ts_node_child(node, (uint32_t)num);
+
+ lua_pushvalue(L, 1);
+ push_node(L, child);
+ return 1;
+}
+
+static int node_named_child(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, &node)) {
+ return 0;
+ }
+ long num = lua_tointeger(L, 2);
+ TSNode child = ts_node_named_child(node, (uint32_t)num);
+
+ lua_pushvalue(L, 1);
+ push_node(L, child);
+ return 1;
+}
+
+static int node_descendant_for_range(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, &node)) {
+ return 0;
+ }
+ TSPoint start = { (uint32_t)lua_tointeger(L, 2),
+ (uint32_t)lua_tointeger(L, 3) };
+ TSPoint end = { (uint32_t)lua_tointeger(L, 4),
+ (uint32_t)lua_tointeger(L, 5) };
+ TSNode child = ts_node_descendant_for_point_range(node, start, end);
+
+ lua_pushvalue(L, 1);
+ push_node(L, child);
+ return 1;
+}
+
+static int node_named_descendant_for_range(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, &node)) {
+ return 0;
+ }
+ TSPoint start = { (uint32_t)lua_tointeger(L, 2),
+ (uint32_t)lua_tointeger(L, 3) };
+ TSPoint end = { (uint32_t)lua_tointeger(L, 4),
+ (uint32_t)lua_tointeger(L, 5) };
+ TSNode child = ts_node_named_descendant_for_point_range(node, start, end);
+
+ lua_pushvalue(L, 1);
+ push_node(L, child);
+ return 1;
+}
+
+static int node_parent(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, &node)) {
+ return 0;
+ }
+ TSNode parent = ts_node_parent(node);
+ push_node(L, parent);
+ return 1;
+}
+
diff --git a/src/nvim/lua/treesitter.h b/src/nvim/lua/treesitter.h
new file mode 100644
index 0000000000..812166f67b
--- /dev/null
+++ b/src/nvim/lua/treesitter.h
@@ -0,0 +1,14 @@
+#ifndef NVIM_LUA_TREESITTER_H
+#define NVIM_LUA_TREESITTER_H
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include "tree_sitter/api.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "lua/treesitter.h.generated.h"
+#endif
+
+#endif // NVIM_LUA_TREESITTER_H
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
index a03e97490d..e7c5458102 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -33,35 +33,35 @@
-- - https://github.com/bakpakin/Fennel (pretty print, repl)
-- - https://github.com/howl-editor/howl/tree/master/lib/howl/util
+local vim = vim
+assert(vim)
-- Internal-only until comments in #8107 are addressed.
-- Returns:
-- {errcode}, {output}
-local function _system(cmd)
- local out = vim.api.nvim_call_function('system', { cmd })
- local err = vim.api.nvim_get_vvar('shell_error')
+function vim._system(cmd)
+ local out = vim.fn.system(cmd)
+ local err = vim.v.shell_error
return err, out
end
-- Gets process info from the `ps` command.
-- Used by nvim_get_proc() as a fallback.
-local function _os_proc_info(pid)
+function vim._os_proc_info(pid)
if pid == nil or pid <= 0 or type(pid) ~= 'number' then
error('invalid pid')
end
local cmd = { 'ps', '-p', pid, '-o', 'comm=', }
- local err, name = _system(cmd)
- if 1 == err and string.gsub(name, '%s*', '') == '' then
+ local err, name = vim._system(cmd)
+ if 1 == err and vim.trim(name) == '' then
return {} -- Process not found.
elseif 0 ~= err then
- local args_str = vim.api.nvim_call_function('string', { cmd })
- error('command failed: '..args_str)
+ error('command failed: '..vim.fn.string(cmd))
end
- local _, ppid = _system({ 'ps', '-p', pid, '-o', 'ppid=', })
+ local _, ppid = vim._system({ 'ps', '-p', pid, '-o', 'ppid=', })
-- Remove trailing whitespace.
- name = string.gsub(string.gsub(name, '%s+$', ''), '^.*/', '')
- ppid = string.gsub(ppid, '%s+$', '')
- ppid = tonumber(ppid) == nil and -1 or tonumber(ppid)
+ name = vim.trim(name):gsub('^.*/', '')
+ ppid = tonumber(ppid) or -1
return {
name = name,
pid = pid,
@@ -71,20 +71,19 @@ end
-- Gets process children from the `pgrep` command.
-- Used by nvim_get_proc_children() as a fallback.
-local function _os_proc_children(ppid)
+function vim._os_proc_children(ppid)
if ppid == nil or ppid <= 0 or type(ppid) ~= 'number' then
error('invalid ppid')
end
local cmd = { 'pgrep', '-P', ppid, }
- local err, rv = _system(cmd)
- if 1 == err and string.gsub(rv, '%s*', '') == '' then
+ local err, rv = vim._system(cmd)
+ if 1 == err and vim.trim(rv) == '' then
return {} -- Process not found.
elseif 0 ~= err then
- local args_str = vim.api.nvim_call_function('string', { cmd })
- error('command failed: '..args_str)
+ error('command failed: '..vim.fn.string(cmd))
end
local children = {}
- for s in string.gmatch(rv, '%S+') do
+ for s in rv:gmatch('%S+') do
local i = tonumber(s)
if i ~= nil then
table.insert(children, i)
@@ -98,7 +97,7 @@ end
-- Last inserted paths. Used to clear out items from package.[c]path when they
-- are no longer in &runtimepath.
local last_nvim_paths = {}
-local function _update_package_paths()
+function vim._update_package_paths()
local cur_nvim_paths = {}
local rtps = vim.api.nvim_list_runtime_paths()
local sep = package.config:sub(1, 1)
@@ -162,22 +161,35 @@ local function inspect(object, options) -- luacheck: no unused
error(object, options) -- Stub for gen_vimdoc.py
end
---- Paste handler, invoked by |nvim_paste()| when a conforming UI
---- (such as the |TUI|) pastes text into the editor.
----
---@see |paste|
----
---@param lines |readfile()|-style list of lines to paste. |channel-lines|
---@param phase -1: "non-streaming" paste: the call contains all lines.
---- If paste is "streamed", `phase` indicates the stream state:
---- - 1: starts the paste (exactly once)
---- - 2: continues the paste (zero or more times)
---- - 3: ends the paste (exactly once)
---@returns false if client should cancel the paste.
-local function paste(lines, phase) end -- luacheck: no unused
-paste = (function()
+do
local tdots, tick, got_line1 = 0, 0, false
- return function(lines, phase)
+
+ --- Paste handler, invoked by |nvim_paste()| when a conforming UI
+ --- (such as the |TUI|) pastes text into the editor.
+ ---
+ --- Example: To remove ANSI color codes when pasting:
+ --- <pre>
+ --- vim.paste = (function(overridden)
+ --- return function(lines, phase)
+ --- for i,line in ipairs(lines) do
+ --- -- Scrub ANSI color codes from paste input.
+ --- lines[i] = line:gsub('\27%[[0-9;mK]+', '')
+ --- end
+ --- overridden(lines, phase)
+ --- end
+ --- end)(vim.paste)
+ --- </pre>
+ ---
+ --@see |paste|
+ ---
+ --@param lines |readfile()|-style list of lines to paste. |channel-lines|
+ --@param phase -1: "non-streaming" paste: the call contains all lines.
+ --- If paste is "streamed", `phase` indicates the stream state:
+ --- - 1: starts the paste (exactly once)
+ --- - 2: continues the paste (zero or more times)
+ --- - 3: ends the paste (exactly once)
+ --@returns false if client should cancel the paste.
+ function vim.paste(lines, phase)
local call = vim.api.nvim_call_function
local now = vim.loop.now()
local mode = call('mode', {}):sub(1,1)
@@ -189,11 +201,14 @@ paste = (function()
if mode == 'c' and not got_line1 then -- cmdline-mode: paste only 1 line.
got_line1 = (#lines > 1)
vim.api.nvim_set_option('paste', true) -- For nvim_input().
- local line1, _ = string.gsub(lines[1], '[\r\n\012\027]', ' ') -- Scrub.
+ local line1 = lines[1]:gsub('<', '<lt>'):gsub('[\r\n\012\027]', ' ') -- Scrub.
vim.api.nvim_input(line1)
vim.api.nvim_set_option('paste', false)
- elseif mode ~= 'c' then -- Else: discard remaining cmdline-mode chunks.
- if phase < 2 and mode ~= 'i' and mode ~= 'R' and mode ~= 't' then
+ elseif mode ~= 'c' then
+ if phase < 2 and mode:find('^[vV\22sS\19]') then
+ vim.api.nvim_command([[exe "normal! \<Del>"]])
+ vim.api.nvim_put(lines, 'c', false, true)
+ elseif phase < 2 and not mode:find('^[iRt]') then
vim.api.nvim_put(lines, 'c', true, true)
-- XXX: Normal-mode: workaround bad cursor-placement after first chunk.
vim.api.nvim_command('normal! a')
@@ -214,42 +229,157 @@ paste = (function()
end
return true -- Paste will not continue if not returning `true`.
end
-end)()
+end
--- Defers callback `cb` until the Nvim API is safe to call.
---
---@see |lua-loop-callbacks|
---@see |vim.schedule()|
---@see |vim.in_fast_event()|
-local function schedule_wrap(cb)
+function vim.schedule_wrap(cb)
return (function (...)
local args = {...}
vim.schedule(function() cb(unpack(args)) end)
end)
end
+-- vim.fn.{func}(...)
+vim.fn = setmetatable({}, {
+ __index = function(t, key)
+ local function _fn(...)
+ return vim.call(key, ...)
+ end
+ t[key] = _fn
+ return _fn
+ end
+})
+
+-- These are for loading runtime modules lazily since they aren't available in
+-- the nvim binary as specified in executor.c
local function __index(t, key)
if key == 'inspect' then
t.inspect = require('vim.inspect')
return t.inspect
- elseif require('vim.shared')[key] ~= nil then
- -- Expose all `vim.shared` functions on the `vim` module.
- t[key] = require('vim.shared')[key]
+ elseif key == 'treesitter' then
+ t.treesitter = require('vim.treesitter')
+ return t.treesitter
+ elseif require('vim.uri')[key] ~= nil then
+ -- Expose all `vim.uri` functions on the `vim` module.
+ t[key] = require('vim.uri')[key]
return t[key]
+ elseif key == 'lsp' then
+ t.lsp = require('vim.lsp')
+ return t.lsp
end
end
-local module = {
- _update_package_paths = _update_package_paths,
- _os_proc_children = _os_proc_children,
- _os_proc_info = _os_proc_info,
- _system = _system,
- paste = paste,
- schedule_wrap = schedule_wrap,
-}
-
-setmetatable(module, {
+setmetatable(vim, {
__index = __index
})
+-- An easier alias for commands.
+vim.cmd = vim.api.nvim_command
+
+-- These are the vim.env/v/g/o/bo/wo variable magic accessors.
+do
+ local a = vim.api
+ local validate = vim.validate
+ local function make_meta_accessor(get, set, del)
+ validate {
+ get = {get, 'f'};
+ set = {set, 'f'};
+ del = {del, 'f', true};
+ }
+ local mt = {}
+ if del then
+ function mt:__newindex(k, v)
+ if v == nil then
+ return del(k)
+ end
+ return set(k, v)
+ end
+ else
+ function mt:__newindex(k, v)
+ return set(k, v)
+ end
+ end
+ function mt:__index(k)
+ return get(k)
+ end
+ return setmetatable({}, mt)
+ end
+ local function pcall_ret(status, ...)
+ if status then return ... end
+ end
+ local function nil_wrap(fn)
+ return function(...)
+ return pcall_ret(pcall(fn, ...))
+ end
+ end
+ vim.g = make_meta_accessor(nil_wrap(a.nvim_get_var), a.nvim_set_var, a.nvim_del_var)
+ vim.v = make_meta_accessor(nil_wrap(a.nvim_get_vvar), a.nvim_set_vvar)
+ vim.o = make_meta_accessor(a.nvim_get_option, a.nvim_set_option)
+ local function getenv(k)
+ local v = vim.fn.getenv(k)
+ if v == vim.NIL then
+ return nil
+ end
+ return v
+ end
+ vim.env = make_meta_accessor(getenv, vim.fn.setenv)
+ -- TODO(ashkan) if/when these are available from an API, generate them
+ -- instead of hardcoding.
+ local window_options = {
+ arab = true; arabic = true; breakindent = true; breakindentopt = true;
+ bri = true; briopt = true; cc = true; cocu = true;
+ cole = true; colorcolumn = true; concealcursor = true; conceallevel = true;
+ crb = true; cuc = true; cul = true; cursorbind = true;
+ cursorcolumn = true; cursorline = true; diff = true; fcs = true;
+ fdc = true; fde = true; fdi = true; fdl = true;
+ fdm = true; fdn = true; fdt = true; fen = true;
+ fillchars = true; fml = true; fmr = true; foldcolumn = true;
+ foldenable = true; foldexpr = true; foldignore = true; foldlevel = true;
+ foldmarker = true; foldmethod = true; foldminlines = true; foldnestmax = true;
+ foldtext = true; lbr = true; lcs = true; linebreak = true;
+ list = true; listchars = true; nu = true; number = true;
+ numberwidth = true; nuw = true; previewwindow = true; pvw = true;
+ relativenumber = true; rightleft = true; rightleftcmd = true; rl = true;
+ rlc = true; rnu = true; scb = true; scl = true;
+ scr = true; scroll = true; scrollbind = true; signcolumn = true;
+ spell = true; statusline = true; stl = true; wfh = true;
+ wfw = true; winbl = true; winblend = true; winfixheight = true;
+ winfixwidth = true; winhighlight = true; winhl = true; wrap = true;
+ }
+ local function new_buf_opt_accessor(bufnr)
+ local function get(k)
+ if window_options[k] then
+ return a.nvim_err_writeln(k.." is a window option, not a buffer option")
+ end
+ if bufnr == nil and type(k) == "number" then
+ return new_buf_opt_accessor(k)
+ end
+ return a.nvim_buf_get_option(bufnr or 0, k)
+ end
+ local function set(k, v)
+ if window_options[k] then
+ return a.nvim_err_writeln(k.." is a window option, not a buffer option")
+ end
+ return a.nvim_buf_set_option(bufnr or 0, k, v)
+ end
+ return make_meta_accessor(get, set)
+ end
+ vim.bo = new_buf_opt_accessor(nil)
+ local function new_win_opt_accessor(winnr)
+ local function get(k)
+ if winnr == nil and type(k) == "number" then
+ return new_win_opt_accessor(k)
+ end
+ return a.nvim_win_get_option(winnr or 0, k)
+ end
+ local function set(k, v) return a.nvim_win_set_option(winnr or 0, k, v) end
+ return make_meta_accessor(get, set)
+ end
+ vim.wo = new_win_opt_accessor(nil)
+end
+
return module
diff --git a/src/nvim/macros.h b/src/nvim/macros.h
index d11507fa6a..f6c8c0a4a0 100644
--- a/src/nvim/macros.h
+++ b/src/nvim/macros.h
@@ -110,8 +110,6 @@
// MB_COPY_CHAR(f, t): copy one char from "f" to "t" and advance the pointers.
// PTR2CHAR(): get character from pointer.
-// Get the length of the character p points to, including composing chars.
-# define MB_PTR2LEN(p) mb_ptr2len(p)
// Advance multi-byte pointer, skip over composing chars.
# define MB_PTR_ADV(p) (p += mb_ptr2len((char_u *)p))
// Advance multi-byte pointer, do not skip over composing chars.
diff --git a/src/nvim/main.c b/src/nvim/main.c
index be1f08bb46..c7011f4f4e 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -27,6 +27,7 @@
#include "nvim/highlight.h"
#include "nvim/iconv.h"
#include "nvim/if_cscope.h"
+#include "nvim/lua/executor.h"
#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif
@@ -143,7 +144,6 @@ static const char *err_extra_cmd =
void event_init(void)
{
- log_init();
loop_init(&main_loop, NULL);
resize_events = multiqueue_new_child(main_loop.events);
@@ -219,6 +219,7 @@ void early_init(void)
// First find out the home directory, needed to expand "~" in options.
init_homedir(); // find real value of $HOME
set_init_1();
+ log_init();
TIME_MSG("inits 1");
set_lang_var(); // set v:lang and v:ctype
@@ -957,6 +958,7 @@ static void command_line_scan(mparm_T *parmp)
case 'r': // "-r" recovery mode
case 'L': { // "-L" recovery mode
recoverymode = 1;
+ headless_mode = true;
break;
}
case 's': {
@@ -1460,12 +1462,13 @@ static void create_windows(mparm_T *parmp)
} else
parmp->window_count = 1;
- if (recoverymode) { /* do recover */
- msg_scroll = TRUE; /* scroll message up */
- ml_recover();
- if (curbuf->b_ml.ml_mfp == NULL) /* failed */
+ if (recoverymode) { // do recover
+ msg_scroll = true; // scroll message up
+ ml_recover(true);
+ if (curbuf->b_ml.ml_mfp == NULL) { // failed
getout(1);
- do_modelines(0); /* do modelines */
+ }
+ do_modelines(0); // do modelines
} else {
// Open a buffer for windows that don't have one yet.
// Commands in the vimrc might have loaded a file or split the window.
@@ -1513,7 +1516,7 @@ static void create_windows(mparm_T *parmp)
/* We can't close the window, it would disturb what
* happens next. Clear the file name and set the arg
* index to -1 to delete it later. */
- setfname(curbuf, NULL, NULL, FALSE);
+ setfname(curbuf, NULL, NULL, false);
curwin->w_arg_idx = -1;
swap_exists_action = SEA_NONE;
} else
@@ -1778,7 +1781,8 @@ static bool do_user_initialization(void)
if (do_source(user_vimrc, true, DOSO_VIMRC) != FAIL) {
do_exrc = p_exrc;
if (do_exrc) {
- do_exrc = (path_full_compare((char_u *)VIMRC_FILE, user_vimrc, false)
+ do_exrc = (path_full_compare((char_u *)VIMRC_FILE, user_vimrc,
+ false, true)
!= kEqualFiles);
}
xfree(user_vimrc);
@@ -1805,7 +1809,7 @@ static bool do_user_initialization(void)
do_exrc = p_exrc;
if (do_exrc) {
do_exrc = (path_full_compare((char_u *)VIMRC_FILE, (char_u *)vimrc,
- false) != kEqualFiles);
+ false, true) != kEqualFiles);
}
xfree(vimrc);
xfree(config_dirs);
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index e103d3cb55..93bc497cf0 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -178,6 +178,16 @@ void setpcmark(void)
curwin->w_pcmark.lnum = 1;
}
+ if (jop_flags & JOP_STACK) {
+ // If we're somewhere in the middle of the jumplist discard everything
+ // after the current index.
+ if (curwin->w_jumplistidx < curwin->w_jumplistlen - 1) {
+ // Discard the rest of the jumplist by cutting the length down to
+ // contain nothing beyond the current index.
+ curwin->w_jumplistlen = curwin->w_jumplistidx + 1;
+ }
+ }
+
/* If jumplist is full: remove oldest entry */
if (++curwin->w_jumplistlen > JUMPLISTSIZE) {
curwin->w_jumplistlen = JUMPLISTSIZE;
@@ -296,17 +306,17 @@ pos_T *movechangelist(int count)
* - NULL if there is no mark called 'c'.
* - -1 if mark is in other file and jumped there (only if changefile is TRUE)
*/
-pos_T *getmark_buf(buf_T *buf, int c, int changefile)
+pos_T *getmark_buf(buf_T *buf, int c, bool changefile)
{
return getmark_buf_fnum(buf, c, changefile, NULL);
}
-pos_T *getmark(int c, int changefile)
+pos_T *getmark(int c, bool changefile)
{
return getmark_buf_fnum(curbuf, c, changefile, NULL);
}
-pos_T *getmark_buf_fnum(buf_T *buf, int c, int changefile, int *fnum)
+pos_T *getmark_buf_fnum(buf_T *buf, int c, bool changefile, int *fnum)
{
pos_T *posp;
pos_T *startp, *endp;
@@ -616,7 +626,7 @@ static char_u *mark_line(pos_T *mp, int lead_len)
/*
* print the marks
*/
-void do_marks(exarg_T *eap)
+void ex_marks(exarg_T *eap)
{
char_u *arg = eap->arg;
int i;
@@ -905,9 +915,10 @@ void mark_adjust(linenr_T line1,
linenr_T line2,
long amount,
long amount_after,
- bool end_temp)
+ bool end_temp,
+ ExtmarkOp op)
{
- mark_adjust_internal(line1, line2, amount, amount_after, true, end_temp);
+ mark_adjust_internal(line1, line2, amount, amount_after, true, end_temp, op);
}
// mark_adjust_nofold() does the same as mark_adjust() but without adjusting
@@ -916,14 +927,16 @@ void mark_adjust(linenr_T line1,
// calling foldMarkAdjust() with arguments line1, line2, amount, amount_after,
// for an example of why this may be necessary, see do_move().
void mark_adjust_nofold(linenr_T line1, linenr_T line2, long amount,
- long amount_after, bool end_temp)
+ long amount_after, bool end_temp,
+ ExtmarkOp op)
{
- mark_adjust_internal(line1, line2, amount, amount_after, false, end_temp);
+ mark_adjust_internal(line1, line2, amount, amount_after, false, end_temp, op);
}
static void mark_adjust_internal(linenr_T line1, linenr_T line2,
long amount, long amount_after,
- bool adjust_folds, bool end_temp)
+ bool adjust_folds, bool end_temp,
+ ExtmarkOp op)
{
int i;
int fnum = curbuf->b_fnum;
@@ -979,6 +992,9 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2,
sign_mark_adjust(line1, line2, amount, amount_after);
bufhl_mark_adjust(curbuf, line1, line2, amount, amount_after, end_temp);
+ if (op != kExtmarkNOOP) {
+ extmark_adjust(curbuf, line1, line2, amount, amount_after, op, end_temp);
+ }
}
/* previous context mark */
@@ -1090,7 +1106,7 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2,
// cursor is inside them.
void mark_col_adjust(
linenr_T lnum, colnr_T mincol, long lnum_amount, long col_amount,
- int spaces_removed)
+ int spaces_removed, ExtmarkOp op)
{
int i;
int fnum = curbuf->b_fnum;
@@ -1110,6 +1126,13 @@ void mark_col_adjust(
col_adjust(&(namedfm[i].fmark.mark));
}
+ // Extmarks
+ if (op != kExtmarkNOOP) {
+ // TODO(timeyyy): consider spaces_removed? (behave like a delete)
+ extmark_col_adjust(curbuf, lnum, mincol, lnum_amount, col_amount,
+ kExtmarkUndo);
+ }
+
/* last Insert position */
col_adjust(&(curbuf->b_last_insert.mark));
@@ -1191,7 +1214,20 @@ void cleanup_jumplist(win_T *wp, bool checktail)
break;
}
}
- if (i >= wp->w_jumplistlen) { // no duplicate
+ bool mustfree;
+ if (i >= wp->w_jumplistlen) { // not duplicate
+ mustfree = false;
+ } else if (i > from + 1) { // non-adjacent duplicate
+ // When the jump options include "stack", duplicates are only removed from
+ // the jumplist when they are adjacent.
+ mustfree = !(jop_flags & JOP_STACK);
+ } else { // adjacent duplicate
+ mustfree = true;
+ }
+
+ if (mustfree) {
+ xfree(wp->w_jumplist[from].fname);
+ } else {
if (to != from) {
// Not using wp->w_jumplist[to++] = wp->w_jumplist[from] because
// this way valgrind complains about overlapping source and destination
@@ -1199,8 +1235,6 @@ void cleanup_jumplist(win_T *wp, bool checktail)
wp->w_jumplist[to] = wp->w_jumplist[from];
}
to++;
- } else {
- xfree(wp->w_jumplist[from].fname);
}
}
if (wp->w_jumplistidx == wp->w_jumplistlen) {
diff --git a/src/nvim/mark_extended.c b/src/nvim/mark_extended.c
new file mode 100644
index 0000000000..17776d438a
--- /dev/null
+++ b/src/nvim/mark_extended.c
@@ -0,0 +1,1139 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+// Implements extended marks for plugins. Each mark exists in a btree of
+// lines containing btrees of columns.
+//
+// The btree provides efficient range lookups.
+// A map of pointers to the marks is used for fast lookup by mark id.
+//
+// Marks are moved by calls to: extmark_col_adjust, extmark_adjust, or
+// extmark_col_adjust_delete which are based on col_adjust and mark_adjust from
+// mark.c
+//
+// Undo/Redo of marks is implemented by storing the call arguments to
+// extmark_col_adjust or extmark_adjust. The list of arguments
+// is applied in extmark_apply_undo. The only case where we have to
+// copy extmarks is for the area being effected by a delete.
+//
+// Marks live in namespaces that allow plugins/users to segregate marks
+// from other users.
+//
+// For possible ideas for efficency improvements see:
+// http://blog.atom.io/2015/06/16/optimizing-an-important-atom-primitive.html
+// TODO(bfredl): These ideas could be used for an enhanced btree, which
+// wouldn't need separate line and column layers.
+// Other implementations exist in gtk and tk toolkits.
+//
+// Deleting marks only happens when explicitly calling extmark_del, deleteing
+// over a range of marks will only move the marks. Deleting on a mark will
+// leave it in same position unless it is on the EOL of a line.
+
+#include <assert.h>
+#include "nvim/vim.h"
+#include "charset.h"
+#include "nvim/mark_extended.h"
+#include "nvim/memline.h"
+#include "nvim/pos.h"
+#include "nvim/globals.h"
+#include "nvim/map.h"
+#include "nvim/lib/kbtree.h"
+#include "nvim/undo.h"
+#include "nvim/buffer.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "mark_extended.c.generated.h"
+#endif
+
+
+/// Create or update an extmark
+///
+/// must not be used during iteration!
+/// @returns whether a new mark was created
+int extmark_set(buf_T *buf, uint64_t ns, uint64_t id,
+ linenr_T lnum, colnr_T col, ExtmarkOp op)
+{
+ Extmark *extmark = extmark_from_id(buf, ns, id);
+ if (!extmark) {
+ extmark_create(buf, ns, id, lnum, col, op);
+ return true;
+ } else {
+ ExtmarkLine *extmarkline = extmark->line;
+ extmark_update(extmark, buf, ns, id, lnum, col, op, NULL);
+ if (kb_size(&extmarkline->items) == 0) {
+ kb_del(extmarklines, &buf->b_extlines, extmarkline);
+ extmarkline_free(extmarkline);
+ }
+ return false;
+ }
+}
+
+// Remove an extmark
+// Returns 0 on missing id
+int extmark_del(buf_T *buf, uint64_t ns, uint64_t id, ExtmarkOp op)
+{
+ Extmark *extmark = extmark_from_id(buf, ns, id);
+ if (!extmark) {
+ return 0;
+ }
+ return extmark_delete(extmark, buf, ns, id, op);
+}
+
+// Free extmarks in a ns between lines
+// if ns = 0, it means clear all namespaces
+void extmark_clear(buf_T *buf, uint64_t ns,
+ linenr_T l_lnum, linenr_T u_lnum, ExtmarkOp undo)
+{
+ if (!buf->b_extmark_ns) {
+ return;
+ }
+
+ bool marks_cleared = false;
+ if (undo == kExtmarkUndo) {
+ // Copy marks that would be effected by clear
+ u_extmark_copy(buf, ns, l_lnum, 0, u_lnum, MAXCOL);
+ }
+
+ bool all_ns = ns == 0 ? true : false;
+ ExtmarkNs *ns_obj;
+ if (!all_ns) {
+ ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns);
+ if (!ns_obj) {
+ // nothing to do
+ return;
+ }
+ }
+
+ FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, {
+ FOR_ALL_EXTMARKS_IN_LINE(extmarkline->items, 0, MAXCOL, {
+ if (extmark->ns_id == ns || all_ns) {
+ marks_cleared = true;
+ if (all_ns) {
+ ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, extmark->ns_id);
+ } else {
+ ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns);
+ }
+ pmap_del(uint64_t)(ns_obj->map, extmark->mark_id);
+ kb_del_itr(markitems, &extmarkline->items, &mitr);
+ }
+ });
+ if (kb_size(&extmarkline->items) == 0) {
+ kb_del_itr(extmarklines, &buf->b_extlines, &itr);
+ extmarkline_free(extmarkline);
+ }
+ });
+
+ // Record the undo for the actual move
+ if (marks_cleared && undo == kExtmarkUndo) {
+ u_extmark_clear(buf, ns, l_lnum, u_lnum);
+ }
+}
+
+// Returns the position of marks between a range,
+// marks found at the start or end index will be included,
+// if upper_lnum or upper_col are negative the buffer
+// will be searched to the start, or end
+// dir can be set to control the order of the array
+// amount = amount of marks to find or -1 for all
+ExtmarkArray extmark_get(buf_T *buf, uint64_t ns,
+ linenr_T l_lnum, colnr_T l_col,
+ linenr_T u_lnum, colnr_T u_col,
+ int64_t amount, bool reverse)
+{
+ ExtmarkArray array = KV_INITIAL_VALUE;
+ // Find all the marks
+ if (!reverse) {
+ FOR_ALL_EXTMARKS(buf, ns, l_lnum, l_col, u_lnum, u_col, {
+ if (extmark->ns_id == ns) {
+ kv_push(array, extmark);
+ if (kv_size(array) == (size_t)amount) {
+ return array;
+ }
+ }
+ })
+ } else {
+ FOR_ALL_EXTMARKS_PREV(buf, ns, l_lnum, l_col, u_lnum, u_col, {
+ if (extmark->ns_id == ns) {
+ kv_push(array, extmark);
+ if (kv_size(array) == (size_t)amount) {
+ return array;
+ }
+ }
+ })
+ }
+ return array;
+}
+
+static void extmark_create(buf_T *buf, uint64_t ns, uint64_t id,
+ linenr_T lnum, colnr_T col, ExtmarkOp op)
+{
+ if (!buf->b_extmark_ns) {
+ buf->b_extmark_ns = pmap_new(uint64_t)();
+ }
+ ExtmarkNs *ns_obj = NULL;
+ ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns);
+ // Initialize a new namespace for this buffer
+ if (!ns_obj) {
+ ns_obj = xmalloc(sizeof(ExtmarkNs));
+ ns_obj->map = pmap_new(uint64_t)();
+ pmap_put(uint64_t)(buf->b_extmark_ns, ns, ns_obj);
+ }
+
+ // Create or get a line
+ ExtmarkLine *extmarkline = extmarkline_ref(buf, lnum, true);
+ // Create and put mark on the line
+ extmark_put(col, id, extmarkline, ns);
+
+ // Marks do not have stable address so we have to look them up
+ // by using the line instead of the mark
+ pmap_put(uint64_t)(ns_obj->map, id, extmarkline);
+ if (op != kExtmarkNoUndo) {
+ u_extmark_set(buf, ns, id, lnum, col, kExtmarkSet);
+ }
+
+ // Set a free id so extmark_free_id_get works
+ extmark_free_id_set(ns_obj, id);
+}
+
+// update the position of an extmark
+// to update while iterating pass the markitems itr
+static void extmark_update(Extmark *extmark, buf_T *buf,
+ uint64_t ns, uint64_t id,
+ linenr_T lnum, colnr_T col,
+ ExtmarkOp op, kbitr_t(markitems) *mitr)
+{
+ assert(op != kExtmarkNOOP);
+ if (op != kExtmarkNoUndo) {
+ u_extmark_update(buf, ns, id, extmark->line->lnum, extmark->col,
+ lnum, col);
+ }
+ ExtmarkLine *old_line = extmark->line;
+ // Move the mark to a new line and update column
+ if (old_line->lnum != lnum) {
+ ExtmarkLine *ref_line = extmarkline_ref(buf, lnum, true);
+ extmark_put(col, id, ref_line, ns);
+ // Update the hashmap
+ ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns);
+ pmap_put(uint64_t)(ns_obj->map, id, ref_line);
+ // Delete old mark
+ if (mitr != NULL) {
+ kb_del_itr(markitems, &(old_line->items), mitr);
+ } else {
+ kb_del(markitems, &old_line->items, *extmark);
+ }
+ // Just update the column
+ } else {
+ if (mitr != NULL) {
+ // The btree stays organized during iteration with kbitr_t
+ extmark->col = col;
+ } else {
+ // Keep the btree in order
+ kb_del(markitems, &old_line->items, *extmark);
+ extmark_put(col, id, old_line, ns);
+ }
+ }
+}
+
+static int extmark_delete(Extmark *extmark,
+ buf_T *buf,
+ uint64_t ns,
+ uint64_t id,
+ ExtmarkOp op)
+{
+ if (op != kExtmarkNoUndo) {
+ u_extmark_set(buf, ns, id, extmark->line->lnum, extmark->col,
+ kExtmarkDel);
+ }
+
+ // Remove our key from the namespace
+ ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns);
+ pmap_del(uint64_t)(ns_obj->map, id);
+
+ // Remove the mark mark from the line
+ ExtmarkLine *extmarkline = extmark->line;
+ kb_del(markitems, &extmarkline->items, *extmark);
+ // Remove the line if there are no more marks in the line
+ if (kb_size(&extmarkline->items) == 0) {
+ kb_del(extmarklines, &buf->b_extlines, extmarkline);
+ extmarkline_free(extmarkline);
+ }
+ return true;
+}
+
+// Lookup an extmark by id
+Extmark *extmark_from_id(buf_T *buf, uint64_t ns, uint64_t id)
+{
+ if (!buf->b_extmark_ns) {
+ return NULL;
+ }
+ ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns);
+ if (!ns_obj || !kh_size(ns_obj->map->table)) {
+ return NULL;
+ }
+ ExtmarkLine *extmarkline = pmap_get(uint64_t)(ns_obj->map, id);
+ if (!extmarkline) {
+ return NULL;
+ }
+
+ FOR_ALL_EXTMARKS_IN_LINE(extmarkline->items, 0, MAXCOL, {
+ if (extmark->ns_id == ns
+ && extmark->mark_id == id) {
+ return extmark;
+ }
+ })
+ return NULL;
+}
+
+// Lookup an extmark by position
+Extmark *extmark_from_pos(buf_T *buf, uint64_t ns, linenr_T lnum, colnr_T col)
+{
+ if (!buf->b_extmark_ns) {
+ return NULL;
+ }
+ FOR_ALL_EXTMARKS(buf, ns, lnum, col, lnum, col, {
+ if (extmark->ns_id == ns) {
+ if (extmark->col == col) {
+ return extmark;
+ }
+ }
+ })
+ return NULL;
+}
+
+// Returns an available id in a namespace
+uint64_t extmark_free_id_get(buf_T *buf, uint64_t ns)
+{
+ if (!buf->b_extmark_ns) {
+ return 1;
+ }
+ ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns);
+ if (!ns_obj) {
+ return 1;
+ }
+ return ns_obj->free_id;
+}
+
+// Set the next free id in a namesapce
+static void extmark_free_id_set(ExtmarkNs *ns_obj, uint64_t id)
+{
+ // Simply Heurstic, the largest id + 1
+ ns_obj->free_id = id + 1;
+}
+
+// free extmarks from the buffer
+void extmark_free_all(buf_T *buf)
+{
+ if (!buf->b_extmark_ns) {
+ return;
+ }
+
+ uint64_t ns;
+ ExtmarkNs *ns_obj;
+
+ FOR_ALL_EXTMARKLINES(buf, 1, MAXLNUM, {
+ kb_del_itr(extmarklines, &buf->b_extlines, &itr);
+ extmarkline_free(extmarkline);
+ })
+
+ map_foreach(buf->b_extmark_ns, ns, ns_obj, {
+ (void)ns;
+ pmap_free(uint64_t)(ns_obj->map);
+ xfree(ns_obj);
+ });
+
+ pmap_free(uint64_t)(buf->b_extmark_ns);
+ buf->b_extmark_ns = NULL;
+
+ // k?_init called to set pointers to NULL
+ kb_destroy(extmarklines, (&buf->b_extlines));
+ kb_init(&buf->b_extlines);
+
+ kv_destroy(buf->b_extmark_move_space);
+ kv_init(buf->b_extmark_move_space);
+}
+
+
+// Save info for undo/redo of set marks
+static void u_extmark_set(buf_T *buf, uint64_t ns, uint64_t id,
+ linenr_T lnum, colnr_T col, UndoObjectType undo_type)
+{
+ u_header_T *uhp = u_force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ ExtmarkSet set;
+ set.ns_id = ns;
+ set.mark_id = id;
+ set.lnum = lnum;
+ set.col = col;
+
+ ExtmarkUndoObject undo = { .type = undo_type,
+ .data.set = set };
+
+ kv_push(uhp->uh_extmark, undo);
+}
+
+// Save info for undo/redo of deleted marks
+static void u_extmark_update(buf_T *buf, uint64_t ns, uint64_t id,
+ linenr_T old_lnum, colnr_T old_col,
+ linenr_T lnum, colnr_T col)
+{
+ u_header_T *uhp = u_force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ ExtmarkUpdate update;
+ update.ns_id = ns;
+ update.mark_id = id;
+ update.old_lnum = old_lnum;
+ update.old_col = old_col;
+ update.lnum = lnum;
+ update.col = col;
+
+ ExtmarkUndoObject undo = { .type = kExtmarkUpdate,
+ .data.update = update };
+ kv_push(uhp->uh_extmark, undo);
+}
+
+// Hueristic works only for when the user is typing in insert mode
+// - Instead of 1 undo object for each char inserted,
+// we create 1 undo objet for all text inserted before the user hits esc
+// Return True if we compacted else False
+static bool u_compact_col_adjust(buf_T *buf, linenr_T lnum, colnr_T mincol,
+ long lnum_amount, long col_amount)
+{
+ u_header_T *uhp = u_force_get_undo_header(buf);
+ if (!uhp) {
+ return false;
+ }
+
+ if (kv_size(uhp->uh_extmark) < 1) {
+ return false;
+ }
+ // Check the last action
+ ExtmarkUndoObject object = kv_last(uhp->uh_extmark);
+
+ if (object.type != kColAdjust) {
+ return false;
+ }
+ ColAdjust undo = object.data.col_adjust;
+ bool compactable = false;
+
+ if (!undo.lnum_amount && !lnum_amount) {
+ if (undo.lnum == lnum) {
+ if ((undo.mincol + undo.col_amount) >= mincol) {
+ compactable = true;
+ } } }
+
+ if (!compactable) {
+ return false;
+ }
+
+ undo.col_amount = undo.col_amount + col_amount;
+ ExtmarkUndoObject new_undo = { .type = kColAdjust,
+ .data.col_adjust = undo };
+ kv_last(uhp->uh_extmark) = new_undo;
+ return true;
+}
+
+// Save col_adjust info so we can undo/redo
+void u_extmark_col_adjust(buf_T *buf, linenr_T lnum, colnr_T mincol,
+ long lnum_amount, long col_amount)
+{
+ u_header_T *uhp = u_force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ if (!u_compact_col_adjust(buf, lnum, mincol, lnum_amount, col_amount)) {
+ ColAdjust col_adjust;
+ col_adjust.lnum = lnum;
+ col_adjust.mincol = mincol;
+ col_adjust.lnum_amount = lnum_amount;
+ col_adjust.col_amount = col_amount;
+
+ ExtmarkUndoObject undo = { .type = kColAdjust,
+ .data.col_adjust = col_adjust };
+
+ kv_push(uhp->uh_extmark, undo);
+ }
+}
+
+// Save col_adjust_delete info so we can undo/redo
+void u_extmark_col_adjust_delete(buf_T *buf, linenr_T lnum,
+ colnr_T mincol, colnr_T endcol, int eol)
+{
+ u_header_T *uhp = u_force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ ColAdjustDelete col_adjust_delete;
+ col_adjust_delete.lnum = lnum;
+ col_adjust_delete.mincol = mincol;
+ col_adjust_delete.endcol = endcol;
+ col_adjust_delete.eol = eol;
+
+ ExtmarkUndoObject undo = { .type = kColAdjustDelete,
+ .data.col_adjust_delete = col_adjust_delete };
+
+ kv_push(uhp->uh_extmark, undo);
+}
+
+// Save adjust info so we can undo/redo
+static void u_extmark_adjust(buf_T * buf, linenr_T line1, linenr_T line2,
+ long amount, long amount_after)
+{
+ u_header_T *uhp = u_force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ Adjust adjust;
+ adjust.line1 = line1;
+ adjust.line2 = line2;
+ adjust.amount = amount;
+ adjust.amount_after = amount_after;
+
+ ExtmarkUndoObject undo = { .type = kLineAdjust,
+ .data.adjust = adjust };
+
+ kv_push(uhp->uh_extmark, undo);
+}
+
+// save info to undo/redo a :move
+void u_extmark_move(buf_T *buf, linenr_T line1, linenr_T line2,
+ linenr_T last_line, linenr_T dest, linenr_T num_lines,
+ linenr_T extra)
+{
+ u_header_T *uhp = u_force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ AdjustMove move;
+ move.line1 = line1;
+ move.line2 = line2;
+ move.last_line = last_line;
+ move.dest = dest;
+ move.num_lines = num_lines;
+ move.extra = extra;
+
+ ExtmarkUndoObject undo = { .type = kAdjustMove,
+ .data.move = move };
+
+ kv_push(uhp->uh_extmark, undo);
+}
+
+// copy extmarks data between range, useful when we cannot simply reverse
+// the operation. This will do nothing on redo, enforces correct position when
+// undo.
+// if ns = 0, it means copy all namespaces
+void u_extmark_copy(buf_T *buf, uint64_t ns,
+ linenr_T l_lnum, colnr_T l_col,
+ linenr_T u_lnum, colnr_T u_col)
+{
+ u_header_T *uhp = u_force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ bool all_ns = ns == 0 ? true : false;
+
+ ExtmarkCopy copy;
+ ExtmarkUndoObject undo;
+ FOR_ALL_EXTMARKS(buf, 1, l_lnum, l_col, u_lnum, u_col, {
+ if (all_ns || extmark->ns_id == ns) {
+ copy.ns_id = extmark->ns_id;
+ copy.mark_id = extmark->mark_id;
+ copy.lnum = extmark->line->lnum;
+ copy.col = extmark->col;
+
+ undo.data.copy = copy;
+ undo.type = kExtmarkCopy;
+ kv_push(uhp->uh_extmark, undo);
+ }
+ });
+}
+
+void u_extmark_copy_place(buf_T *buf,
+ linenr_T l_lnum, colnr_T l_col,
+ linenr_T u_lnum, colnr_T u_col,
+ linenr_T p_lnum, colnr_T p_col)
+{
+ u_header_T *uhp = u_force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ ExtmarkCopyPlace copy_place;
+ copy_place.l_lnum = l_lnum;
+ copy_place.l_col = l_col;
+ copy_place.u_lnum = u_lnum;
+ copy_place.u_col = u_col;
+ copy_place.p_lnum = p_lnum;
+ copy_place.p_col = p_col;
+
+ ExtmarkUndoObject undo = { .type = kExtmarkCopyPlace,
+ .data.copy_place = copy_place };
+
+ kv_push(uhp->uh_extmark, undo);
+}
+
+// Save info for undo/redo of extmark_clear
+static void u_extmark_clear(buf_T *buf, uint64_t ns,
+ linenr_T l_lnum, linenr_T u_lnum)
+{
+ u_header_T *uhp = u_force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ ExtmarkClear clear;
+ clear.ns_id = ns;
+ clear.l_lnum = l_lnum;
+ clear.u_lnum = u_lnum;
+
+ ExtmarkUndoObject undo = { .type = kExtmarkClear,
+ .data.clear = clear };
+ kv_push(uhp->uh_extmark, undo);
+}
+
+// undo or redo an extmark operation
+void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo)
+{
+ linenr_T lnum;
+ colnr_T mincol;
+ long lnum_amount;
+ long col_amount;
+ linenr_T line1;
+ linenr_T line2;
+ long amount;
+ long amount_after;
+
+ // use extmark_col_adjust
+ if (undo_info.type == kColAdjust) {
+ // Undo
+ if (undo) {
+ lnum = (undo_info.data.col_adjust.lnum
+ + undo_info.data.col_adjust.lnum_amount);
+ lnum_amount = -undo_info.data.col_adjust.lnum_amount;
+ col_amount = -undo_info.data.col_adjust.col_amount;
+ mincol = (undo_info.data.col_adjust.mincol
+ + (colnr_T)undo_info.data.col_adjust.col_amount);
+ // Redo
+ } else {
+ lnum = undo_info.data.col_adjust.lnum;
+ col_amount = undo_info.data.col_adjust.col_amount;
+ lnum_amount = undo_info.data.col_adjust.lnum_amount;
+ mincol = undo_info.data.col_adjust.mincol;
+ }
+ extmark_col_adjust(curbuf,
+ lnum, mincol, lnum_amount, col_amount, kExtmarkNoUndo);
+ // use extmark_col_adjust_delete
+ } else if (undo_info.type == kColAdjustDelete) {
+ if (undo) {
+ mincol = undo_info.data.col_adjust_delete.mincol;
+ col_amount = (undo_info.data.col_adjust_delete.endcol
+ - undo_info.data.col_adjust_delete.mincol) + 1;
+ extmark_col_adjust(curbuf,
+ undo_info.data.col_adjust_delete.lnum,
+ mincol,
+ 0,
+ col_amount,
+ kExtmarkNoUndo);
+ // Redo
+ } else {
+ extmark_col_adjust_delete(curbuf,
+ undo_info.data.col_adjust_delete.lnum,
+ undo_info.data.col_adjust_delete.mincol,
+ undo_info.data.col_adjust_delete.endcol,
+ kExtmarkNoUndo,
+ undo_info.data.col_adjust_delete.eol);
+ }
+ // use extmark_adjust
+ } else if (undo_info.type == kLineAdjust) {
+ if (undo) {
+ // Undo - call signature type one - insert now
+ if (undo_info.data.adjust.amount == MAXLNUM) {
+ line1 = undo_info.data.adjust.line1;
+ line2 = MAXLNUM;
+ amount = -undo_info.data.adjust.amount_after;
+ amount_after = 0;
+ // Undo - call singature type two - delete now
+ } else if (undo_info.data.adjust.line2 == MAXLNUM) {
+ line1 = undo_info.data.adjust.line1;
+ line2 = undo_info.data.adjust.line2;
+ amount = -undo_info.data.adjust.amount;
+ amount_after = undo_info.data.adjust.amount_after;
+ // Undo - call signature three - move lines
+ } else {
+ line1 = (undo_info.data.adjust.line1
+ + undo_info.data.adjust.amount);
+ line2 = (undo_info.data.adjust.line2
+ + undo_info.data.adjust.amount);
+ amount = -undo_info.data.adjust.amount;
+ amount_after = -undo_info.data.adjust.amount_after;
+ }
+ // redo
+ } else {
+ line1 = undo_info.data.adjust.line1;
+ line2 = undo_info.data.adjust.line2;
+ amount = undo_info.data.adjust.amount;
+ amount_after = undo_info.data.adjust.amount_after;
+ }
+ extmark_adjust(curbuf,
+ line1, line2, amount, amount_after, kExtmarkNoUndo, false);
+ // kExtmarkCopy
+ } else if (undo_info.type == kExtmarkCopy) {
+ // Redo should be handled by kColAdjustDelete or kExtmarkCopyPlace
+ if (undo) {
+ extmark_set(curbuf,
+ undo_info.data.copy.ns_id,
+ undo_info.data.copy.mark_id,
+ undo_info.data.copy.lnum,
+ undo_info.data.copy.col,
+ kExtmarkNoUndo);
+ }
+ // uses extmark_copy_and_place
+ } else if (undo_info.type == kExtmarkCopyPlace) {
+ // Redo, undo is handle by kExtmarkCopy
+ if (!undo) {
+ extmark_copy_and_place(curbuf,
+ undo_info.data.copy_place.l_lnum,
+ undo_info.data.copy_place.l_col,
+ undo_info.data.copy_place.u_lnum,
+ undo_info.data.copy_place.u_col,
+ undo_info.data.copy_place.p_lnum,
+ undo_info.data.copy_place.p_col,
+ kExtmarkNoUndo, true, NULL);
+ }
+ // kExtmarkClear
+ } else if (undo_info.type == kExtmarkClear) {
+ // Redo, undo is handle by kExtmarkCopy
+ if (!undo) {
+ extmark_clear(curbuf,
+ undo_info.data.clear.ns_id,
+ undo_info.data.clear.l_lnum,
+ undo_info.data.clear.u_lnum,
+ kExtmarkNoUndo);
+ }
+ // kAdjustMove
+ } else if (undo_info.type == kAdjustMove) {
+ apply_undo_move(undo_info, undo);
+ // extmark_set
+ } else if (undo_info.type == kExtmarkSet) {
+ if (undo) {
+ extmark_del(curbuf,
+ undo_info.data.set.ns_id,
+ undo_info.data.set.mark_id,
+ kExtmarkNoUndo);
+ // Redo
+ } else {
+ extmark_set(curbuf,
+ undo_info.data.set.ns_id,
+ undo_info.data.set.mark_id,
+ undo_info.data.set.lnum,
+ undo_info.data.set.col,
+ kExtmarkNoUndo);
+ }
+ // extmark_set into update
+ } else if (undo_info.type == kExtmarkUpdate) {
+ if (undo) {
+ extmark_set(curbuf,
+ undo_info.data.update.ns_id,
+ undo_info.data.update.mark_id,
+ undo_info.data.update.old_lnum,
+ undo_info.data.update.old_col,
+ kExtmarkNoUndo);
+ // Redo
+ } else {
+ extmark_set(curbuf,
+ undo_info.data.update.ns_id,
+ undo_info.data.update.mark_id,
+ undo_info.data.update.lnum,
+ undo_info.data.update.col,
+ kExtmarkNoUndo);
+ }
+ // extmark_del
+ } else if (undo_info.type == kExtmarkDel) {
+ if (undo) {
+ extmark_set(curbuf,
+ undo_info.data.set.ns_id,
+ undo_info.data.set.mark_id,
+ undo_info.data.set.lnum,
+ undo_info.data.set.col,
+ kExtmarkNoUndo);
+ // Redo
+ } else {
+ extmark_del(curbuf,
+ undo_info.data.set.ns_id,
+ undo_info.data.set.mark_id,
+ kExtmarkNoUndo);
+ }
+ }
+}
+
+// undo/redo an kExtmarkMove operation
+static void apply_undo_move(ExtmarkUndoObject undo_info, bool undo)
+{
+ // 3 calls are required , see comment in function do_move (ex_cmds.c)
+ linenr_T line1 = undo_info.data.move.line1;
+ linenr_T line2 = undo_info.data.move.line2;
+ linenr_T last_line = undo_info.data.move.last_line;
+ linenr_T dest = undo_info.data.move.dest;
+ linenr_T num_lines = undo_info.data.move.num_lines;
+ linenr_T extra = undo_info.data.move.extra;
+
+ if (undo) {
+ if (dest >= line2) {
+ extmark_adjust(curbuf, dest - num_lines + 1, dest,
+ last_line - dest + num_lines - 1, 0L, kExtmarkNoUndo,
+ true);
+ extmark_adjust(curbuf, dest - line2, dest - line1,
+ dest - line2, 0L, kExtmarkNoUndo, false);
+ } else {
+ extmark_adjust(curbuf, line1-num_lines, line2-num_lines,
+ last_line - (line1-num_lines), 0L, kExtmarkNoUndo, true);
+ extmark_adjust(curbuf, (line1-num_lines) + 1, (line2-num_lines) + 1,
+ -num_lines, 0L, kExtmarkNoUndo, false);
+ }
+ extmark_adjust(curbuf, last_line, last_line + num_lines - 1,
+ line1 - last_line, 0L, kExtmarkNoUndo, true);
+ // redo
+ } else {
+ extmark_adjust(curbuf, line1, line2,
+ last_line - line2, 0L, kExtmarkNoUndo, true);
+ if (dest >= line2) {
+ extmark_adjust(curbuf, line2 + 1, dest,
+ -num_lines, 0L, kExtmarkNoUndo, false);
+ } else {
+ extmark_adjust(curbuf, dest + 1, line1 - 1,
+ num_lines, 0L, kExtmarkNoUndo, false);
+ }
+ extmark_adjust(curbuf, last_line - num_lines + 1, last_line,
+ -(last_line - dest - extra), 0L, kExtmarkNoUndo, true);
+ }
+}
+
+
+/// Get the column position for EOL on a line
+///
+/// If the lnum doesn't exist, returns 0
+colnr_T extmark_eol_col(buf_T *buf, linenr_T lnum)
+{
+ if (lnum > buf->b_ml.ml_line_count) {
+ return 0;
+ }
+ return (colnr_T)STRLEN(ml_get_buf(buf, lnum, false)) + 1;
+}
+
+
+// Adjust columns and rows for extmarks
+// based off mark_col_adjust in mark.c
+// returns true if something was moved otherwise false
+static bool extmark_col_adjust_impl(buf_T *buf, linenr_T lnum,
+ colnr_T mincol, long lnum_amount,
+ bool for_delete,
+ long update_col)
+{
+ bool marks_exist = false;
+
+ ExtmarkLine *extmarkline = extmarkline_ref(buf, lnum, false);
+ if (!extmarkline) {
+ return false;
+ }
+
+ FOR_ALL_EXTMARKS_IN_LINE(extmarkline->items, mincol, MAXCOL, {
+ marks_exist = true;
+
+ // Calculate desired col amount where the adjustment should take place
+ // (not taking) eol into account
+ long col_amount;
+ if (for_delete) {
+ if (extmark->col < update_col) {
+ // When mark inside range
+ colnr_T start_effected_range = mincol - 1;
+ col_amount = -(extmark->col - start_effected_range);
+ } else {
+ // Mark outside of range
+ // -1 because a delete of width 0 should still move marks
+ col_amount = -(update_col - mincol) - 1;
+ }
+ } else {
+ // for anything other than deletes
+ col_amount = update_col;
+ }
+
+ // No update required for this guy
+ if (col_amount == 0 && lnum_amount == 0) {
+ continue;
+ }
+
+ // Set mark to start of line
+ if (col_amount < 0
+ && extmark->col <= (colnr_T)-col_amount) {
+ extmark_update(extmark, buf, extmark->ns_id, extmark->mark_id,
+ extmarkline->lnum + lnum_amount,
+ 1, kExtmarkNoUndo, &mitr);
+ // Update the mark
+ } else {
+ // Note: The undo is handled by u_extmark_col_adjust, NoUndo here
+ extmark_update(extmark, buf, extmark->ns_id, extmark->mark_id,
+ extmarkline->lnum + lnum_amount,
+ extmark->col + (colnr_T)col_amount, kExtmarkNoUndo, &mitr);
+ }
+ })
+
+ if (kb_size(&extmarkline->items) == 0) {
+ kb_del(extmarklines, &buf->b_extlines, extmarkline);
+ extmarkline_free(extmarkline);
+ }
+
+ return marks_exist;
+}
+
+// Adjust columns and rows for extmarks
+//
+// based off mark_col_adjust in mark.c
+// use extmark_col_adjust_impl to move columns by inserting
+// Doesn't take the eol into consideration (possible to put marks in invalid
+// positions)
+void extmark_col_adjust(buf_T *buf, linenr_T lnum,
+ colnr_T mincol, long lnum_amount,
+ long col_amount, ExtmarkOp undo)
+{
+ assert(col_amount > INT_MIN && col_amount <= INT_MAX);
+
+ bool marks_moved = extmark_col_adjust_impl(buf, lnum, mincol, lnum_amount,
+ false, col_amount);
+
+ marks_moved |= bufhl_mark_col_adjust(buf, lnum, mincol,
+ lnum_amount, col_amount);
+
+ if (undo == kExtmarkUndo && marks_moved) {
+ u_extmark_col_adjust(buf, lnum, mincol, lnum_amount, col_amount);
+ }
+}
+
+// Adjust marks after a delete on a line
+//
+// Automatically readjusts to take the eol into account
+// TODO(timeyyy): change mincol to be for the mark to be copied, not moved
+//
+// @param mincol First column that needs to be moved (start of delete range) + 1
+// @param endcol Last column which needs to be copied (end of delete range + 1)
+void extmark_col_adjust_delete(buf_T *buf, linenr_T lnum,
+ colnr_T mincol, colnr_T endcol,
+ ExtmarkOp undo, int _eol)
+{
+ colnr_T start_effected_range = mincol;
+
+ bool marks_moved;
+ if (undo == kExtmarkUndo) {
+ // Copy marks that would be effected by delete
+ // -1 because we need to restore if a mark existed at the start pos
+ u_extmark_copy(buf, 0, lnum, start_effected_range, lnum, endcol);
+ }
+
+ marks_moved = extmark_col_adjust_impl(buf, lnum, mincol, 0,
+ true, (long)endcol);
+
+ marks_moved |= bufhl_mark_col_adjust(buf, lnum, endcol, 0, mincol-(endcol+1));
+ // Deletes at the end of the line have different behaviour than the normal
+ // case when deleted.
+ // Cleanup any marks that are floating beyond the end of line.
+ // we allow this to be passed in as well because the buffer may have already
+ // been mutated.
+ int eol = _eol;
+ if (!eol) {
+ eol = extmark_eol_col(buf, lnum);
+ }
+ FOR_ALL_EXTMARKS(buf, 1, lnum, eol, lnum, -1, {
+ extmark_update(extmark, buf, extmark->ns_id, extmark->mark_id,
+ extmarkline->lnum, (colnr_T)eol, kExtmarkNoUndo, &mitr);
+ })
+
+ // Record the undo for the actual move
+ if (marks_moved && undo == kExtmarkUndo) {
+ u_extmark_col_adjust_delete(buf, lnum, mincol, endcol, eol);
+ }
+}
+
+// Adjust extmark row for inserted/deleted rows (columns stay fixed).
+void extmark_adjust(buf_T *buf,
+ linenr_T line1,
+ linenr_T line2,
+ long amount,
+ long amount_after,
+ ExtmarkOp undo,
+ bool end_temp)
+{
+ ExtmarkLine *_extline;
+
+ // btree needs to be kept ordered to work, so far only :move requires this
+ // 2nd call with end_temp = true unpack the lines from the temp position
+ if (end_temp && amount < 0) {
+ for (size_t i = 0; i < kv_size(buf->b_extmark_move_space); i++) {
+ _extline = kv_A(buf->b_extmark_move_space, i);
+ _extline->lnum += amount;
+ kb_put(extmarklines, &buf->b_extlines, _extline);
+ }
+ kv_size(buf->b_extmark_move_space) = 0;
+ return;
+ }
+
+ bool marks_exist = false;
+ linenr_T *lp;
+
+ linenr_T adj_start = line1;
+ if (amount == MAXLNUM) {
+ // Careful! marks from deleted region can end up on en extisting extmarkline
+ // that is goinig to be adjusted to the target position.
+ linenr_T join_num = line1 - amount_after;
+ ExtmarkLine *joinline = (join_num > line2
+ ? extmarkline_ref(buf, join_num, false) : NULL);
+
+ // extmark_adjust is already redoable, the copy should only be for undo
+ marks_exist = extmark_copy_and_place(curbuf,
+ line1, 1,
+ line2, MAXCOL,
+ line1, 1,
+ kExtmarkUndoNoRedo, true, joinline);
+ adj_start = line2+1;
+ }
+ FOR_ALL_EXTMARKLINES(buf, adj_start, MAXLNUM, {
+ marks_exist = true;
+ lp = &(extmarkline->lnum);
+ if (*lp <= line2) {
+ // 1st call with end_temp = true, store the lines in a temp position
+ if (end_temp && amount > 0) {
+ kb_del_itr_extmarklines(&buf->b_extlines, &itr);
+ kv_push(buf->b_extmark_move_space, extmarkline);
+ }
+
+ *lp += amount;
+ } else if (amount_after && *lp > line2) {
+ *lp += amount_after;
+ }
+ })
+
+ if (undo == kExtmarkUndo && marks_exist) {
+ u_extmark_adjust(buf, line1, line2, amount, amount_after);
+ }
+}
+
+/// Range points to copy
+///
+/// if part of a larger iteration we can't delete, then the caller
+/// must check for empty lines.
+bool extmark_copy_and_place(buf_T *buf,
+ linenr_T l_lnum, colnr_T l_col,
+ linenr_T u_lnum, colnr_T u_col,
+ linenr_T p_lnum, colnr_T p_col,
+ ExtmarkOp undo, bool delete,
+ ExtmarkLine *destline)
+
+{
+ bool marks_moved = false;
+ if (undo == kExtmarkUndo || undo == kExtmarkUndoNoRedo) {
+ // Copy marks that would be effected by delete
+ u_extmark_copy(buf, 0, l_lnum, l_col, u_lnum, u_col);
+ }
+
+ // Move extmarks to their final position
+ // Careful: if we move items within the same line, we might change order of
+ // marks within the same extmarkline. Too keep it simple, first delete all
+ // items from the extmarkline and put them back in the right order.
+ FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, {
+ kvec_t(Extmark) temp_space = KV_INITIAL_VALUE;
+ bool same_line = extmarkline == destline;
+ FOR_ALL_EXTMARKS_IN_LINE(extmarkline->items,
+ (extmarkline->lnum > l_lnum) ? 0 : l_col,
+ (extmarkline->lnum < u_lnum) ? MAXCOL : u_col, {
+ if (!destline) {
+ destline = extmarkline_ref(buf, p_lnum, true);
+ same_line = extmarkline == destline;
+ }
+ marks_moved = true;
+ if (!same_line) {
+ extmark_put(p_col, extmark->mark_id, destline, extmark->ns_id);
+ ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns,
+ extmark->ns_id);
+ pmap_put(uint64_t)(ns_obj->map, extmark->mark_id, destline);
+ } else {
+ kv_push(temp_space, *extmark);
+ }
+ // Delete old mark
+ kb_del_itr(markitems, &extmarkline->items, &mitr);
+ })
+ if (same_line) {
+ for (size_t i = 0; i < kv_size(temp_space); i++) {
+ Extmark mark = kv_A(temp_space, i);
+ extmark_put(p_col, mark.mark_id, extmarkline, mark.ns_id);
+ }
+ kv_destroy(temp_space);
+ } else if (delete && kb_size(&extmarkline->items) == 0) {
+ kb_del_itr(extmarklines, &buf->b_extlines, &itr);
+ extmarkline_free(extmarkline);
+ }
+ })
+
+ // Record the undo for the actual move
+ if (marks_moved && undo == kExtmarkUndo) {
+ u_extmark_copy_place(buf, l_lnum, l_col, u_lnum, u_col, p_lnum, p_col);
+ }
+
+ return marks_moved;
+}
+
+// Get reference to line in kbtree_t, allocating it if neccessary.
+ExtmarkLine *extmarkline_ref(buf_T *buf, linenr_T lnum, bool put)
+{
+ kbtree_t(extmarklines) *b = &buf->b_extlines;
+ ExtmarkLine t, **pp;
+ t.lnum = lnum;
+
+ pp = kb_get(extmarklines, b, &t);
+ if (!pp) {
+ if (!put) {
+ return NULL;
+ }
+ ExtmarkLine *p = xcalloc(sizeof(ExtmarkLine), 1);
+ p->lnum = lnum;
+ // p->items zero initialized
+ kb_put(extmarklines, b, p);
+ return p;
+ }
+ // Return existing
+ return *pp;
+}
+
+void extmarkline_free(ExtmarkLine *extmarkline)
+{
+ kb_destroy(markitems, (&extmarkline->items));
+ xfree(extmarkline);
+}
+
+/// Put an extmark into a line,
+///
+/// caller must ensure combination of id and ns_id isn't in use.
+void extmark_put(colnr_T col, uint64_t id,
+ ExtmarkLine *extmarkline, uint64_t ns)
+{
+ Extmark t;
+ t.col = col;
+ t.mark_id = id;
+ t.line = extmarkline;
+ t.ns_id = ns;
+
+ kbtree_t(markitems) *b = &(extmarkline->items);
+ // kb_put requries the key to not be there
+ assert(!kb_getp(markitems, b, &t));
+
+ kb_put(markitems, b, t);
+}
+
+
diff --git a/src/nvim/mark_extended.h b/src/nvim/mark_extended.h
new file mode 100644
index 0000000000..ee1da26875
--- /dev/null
+++ b/src/nvim/mark_extended.h
@@ -0,0 +1,282 @@
+#ifndef NVIM_MARK_EXTENDED_H
+#define NVIM_MARK_EXTENDED_H
+
+#include "nvim/mark_extended_defs.h"
+#include "nvim/buffer_defs.h" // for buf_T
+
+
+// Macro Documentation: FOR_ALL_?
+// Search exclusively using the range values given.
+// Use MAXCOL/MAXLNUM for the start and end of the line/col.
+// The ns parameter: Unless otherwise stated, this is only a starting point
+// for the btree to searched in, the results being itterated over will
+// still contain extmarks from other namespaces.
+
+// see FOR_ALL_? for documentation
+#define FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, code)\
+ kbitr_t(extmarklines) itr;\
+ ExtmarkLine t;\
+ t.lnum = l_lnum;\
+ if (!kb_itr_get(extmarklines, &buf->b_extlines, &t, &itr)) { \
+ kb_itr_next(extmarklines, &buf->b_extlines, &itr);\
+ }\
+ ExtmarkLine *extmarkline;\
+ for (; kb_itr_valid(&itr); kb_itr_next(extmarklines, \
+ &buf->b_extlines, &itr)) { \
+ extmarkline = kb_itr_key(&itr);\
+ if (extmarkline->lnum > u_lnum) { \
+ break;\
+ }\
+ code;\
+ }
+
+// see FOR_ALL_? for documentation
+#define FOR_ALL_EXTMARKLINES_PREV(buf, l_lnum, u_lnum, code)\
+ kbitr_t(extmarklines) itr;\
+ ExtmarkLine t;\
+ t.lnum = u_lnum;\
+ if (!kb_itr_get(extmarklines, &buf->b_extlines, &t, &itr)) { \
+ kb_itr_prev(extmarklines, &buf->b_extlines, &itr);\
+ }\
+ ExtmarkLine *extmarkline;\
+ for (; kb_itr_valid(&itr); kb_itr_prev(extmarklines, \
+ &buf->b_extlines, &itr)) { \
+ extmarkline = kb_itr_key(&itr);\
+ if (extmarkline->lnum < l_lnum) { \
+ break;\
+ }\
+ code;\
+ }
+
+// see FOR_ALL_? for documentation
+#define FOR_ALL_EXTMARKS(buf, ns, l_lnum, l_col, u_lnum, u_col, code)\
+ kbitr_t(markitems) mitr;\
+ Extmark mt;\
+ mt.ns_id = ns;\
+ mt.mark_id = 0;\
+ mt.line = NULL;\
+ FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, { \
+ mt.col = (extmarkline->lnum != l_lnum) ? MINCOL : l_col;\
+ if (!kb_itr_get(markitems, &extmarkline->items, mt, &mitr)) { \
+ kb_itr_next(markitems, &extmarkline->items, &mitr);\
+ } \
+ Extmark *extmark;\
+ for (; \
+ kb_itr_valid(&mitr); \
+ kb_itr_next(markitems, &extmarkline->items, &mitr)) { \
+ extmark = &kb_itr_key(&mitr);\
+ if (extmark->line->lnum == u_lnum \
+ && extmark->col > u_col) { \
+ break;\
+ }\
+ code;\
+ }\
+ })
+
+
+// see FOR_ALL_? for documentation
+#define FOR_ALL_EXTMARKS_PREV(buf, ns, l_lnum, l_col, u_lnum, u_col, code)\
+ kbitr_t(markitems) mitr;\
+ Extmark mt;\
+ mt.mark_id = sizeof(uint64_t);\
+ mt.ns_id = ns;\
+ FOR_ALL_EXTMARKLINES_PREV(buf, l_lnum, u_lnum, { \
+ mt.col = (extmarkline->lnum != u_lnum) ? MAXCOL : u_col;\
+ if (!kb_itr_get(markitems, &extmarkline->items, mt, &mitr)) { \
+ kb_itr_prev(markitems, &extmarkline->items, &mitr);\
+ } \
+ Extmark *extmark;\
+ for (; \
+ kb_itr_valid(&mitr); \
+ kb_itr_prev(markitems, &extmarkline->items, &mitr)) { \
+ extmark = &kb_itr_key(&mitr);\
+ if (extmark->line->lnum == l_lnum \
+ && extmark->col < l_col) { \
+ break;\
+ }\
+ code;\
+ }\
+ })
+
+
+#define FOR_ALL_EXTMARKS_IN_LINE(items, l_col, u_col, code)\
+ kbitr_t(markitems) mitr;\
+ Extmark mt;\
+ mt.ns_id = 0;\
+ mt.mark_id = 0;\
+ mt.line = NULL;\
+ mt.col = l_col;\
+ colnr_T extmarkline_u_col = u_col;\
+ if (!kb_itr_get(markitems, &items, mt, &mitr)) { \
+ kb_itr_next(markitems, &items, &mitr);\
+ } \
+ Extmark *extmark;\
+ for (; kb_itr_valid(&mitr); kb_itr_next(markitems, &items, &mitr)) { \
+ extmark = &kb_itr_key(&mitr);\
+ if (extmark->col > extmarkline_u_col) { \
+ break;\
+ }\
+ code;\
+ }
+
+
+typedef struct ExtmarkNs { // For namespacing extmarks
+ PMap(uint64_t) *map; // For fast lookup
+ uint64_t free_id; // For automatically assigning id's
+} ExtmarkNs;
+
+
+typedef kvec_t(Extmark *) ExtmarkArray;
+
+
+// Undo/redo extmarks
+
+typedef enum {
+ kExtmarkNOOP, // Extmarks shouldn't be moved
+ kExtmarkUndo, // Operation should be reversable/undoable
+ kExtmarkNoUndo, // Operation should not be reversable
+ kExtmarkUndoNoRedo, // Operation should be undoable, but not redoable
+} ExtmarkOp;
+
+
+// adjust line numbers only, corresponding to mark_adjust call
+typedef struct {
+ linenr_T line1;
+ linenr_T line2;
+ long amount;
+ long amount_after;
+} Adjust;
+
+// adjust columns after split/join line, like mark_col_adjust
+typedef struct {
+ linenr_T lnum;
+ colnr_T mincol;
+ long col_amount;
+ long lnum_amount;
+} ColAdjust;
+
+// delete the columns between mincol and endcol
+typedef struct {
+ linenr_T lnum;
+ colnr_T mincol;
+ colnr_T endcol;
+ int eol;
+} ColAdjustDelete;
+
+// adjust linenumbers after :move operation
+typedef struct {
+ linenr_T line1;
+ linenr_T line2;
+ linenr_T last_line;
+ linenr_T dest;
+ linenr_T num_lines;
+ linenr_T extra;
+} AdjustMove;
+
+// TODO(bfredl): reconsider if we really should track mark creation/updating
+// itself, these are not really "edit" operation.
+// extmark was created
+typedef struct {
+ uint64_t ns_id;
+ uint64_t mark_id;
+ linenr_T lnum;
+ colnr_T col;
+} ExtmarkSet;
+
+// extmark was updated
+typedef struct {
+ uint64_t ns_id;
+ uint64_t mark_id;
+ linenr_T old_lnum;
+ colnr_T old_col;
+ linenr_T lnum;
+ colnr_T col;
+} ExtmarkUpdate;
+
+// copied mark before deletion (as operation is destructive)
+typedef struct {
+ uint64_t ns_id;
+ uint64_t mark_id;
+ linenr_T lnum;
+ colnr_T col;
+} ExtmarkCopy;
+
+// also used as part of :move operation? probably can be simplified to one
+// event.
+typedef struct {
+ linenr_T l_lnum;
+ colnr_T l_col;
+ linenr_T u_lnum;
+ colnr_T u_col;
+ linenr_T p_lnum;
+ colnr_T p_col;
+} ExtmarkCopyPlace;
+
+// extmark was cleared.
+// TODO(bfredl): same reconsideration as for ExtmarkSet/ExtmarkUpdate
+typedef struct {
+ uint64_t ns_id;
+ linenr_T l_lnum;
+ linenr_T u_lnum;
+} ExtmarkClear;
+
+
+typedef enum {
+ kLineAdjust,
+ kColAdjust,
+ kColAdjustDelete,
+ kAdjustMove,
+ kExtmarkSet,
+ kExtmarkDel,
+ kExtmarkUpdate,
+ kExtmarkCopy,
+ kExtmarkCopyPlace,
+ kExtmarkClear,
+} UndoObjectType;
+
+// TODO(bfredl): reduce the number of undo action types
+struct undo_object {
+ UndoObjectType type;
+ union {
+ Adjust adjust;
+ ColAdjust col_adjust;
+ ColAdjustDelete col_adjust_delete;
+ AdjustMove move;
+ ExtmarkSet set;
+ ExtmarkUpdate update;
+ ExtmarkCopy copy;
+ ExtmarkCopyPlace copy_place;
+ ExtmarkClear clear;
+ } data;
+};
+
+
+// For doing move of extmarks in substitutions
+typedef struct {
+ lpos_T startpos;
+ lpos_T endpos;
+ linenr_T lnum;
+ int sublen;
+} ExtmarkSubSingle;
+
+// For doing move of extmarks in substitutions
+typedef struct {
+ lpos_T startpos;
+ lpos_T endpos;
+ linenr_T lnum;
+ linenr_T newline_in_pat;
+ linenr_T newline_in_sub;
+ linenr_T lnum_added;
+ lpos_T cm_start; // start of the match
+ lpos_T cm_end; // end of the match
+ int eol; // end of the match
+} ExtmarkSubMulti;
+
+typedef kvec_t(ExtmarkSubSingle) extmark_sub_single_vec_t;
+typedef kvec_t(ExtmarkSubMulti) extmark_sub_multi_vec_t;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "mark_extended.h.generated.h"
+#endif
+
+#endif // NVIM_MARK_EXTENDED_H
diff --git a/src/nvim/mark_extended_defs.h b/src/nvim/mark_extended_defs.h
new file mode 100644
index 0000000000..565c599d06
--- /dev/null
+++ b/src/nvim/mark_extended_defs.h
@@ -0,0 +1,54 @@
+#ifndef NVIM_MARK_EXTENDED_DEFS_H
+#define NVIM_MARK_EXTENDED_DEFS_H
+
+#include "nvim/pos.h" // for colnr_T
+#include "nvim/map.h" // for uint64_t
+#include "nvim/lib/kbtree.h"
+#include "nvim/lib/kvec.h"
+
+struct ExtmarkLine;
+
+typedef struct Extmark
+{
+ uint64_t ns_id;
+ uint64_t mark_id;
+ struct ExtmarkLine *line;
+ colnr_T col;
+} Extmark;
+
+
+// We only need to compare columns as rows are stored in a different tree.
+// Marks are ordered by: position, namespace, mark_id
+// This improves moving marks but slows down all other use cases (searches)
+static inline int extmark_cmp(Extmark a, Extmark b)
+{
+ int cmp = kb_generic_cmp(a.col, b.col);
+ if (cmp != 0) {
+ return cmp;
+ }
+ cmp = kb_generic_cmp(a.ns_id, b.ns_id);
+ if (cmp != 0) {
+ return cmp;
+ }
+ return kb_generic_cmp(a.mark_id, b.mark_id);
+}
+
+
+#define markitems_cmp(a, b) (extmark_cmp((a), (b)))
+KBTREE_INIT(markitems, Extmark, markitems_cmp, 10)
+
+typedef struct ExtmarkLine
+{
+ linenr_T lnum;
+ kbtree_t(markitems) items;
+} ExtmarkLine;
+
+#define EXTMARKLINE_CMP(a, b) (kb_generic_cmp((a)->lnum, (b)->lnum))
+KBTREE_INIT(extmarklines, ExtmarkLine *, EXTMARKLINE_CMP, 10)
+
+
+typedef struct undo_object ExtmarkUndoObject;
+typedef kvec_t(ExtmarkUndoObject) extmark_undo_vec_t;
+
+
+#endif // NVIM_MARK_EXTENDED_DEFS_H
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index 15dd2767a2..e5ba17a0a7 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -523,9 +523,9 @@ void ml_open_file(buf_T *buf)
}
}
- if (mfp->mf_fname == NULL) { /* Failed! */
- need_wait_return = TRUE; /* call wait_return later */
- ++no_wait_return;
+ if (*p_dir != NUL && mfp->mf_fname == NULL) {
+ need_wait_return = true; // call wait_return later
+ no_wait_return++;
(void)EMSG2(_(
"E303: Unable to open swap file for \"%s\", recovery impossible"),
buf_spname(buf) != NULL ? buf_spname(buf) : buf->b_fname);
@@ -540,7 +540,7 @@ void ml_open_file(buf_T *buf)
/// file, or reading into an existing buffer, create a swap file now.
///
/// @param newfile reading file into new buffer
-void check_need_swap(int newfile)
+void check_need_swap(bool newfile)
{
int old_msg_silent = msg_silent; // might be reset by an E325 message
msg_silent = 0; // If swap dialog prompts for input, user needs to see it!
@@ -738,10 +738,10 @@ static void add_b0_fenc(ZERO_BL *b0p, buf_T *buf)
}
-/*
- * Try to recover curbuf from the .swp file.
- */
-void ml_recover(void)
+/// Try to recover curbuf from the .swp file.
+/// @param checkext If true, check the extension and detect whether it is a
+/// swap file.
+void ml_recover(bool checkext)
{
buf_T *buf = NULL;
memfile_T *mfp = NULL;
@@ -785,7 +785,7 @@ void ml_recover(void)
if (fname == NULL) /* When there is no file name */
fname = (char_u *)"";
len = (int)STRLEN(fname);
- if (len >= 4
+ if (checkext && len >= 4
&& STRNICMP(fname + len - 4, ".s", 2) == 0
&& vim_strchr((char_u *)"abcdefghijklmnopqrstuvw",
TOLOWER_ASC(fname[len - 2])) != NULL
@@ -937,8 +937,9 @@ void ml_recover(void)
*/
if (directly) {
expand_env(b0p->b0_fname, NameBuff, MAXPATHL);
- if (setfname(curbuf, NameBuff, NULL, TRUE) == FAIL)
+ if (setfname(curbuf, NameBuff, NULL, true) == FAIL) {
goto theend;
+ }
}
home_replace(NULL, mfp->mf_fname, NameBuff, MAXPATHL, TRUE);
@@ -1375,7 +1376,9 @@ recover_names (
if (curbuf->b_ml.ml_mfp != NULL
&& (p = curbuf->b_ml.ml_mfp->mf_fname) != NULL) {
for (int i = 0; i < num_files; i++) {
- if (path_full_compare(p, files[i], true) & kEqualFiles) {
+ // Do not expand wildcards, on Windows would try to expand
+ // "%tmp%" in "%tmp%file"
+ if (path_full_compare(p, files[i], true, false) & kEqualFiles) {
// Remove the name from files[i]. Move further entries
// down. When the array becomes empty free it here, since
// FreeWild() won't be called below.
@@ -1435,7 +1438,7 @@ recover_names (
* Append the full path to name with path separators made into percent
* signs, to dir. An unnamed buffer is handled as "" (<currentdir>/"")
*/
-static char *make_percent_swname(const char *dir, char *name)
+char *make_percent_swname(const char *dir, char *name)
FUNC_ATTR_NONNULL_ARG(1)
{
char *d = NULL;
@@ -1927,6 +1930,7 @@ int ml_append_buf(
colnr_T len, // length of new line, including NUL, or 0
bool newfile // flag, see above
)
+ FUNC_ATTR_NONNULL_ARG(1)
{
if (buf->b_ml.ml_mfp == NULL)
return FAIL;
diff --git a/src/nvim/memory.c b/src/nvim/memory.c
index 64aae71433..9bc6b23ce3 100644
--- a/src/nvim/memory.c
+++ b/src/nvim/memory.c
@@ -693,6 +693,8 @@ void free_all_mem(void)
clear_hl_tables(false);
list_free_log();
+
+ check_quickfix_busy();
}
#endif
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 30e906cd5f..067f1c3283 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -233,12 +233,15 @@ void msg_multiline_attr(const char *s, int attr, bool check_int)
{
const char *next_spec = s;
- while (next_spec != NULL && (!check_int || !got_int)) {
+ while (next_spec != NULL) {
+ if (check_int && got_int) {
+ return;
+ }
next_spec = strpbrk(s, "\t\n\r");
if (next_spec != NULL) {
// Printing all char that are before the char found by strpbrk
- msg_outtrans_len_attr((char_u *)s, next_spec - s, attr);
+ msg_outtrans_len_attr((const char_u *)s, next_spec - s, attr);
if (*next_spec != TAB) {
msg_clr_eos();
@@ -1375,7 +1378,7 @@ static void msg_home_replace_attr(char_u *fname, int attr)
/*
* Output 'len' characters in 'str' (including NULs) with translation
- * if 'len' is -1, output upto a NUL character.
+ * if 'len' is -1, output up to a NUL character.
* Use attributes 'attr'.
* Return the number of characters it takes on the screen.
*/
@@ -1384,12 +1387,12 @@ int msg_outtrans(char_u *str)
return msg_outtrans_attr(str, 0);
}
-int msg_outtrans_attr(char_u *str, int attr)
+int msg_outtrans_attr(const char_u *str, int attr)
{
return msg_outtrans_len_attr(str, (int)STRLEN(str), attr);
}
-int msg_outtrans_len(char_u *str, int len)
+int msg_outtrans_len(const char_u *str, int len)
{
return msg_outtrans_len_attr(str, len, 0);
}
@@ -1402,7 +1405,7 @@ char_u *msg_outtrans_one(char_u *p, int attr)
{
int l;
- if (has_mbyte && (l = (*mb_ptr2len)(p)) > 1) {
+ if ((l = utfc_ptr2len(p)) > 1) {
msg_outtrans_len_attr(p, l, attr);
return p + l;
}
@@ -1410,7 +1413,7 @@ char_u *msg_outtrans_one(char_u *p, int attr)
return p + 1;
}
-int msg_outtrans_len_attr(char_u *msgstr, int len, int attr)
+int msg_outtrans_len_attr(const char_u *msgstr, int len, int attr)
{
int retval = 0;
const char *str = (const char *)msgstr;
@@ -1498,7 +1501,7 @@ void msg_make(char_u *arg)
}
}
-/// Output the string 'str' upto a NUL character.
+/// Output the string 'str' up to a NUL character.
/// Return the number of characters it takes on the screen.
///
/// If K_SPECIAL is encountered, then it is taken in conjunction with the
diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c
index ab3520dd73..a871d424c6 100644
--- a/src/nvim/misc1.c
+++ b/src/nvim/misc1.c
@@ -30,7 +30,6 @@
#include "nvim/indent_c.h"
#include "nvim/buffer_updates.h"
#include "nvim/main.h"
-#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -484,25 +483,39 @@ int plines_win_col(win_T *wp, linenr_T lnum, long column)
return lines;
}
+/// Get the number of screen lines lnum takes up. This takes care of
+/// both folds and topfill, and limits to the current window height.
+///
+/// @param[in] wp window line is in
+/// @param[in] lnum line number
+/// @param[out] nextp if not NULL, the line after a fold
+/// @param[out] foldedp if not NULL, whether lnum is on a fold
+/// @param[in] cache whether to use the window's cache for folds
+///
+/// @return the total number of screen lines
+int plines_win_full(win_T *wp, linenr_T lnum, linenr_T *const nextp,
+ bool *const foldedp, const bool cache)
+{
+ bool folded = hasFoldingWin(wp, lnum, NULL, nextp, cache, NULL);
+ if (foldedp) {
+ *foldedp = folded;
+ }
+ if (folded) {
+ return 1;
+ } else if (lnum == wp->w_topline) {
+ return plines_win_nofill(wp, lnum, true) + wp->w_topfill;
+ }
+ return plines_win(wp, lnum, true);
+}
+
int plines_m_win(win_T *wp, linenr_T first, linenr_T last)
{
int count = 0;
while (first <= last) {
- // Check if there are any really folded lines, but also included lines
- // that are maybe folded.
- linenr_T x = foldedCount(wp, first, NULL);
- if (x > 0) {
- ++count; /* count 1 for "+-- folded" line */
- first += x;
- } else {
- if (first == wp->w_topline) {
- count += plines_win_nofill(wp, first, true) + wp->w_topfill;
- } else {
- count += plines_win(wp, first, true);
- }
- first++;
- }
+ linenr_T next = first;
+ count += plines_win_full(wp, first, &next, NULL, false);
+ first = next + 1;
}
return count;
}
diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c
index d0aa0653cb..deb7ee6342 100644
--- a/src/nvim/mouse.c
+++ b/src/nvim/mouse.c
@@ -508,31 +508,30 @@ static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp)
return NULL;
}
-/*
- * setmouse() - switch mouse on/off depending on current mode and 'mouse'
- */
+/// Set UI mouse depending on current mode and 'mouse'.
+///
+/// Emits mouse_on/mouse_off UI event (unless 'mouse' is empty).
void setmouse(void)
{
- int checkfor;
-
ui_cursor_shape();
- /* be quick when mouse is off */
- if (*p_mouse == NUL)
+ // Be quick when mouse is off.
+ if (*p_mouse == NUL) {
return;
+ }
- if (VIsual_active)
+ int checkfor = MOUSE_NORMAL; // assume normal mode
+ if (VIsual_active) {
checkfor = MOUSE_VISUAL;
- else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE)
+ } else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE) {
checkfor = MOUSE_RETURN;
- else if (State & INSERT)
+ } else if (State & INSERT) {
checkfor = MOUSE_INSERT;
- else if (State & CMDLINE)
+ } else if (State & CMDLINE) {
checkfor = MOUSE_COMMAND;
- else if (State == CONFIRM || State == EXTERNCMD)
- checkfor = ' '; /* don't use mouse for ":confirm" or ":!cmd" */
- else
- checkfor = MOUSE_NORMAL; /* assume normal mode */
+ } else if (State == CONFIRM || State == EXTERNCMD) {
+ checkfor = ' '; // don't use mouse for ":confirm" or ":!cmd"
+ }
if (mouse_has(checkfor)) {
ui_call_mouse_on();
diff --git a/src/nvim/move.c b/src/nvim/move.c
index e6fee9999f..efbc548620 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -65,17 +65,10 @@ static void comp_botline(win_T *wp)
done = 0;
}
- for (; lnum <= wp->w_buffer->b_ml.ml_line_count; ++lnum) {
- int n;
+ for (; lnum <= wp->w_buffer->b_ml.ml_line_count; lnum++) {
linenr_T last = lnum;
- bool folded = hasFoldingWin(wp, lnum, NULL, &last, true, NULL);
- if (folded) {
- n = 1;
- } else if (lnum == wp->w_topline) {
- n = plines_win_nofill(wp, lnum, true) + wp->w_topfill;
- } else {
- n = plines_win(wp, lnum, true);
- }
+ bool folded;
+ int n = plines_win_full(wp, lnum, &last, &folded, true);
if (lnum <= wp->w_cursor.lnum && last >= wp->w_cursor.lnum) {
wp->w_cline_row = done;
wp->w_cline_height = n;
@@ -571,18 +564,14 @@ static void curs_rows(win_T *wp)
break;
wp->w_cline_row += wp->w_lines[i].wl_size;
} else {
- long fold_count = foldedCount(wp, lnum, NULL);
- if (fold_count) {
- lnum += fold_count;
- if (lnum > wp->w_cursor.lnum)
- break;
- ++wp->w_cline_row;
- } else if (lnum == wp->w_topline) {
- wp->w_cline_row += plines_win_nofill(wp, lnum++, true)
- + wp->w_topfill;
- } else {
- wp->w_cline_row += plines_win(wp, lnum++, true);
+ linenr_T last = lnum;
+ bool folded;
+ int n = plines_win_full(wp, lnum, &last, &folded, false);
+ lnum = last + 1;
+ if (folded && lnum > wp->w_cursor.lnum) {
+ break;
}
+ wp->w_cline_row += n;
}
}
@@ -593,18 +582,13 @@ static void curs_rows(win_T *wp)
|| (i < wp->w_lines_valid
&& (!wp->w_lines[i].wl_valid
|| wp->w_lines[i].wl_lnum != wp->w_cursor.lnum))) {
- if (wp->w_cursor.lnum == wp->w_topline)
- wp->w_cline_height = plines_win_nofill(wp, wp->w_cursor.lnum,
- true) + wp->w_topfill;
- else
- wp->w_cline_height = plines_win(wp, wp->w_cursor.lnum, true);
- wp->w_cline_folded = hasFoldingWin(wp, wp->w_cursor.lnum,
- NULL, NULL, true, NULL);
+ wp->w_cline_height = plines_win_full(wp, wp->w_cursor.lnum, NULL,
+ &wp->w_cline_folded, true);
} else if (i > wp->w_lines_valid) {
/* a line that is too long to fit on the last screen line */
wp->w_cline_height = 0;
- wp->w_cline_folded = hasFoldingWin(wp, wp->w_cursor.lnum,
- NULL, NULL, true, NULL);
+ wp->w_cline_folded = hasFoldingWin(wp, wp->w_cursor.lnum, NULL,
+ NULL, true, NULL);
} else {
wp->w_cline_height = wp->w_lines[i].wl_size;
wp->w_cline_folded = wp->w_lines[i].wl_folded;
@@ -646,12 +630,9 @@ static void validate_cheight(void)
{
check_cursor_moved(curwin);
if (!(curwin->w_valid & VALID_CHEIGHT)) {
- if (curwin->w_cursor.lnum == curwin->w_topline)
- curwin->w_cline_height = plines_nofill(curwin->w_cursor.lnum)
- + curwin->w_topfill;
- else
- curwin->w_cline_height = plines(curwin->w_cursor.lnum);
- curwin->w_cline_folded = hasFolding(curwin->w_cursor.lnum, NULL, NULL);
+ curwin->w_cline_height = plines_win_full(curwin, curwin->w_cursor.lnum,
+ NULL, &curwin->w_cline_folded,
+ true);
curwin->w_valid |= VALID_CHEIGHT;
}
}
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index e7e6d2b365..b9dbcc6805 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -1422,12 +1422,12 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
if (oap->motion_type == kMTLineWise) {
oap->inclusive = false;
} else if (oap->motion_type == kMTCharWise) {
- // If the motion already was characterwise, toggle "inclusive"
+ // If the motion already was charwise, toggle "inclusive"
oap->inclusive = !oap->inclusive;
}
oap->motion_type = kMTCharWise;
} else if (oap->motion_force == Ctrl_V) {
- // Change line- or characterwise motion into Visual block mode.
+ // Change line- or charwise motion into Visual block mode.
if (!VIsual_active) {
VIsual_active = true;
VIsual = oap->start;
@@ -1516,7 +1516,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
}
// In Select mode, a linewise selection is operated upon like a
- // characterwise selection.
+ // charwise selection.
// Special case: gH<Del> deletes the last line.
if (VIsual_select && VIsual_mode == 'V'
&& cap->oap->op_type != OP_DELETE) {
@@ -1553,9 +1553,11 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
if (!VIsual_active) {
if (hasFolding(oap->start.lnum, &oap->start.lnum, NULL))
oap->start.col = 0;
- if (hasFolding(curwin->w_cursor.lnum, NULL,
- &curwin->w_cursor.lnum))
+ if ((curwin->w_cursor.col > 0 || oap->inclusive)
+ && hasFolding(curwin->w_cursor.lnum, NULL,
+ &curwin->w_cursor.lnum)) {
curwin->w_cursor.col = (colnr_T)STRLEN(get_cursor_line_ptr());
+ }
}
oap->end = curwin->w_cursor;
curwin->w_cursor = oap->start;
@@ -3792,7 +3794,7 @@ find_decl (
valid = false;
(void)valid; // Avoid "dead assignment" warning.
t = searchit(curwin, curbuf, &curwin->w_cursor, NULL, FORWARD,
- pat, 1L, searchflags, RE_LAST, (linenr_T)0, NULL, NULL);
+ pat, 1L, searchflags, RE_LAST, NULL);
if (curwin->w_cursor.lnum >= old_pos.lnum) {
t = false; // match after start is failure too
}
@@ -3923,18 +3925,20 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist)
n = ((linelen - width1 - 1) / width2 + 1) * width2 + width1;
else
n = width1;
- if (curwin->w_curswant > (colnr_T)n + 1)
- curwin->w_curswant -= ((curwin->w_curswant - n) / width2 + 1)
- * width2;
+ if (curwin->w_curswant >= n) {
+ curwin->w_curswant = n - 1;
+ }
}
while (dist--) {
if (dir == BACKWARD) {
- if ((long)curwin->w_curswant >= width2)
- /* move back within line */
+ if (curwin->w_curswant >= width1) {
+ // Move back within the line. This can give a negative value
+ // for w_curswant if width1 < width2 (with cpoptions+=n),
+ // which will get clipped to column 0.
curwin->w_curswant -= width2;
- else {
- /* to previous line */
+ } else {
+ // to previous line
if (curwin->w_cursor.lnum == 1) {
retval = false;
break;
@@ -3971,6 +3975,13 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist)
}
curwin->w_cursor.lnum++;
curwin->w_curswant %= width2;
+ // Check if the cursor has moved below the number display
+ // when width1 < width2 (with cpoptions+=n). Subtract width2
+ // to get a negative value for w_curswant, which will get
+ // clipped to column 0.
+ if (curwin->w_curswant >= width1) {
+ curwin->w_curswant -= width2;
+ }
linelen = linetabsize(get_cursor_line_ptr());
}
}
@@ -4577,7 +4588,7 @@ static void nv_colon(cmdarg_T *cap)
nv_operator(cap);
} else {
if (cap->oap->op_type != OP_NOP) {
- // Using ":" as a movement is characterwise exclusive.
+ // Using ":" as a movement is charwise exclusive.
cap->oap->motion_type = kMTCharWise;
cap->oap->inclusive = false;
} else if (cap->count0 && !is_cmdkey) {
@@ -4678,9 +4689,8 @@ static void nv_ctrlo(cmdarg_T *cap)
}
}
-/*
- * CTRL-^ command, short for ":e #"
- */
+// CTRL-^ command, short for ":e #". Works even when the alternate buffer is
+// not named.
static void nv_hat(cmdarg_T *cap)
{
if (!checkclearopq(cap->oap))
@@ -4926,9 +4936,12 @@ static void nv_ident(cmdarg_T *cap)
/* put pattern in search history */
init_history();
add_to_history(HIST_SEARCH, (char_u *)buf, true, NUL);
- (void)normal_search(cap, cmdchar == '*' ? '/' : '?', (char_u *)buf, 0);
+ (void)normal_search(cap, cmdchar == '*' ? '/' : '?', (char_u *)buf, 0,
+ NULL);
} else {
+ g_tag_at_cursor = true;
do_cmdline_cmd(buf);
+ g_tag_at_cursor = false;
}
xfree(buf);
@@ -5125,14 +5138,10 @@ static void nv_right(cmdarg_T *cap)
break;
} else if (PAST_LINE) {
curwin->w_set_curswant = true;
- if (virtual_active())
+ if (virtual_active()) {
oneright();
- else {
- if (has_mbyte)
- curwin->w_cursor.col +=
- (*mb_ptr2len)(get_cursor_pos_ptr());
- else
- ++curwin->w_cursor.col;
+ } else {
+ curwin->w_cursor.col += (*mb_ptr2len)(get_cursor_pos_ptr());
}
}
}
@@ -5346,7 +5355,7 @@ static void nv_search(cmdarg_T *cap)
// When using 'incsearch' the cursor may be moved to set a different search
// start position.
- cap->searchbuf = getcmdline(cap->cmdchar, cap->count1, 0);
+ cap->searchbuf = getcmdline(cap->cmdchar, cap->count1, 0, true);
if (cap->searchbuf == NULL) {
clearop(oap);
@@ -5355,7 +5364,7 @@ static void nv_search(cmdarg_T *cap)
(void)normal_search(cap, cap->cmdchar, cap->searchbuf,
(cap->arg || !equalpos(save_cursor, curwin->w_cursor))
- ? 0 : SEARCH_MARK);
+ ? 0 : SEARCH_MARK, NULL);
}
/*
@@ -5365,14 +5374,15 @@ static void nv_search(cmdarg_T *cap)
static void nv_next(cmdarg_T *cap)
{
pos_T old = curwin->w_cursor;
- int i = normal_search(cap, 0, NULL, SEARCH_MARK | cap->arg);
+ int wrapped = false;
+ int i = normal_search(cap, 0, NULL, SEARCH_MARK | cap->arg, &wrapped);
- if (i == 1 && equalpos(old, curwin->w_cursor)) {
+ if (i == 1 && !wrapped && equalpos(old, curwin->w_cursor)) {
// Avoid getting stuck on the current cursor position, which can happen when
// an offset is given and the cursor is on the last char in the buffer:
// Repeat with count + 1.
cap->count1 += 1;
- (void)normal_search(cap, 0, NULL, SEARCH_MARK | cap->arg);
+ (void)normal_search(cap, 0, NULL, SEARCH_MARK | cap->arg, NULL);
cap->count1 -= 1;
}
}
@@ -5386,18 +5396,24 @@ static int normal_search(
cmdarg_T *cap,
int dir,
char_u *pat,
- int opt /* extra flags for do_search() */
+ int opt, // extra flags for do_search()
+ int *wrapped
)
{
int i;
+ searchit_arg_T sia;
cap->oap->motion_type = kMTCharWise;
cap->oap->inclusive = false;
cap->oap->use_reg_one = true;
curwin->w_set_curswant = true;
+ memset(&sia, 0, sizeof(sia));
i = do_search(cap->oap, dir, pat, cap->count1,
- opt | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG, NULL, NULL);
+ opt | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG, &sia);
+ if (wrapped != NULL) {
+ *wrapped = sia.sa_wrapped;
+ }
if (i == 0) {
clearop(cap->oap);
} else {
@@ -6356,8 +6372,8 @@ static void nv_visual(cmdarg_T *cap)
if (cap->cmdchar == Ctrl_Q)
cap->cmdchar = Ctrl_V;
- /* 'v', 'V' and CTRL-V can be used while an operator is pending to make it
- * characterwise, linewise, or blockwise. */
+ // 'v', 'V' and CTRL-V can be used while an operator is pending to make it
+ // charwise, linewise, or blockwise.
if (cap->oap->op_type != OP_NOP) {
motion_force = cap->oap->motion_force = cap->cmdchar;
finish_op = false; // operator doesn't finish now but later
@@ -6733,6 +6749,22 @@ static void nv_g_cmd(cmdarg_T *cap)
curwin->w_set_curswant = true;
break;
+ case 'M':
+ {
+ const char_u *const ptr = get_cursor_line_ptr();
+
+ oap->motion_type = kMTCharWise;
+ oap->inclusive = false;
+ i = (int)mb_string2cells_len(ptr, STRLEN(ptr));
+ if (cap->count0 > 0 && cap->count0 <= 100) {
+ coladvance((colnr_T)(i * cap->count0 / 100));
+ } else {
+ coladvance((colnr_T)(i / 2));
+ }
+ curwin->w_set_curswant = true;
+ }
+ break;
+
case '_':
/* "g_": to the last non-blank character in the line or <count> lines
* downward. */
@@ -6797,10 +6829,14 @@ static void nv_g_cmd(cmdarg_T *cap)
} else if (nv_screengo(oap, FORWARD, cap->count1 - 1) == false)
clearopbeep(oap);
} else {
+ if (cap->count1 > 1) {
+ // if it fails, let the cursor still move to the last char
+ cursor_down(cap->count1 - 1, false);
+ }
i = curwin->w_leftcol + curwin->w_width_inner - col_off - 1;
coladvance((colnr_T)i);
- /* Make sure we stick in this column. */
+ // Make sure we stick in this column.
validate_virtcol();
curwin->w_curswant = curwin->w_virtcol;
curwin->w_set_curswant = false;
@@ -7851,15 +7887,17 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
cap->oap->regname = regname;
}
- /* When deleted a linewise Visual area, put the register as
- * lines to avoid it joined with the next line. When deletion was
- * characterwise, split a line when putting lines. */
- if (VIsual_mode == 'V')
+ // When deleted a linewise Visual area, put the register as
+ // lines to avoid it joined with the next line. When deletion was
+ // charwise, split a line when putting lines.
+ if (VIsual_mode == 'V') {
flags |= PUT_LINE;
- else if (VIsual_mode == 'v')
+ } else if (VIsual_mode == 'v') {
flags |= PUT_LINE_SPLIT;
- if (VIsual_mode == Ctrl_V && dir == FORWARD)
+ }
+ if (VIsual_mode == Ctrl_V && dir == FORWARD) {
flags |= PUT_LINE_FORWARD;
+ }
dir = BACKWARD;
if ((VIsual_mode != 'V'
&& curwin->w_cursor.col < curbuf->b_op_start.col)
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 4f0c1b5cb9..294c65ca03 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -49,6 +49,7 @@
#include "nvim/undo.h"
#include "nvim/macros.h"
#include "nvim/window.h"
+#include "nvim/lib/kvec.h"
#include "nvim/os/input.h"
#include "nvim/os/time.h"
@@ -270,20 +271,21 @@ void shift_line(
int left,
int round,
int amount,
- int call_changed_bytes /* call changed_bytes() */
+ int call_changed_bytes // call changed_bytes()
)
{
int count;
int i, j;
int p_sw = get_sw_value(curbuf);
- count = get_indent(); /* get current indent */
+ count = get_indent(); // get current indent
- if (round) { /* round off indent */
- i = count / p_sw; /* number of p_sw rounded down */
- j = count % p_sw; /* extra spaces */
- if (j && left) /* first remove extra spaces */
- --amount;
+ if (round) { // round off indent
+ i = count / p_sw; // number of p_sw rounded down
+ j = count % p_sw; // extra spaces
+ if (j && left) { // first remove extra spaces
+ amount--;
+ }
if (left) {
i -= amount;
if (i < 0)
@@ -291,7 +293,7 @@ void shift_line(
} else
i += amount;
count = i * p_sw;
- } else { /* original vi indent */
+ } else { // original vi indent
if (left) {
count -= p_sw * amount;
if (count < 0)
@@ -300,11 +302,21 @@ void shift_line(
count += p_sw * amount;
}
- /* Set new indent */
- if (State & VREPLACE_FLAG)
- change_indent(INDENT_SET, count, FALSE, NUL, call_changed_bytes);
- else
+ // Set new indent
+ if (State & VREPLACE_FLAG) {
+ change_indent(INDENT_SET, count, false, NUL, call_changed_bytes);
+ } else {
(void)set_indent(count, call_changed_bytes ? SIN_CHANGED : 0);
+
+ colnr_T mincol = (curwin->w_cursor.col + 1) -p_sw;
+ colnr_T col_amount = left ? -p_sw : p_sw;
+ extmark_col_adjust(curbuf,
+ curwin->w_cursor.lnum,
+ mincol,
+ 0,
+ col_amount,
+ kExtmarkUndo);
+ }
}
/*
@@ -477,6 +489,10 @@ static void shift_block(oparg_T *oap, int amount)
State = oldstate;
curwin->w_cursor.col = oldcol;
p_ri = old_p_ri;
+
+ colnr_T col_amount = left ? -p_sw : p_sw;
+ extmark_col_adjust(curbuf, curwin->w_cursor.lnum,
+ curwin->w_cursor.col, 0, col_amount, kExtmarkUndo);
}
/*
@@ -621,10 +637,19 @@ void op_reindent(oparg_T *oap, Indenter how)
amount = how(); /* get the indent for this line */
if (amount >= 0 && set_indent(amount, SIN_UNDO)) {
- /* did change the indent, call changed_lines() later */
- if (first_changed == 0)
+ // did change the indent, call changed_lines() later
+ if (first_changed == 0) {
first_changed = curwin->w_cursor.lnum;
+ }
last_changed = curwin->w_cursor.lnum;
+
+ // Adjust extmarks
+ extmark_col_adjust(curbuf,
+ curwin->w_cursor.lnum,
+ 0, // mincol
+ 0, // lnum_amount
+ amount, // col_amount
+ kExtmarkUndo);
}
}
++curwin->w_cursor.lnum;
@@ -671,13 +696,15 @@ int get_expr_register(void)
{
char_u *new_line;
- new_line = getcmdline('=', 0L, 0);
- if (new_line == NULL)
+ new_line = getcmdline('=', 0L, 0, true);
+ if (new_line == NULL) {
return NUL;
- if (*new_line == NUL) /* use previous line */
+ }
+ if (*new_line == NUL) { // use previous line
xfree(new_line);
- else
+ } else {
set_expr_line(new_line);
+ }
return '=';
}
@@ -1558,6 +1585,7 @@ int op_delete(oparg_T *oap)
oap->end = curwin->w_cursor;
curwin->w_cursor = oap->start;
}
+ mb_adjust_opend(oap);
}
if (oap->line_count == 1) { /* delete characters within one line */
@@ -1627,10 +1655,35 @@ setmarks:
if (oap->motion_type == kMTBlockWise) {
curbuf->b_op_end.lnum = oap->end.lnum;
curbuf->b_op_end.col = oap->start.col;
- } else
+ } else {
curbuf->b_op_end = oap->start;
+ }
curbuf->b_op_start = oap->start;
+ // TODO(timeyyy): refactor: Move extended marks
+ // + 1 to change to buf mode,
+ // and + 1 because we only move marks after the deleted col
+ colnr_T mincol = oap->start.col + 1 + 1;
+ colnr_T endcol;
+ if (oap->motion_type == kMTBlockWise) {
+ // TODO(timeyyy): refactor extmark_col_adjust to take lnumstart, lnum_end ?
+ endcol = bd.end_vcol + 1;
+ for (lnum = curwin->w_cursor.lnum; lnum <= oap->end.lnum; lnum++) {
+ extmark_col_adjust_delete(curbuf, lnum, mincol, endcol,
+ kExtmarkUndo, 0);
+ }
+
+ // Delete characters within one line,
+ // The case with multiple lines is handled by do_join
+ } else if (oap->motion_type == kMTCharWise && oap->line_count == 1) {
+ // + 1 to change to buf mode, then plus 1 to fit function requirements
+ endcol = oap->end.col + 1 + 1;
+
+ lnum = curwin->w_cursor.lnum;
+ if (oap->is_VIsual == false) {
+ endcol = MAX(endcol - 1, mincol);
+ }
+ }
return OK;
}
@@ -2026,8 +2079,8 @@ bool swapchar(int op_type, pos_T *pos)
pos_T sp = curwin->w_cursor;
curwin->w_cursor = *pos;
- /* don't use del_char(), it also removes composing chars */
- del_bytes(utf_ptr2len(get_cursor_pos_ptr()), FALSE, FALSE);
+ // don't use del_char(), it also removes composing chars
+ del_bytes(utf_ptr2len(get_cursor_pos_ptr()), false, false);
ins_char(nc);
curwin->w_cursor = sp;
} else {
@@ -2100,8 +2153,9 @@ void op_insert(oparg_T *oap, long count1)
* values in "bd". */
if (u_save_cursor() == FAIL)
return;
- for (i = 0; i < bd.endspaces; i++)
+ for (i = 0; i < bd.endspaces; i++) {
ins_char(' ');
+ }
bd.textlen += bd.endspaces;
}
} else {
@@ -2219,6 +2273,10 @@ void op_insert(oparg_T *oap, long count1)
xfree(ins_text);
}
}
+ colnr_T col = oap->start.col;
+ for (linenr_T lnum = oap->start.lnum; lnum <= oap->end.lnum; lnum++) {
+ extmark_col_adjust(curbuf, lnum, col, 0, 1, kExtmarkUndo);
+ }
}
/*
@@ -2689,6 +2747,27 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg)
}
+static void extmarks_do_put(int dir,
+ size_t totlen,
+ MotionType y_type,
+ linenr_T lnum,
+ colnr_T col)
+{
+ // adjust extmarks
+ colnr_T col_amount = (colnr_T)(dir == FORWARD ? totlen-1 : totlen);
+ // Move extmark with char put
+ if (y_type == kMTCharWise) {
+ extmark_col_adjust(curbuf, lnum, col, 0, col_amount, kExtmarkUndo);
+ // Move extmark with blockwise put
+ } else if (y_type == kMTBlockWise) {
+ for (lnum = curbuf->b_op_start.lnum;
+ lnum <= curbuf->b_op_end.lnum;
+ lnum++) {
+ extmark_col_adjust(curbuf, lnum, col, 0, col_amount, kExtmarkUndo);
+ }
+ }
+}
+
/*
* Put contents of register "regname" into the text.
* Caller must check "regname" to be valid!
@@ -2703,8 +2782,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
char_u *oldp;
int yanklen;
size_t totlen = 0; // init for gcc
- linenr_T lnum;
- colnr_T col;
+ linenr_T lnum = 0;
+ colnr_T col = 0;
size_t i; // index in y_array[]
MotionType y_type;
size_t y_size;
@@ -3281,11 +3360,11 @@ error:
curbuf->b_op_start.lnum++;
}
// Skip mark_adjust when adding lines after the last one, there
- // can't be marks there. But still needed in diff mode.
+ // can't be marks there.
if (curbuf->b_op_start.lnum + (y_type == kMTCharWise) - 1 + nr_lines
- < curbuf->b_ml.ml_line_count || curwin->w_p_diff) {
+ < curbuf->b_ml.ml_line_count) {
mark_adjust(curbuf->b_op_start.lnum + (y_type == kMTCharWise),
- (linenr_T)MAXLNUM, nr_lines, 0L, false);
+ (linenr_T)MAXLNUM, nr_lines, 0L, false, kExtmarkUndo);
}
// note changed text for displaying and folding
@@ -3347,6 +3426,8 @@ end:
/* If the cursor is past the end of the line put it at the end. */
adjust_cursor_eol();
+
+ extmarks_do_put(dir, totlen, y_type, lnum, col);
}
/*
@@ -3689,7 +3770,10 @@ int do_join(size_t count,
if (insert_space && t > 0) {
curr = skipwhite(curr);
- if (*curr != ')' && currsize != 0 && endcurr1 != TAB
+ if (*curr != NUL
+ && *curr != ')'
+ && currsize != 0
+ && endcurr1 != TAB
&& (!has_format_option(FO_MBYTE_JOIN)
|| (utf_ptr2char(curr) < 0x100 && endcurr1 < 0x100))
&& (!has_format_option(FO_MBYTE_JOIN2)
@@ -3740,6 +3824,7 @@ int do_join(size_t count,
* column. This is not Vi compatible, but Vi deletes the marks, thus that
* should not really be a problem.
*/
+
for (t = (linenr_T)count - 1;; t--) {
cend -= currsize;
memmove(cend, curr, (size_t)currsize);
@@ -3751,12 +3836,18 @@ int do_join(size_t count,
// If deleting more spaces than adding, the cursor moves no more than
// what is added if it is inside these spaces.
const int spaces_removed = (int)((curr - curr_start) - spaces[t]);
+ linenr_T lnum = curwin->w_cursor.lnum + t;
+ colnr_T mincol = (colnr_T)0;
+ long lnum_amount = (linenr_T)-t;
+ long col_amount = (long)(cend - newp - spaces_removed);
+
+ mark_col_adjust(lnum, mincol, lnum_amount, col_amount, spaces_removed,
+ kExtmarkUndo);
- mark_col_adjust(curwin->w_cursor.lnum + t, (colnr_T)0, (linenr_T)-t,
- (long)(cend - newp - spaces_removed), spaces_removed);
if (t == 0) {
break;
}
+
curr = curr_start = ml_get((linenr_T)(curwin->w_cursor.lnum + t - 1));
if (remove_comments)
curr += comments[t - 1];
@@ -3764,6 +3855,7 @@ int do_join(size_t count,
curr = skipwhite(curr);
currsize = (int)STRLEN(curr);
}
+
ml_replace(curwin->w_cursor.lnum, newp, false);
if (setmark) {
@@ -4184,14 +4276,14 @@ format_lines(
if (next_leader_len > 0) {
(void)del_bytes(next_leader_len, false, false);
mark_col_adjust(curwin->w_cursor.lnum, (colnr_T)0, 0L,
- (long)-next_leader_len, 0);
+ (long)-next_leader_len, 0, kExtmarkNOOP);
} else if (second_indent > 0) { // the "leader" for FO_Q_SECOND
int indent = (int)getwhitecols_curline();
if (indent > 0) {
- (void)del_bytes(indent, FALSE, FALSE);
+ (void)del_bytes(indent, false, false);
mark_col_adjust(curwin->w_cursor.lnum,
- (colnr_T)0, 0L, (long)-indent, 0);
+ (colnr_T)0, 0L, (long)-indent, 0, kExtmarkNOOP);
}
}
curwin->w_cursor.lnum--;
@@ -4279,15 +4371,13 @@ int paragraph_start(linenr_T lnum)
return TRUE; /* after empty line */
do_comments = has_format_option(FO_Q_COMS);
- if (fmt_check_par(lnum - 1
- , &leader_len, &leader_flags, do_comments
- ))
- return TRUE; /* after non-paragraph line */
+ if (fmt_check_par(lnum - 1, &leader_len, &leader_flags, do_comments)) {
+ return true; // after non-paragraph line
+ }
- if (fmt_check_par(lnum
- , &next_leader_len, &next_leader_flags, do_comments
- ))
- return TRUE; /* "lnum" is not a paragraph line */
+ if (fmt_check_par(lnum, &next_leader_len, &next_leader_flags, do_comments)) {
+ return true; // "lnum" is not a paragraph line
+ }
if (has_format_option(FO_WHITE_PAR) && !ends_in_white(lnum - 1))
return TRUE; /* missing trailing space in previous line. */
@@ -4536,7 +4626,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd)
int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
{
int col;
- char_u *buf1;
+ char_u *buf1 = NULL;
char_u buf2[NUMBUFLEN];
int pre; // 'X' or 'x': hex; '0': octal; 'B' or 'b': bin
static bool hexupper = false; // 0xABC
@@ -4641,7 +4731,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
if (visual) {
while (ptr[col] != NUL && length > 0 && !ascii_isdigit(ptr[col])
&& !(doalp && ASCII_ISALPHA(ptr[col]))) {
- int mb_len = MB_PTR2LEN(ptr + col);
+ int mb_len = utfc_ptr2len(ptr + col);
col += mb_len;
length -= mb_len;
@@ -4845,7 +4935,6 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
*ptr = NUL;
STRCAT(buf1, buf2);
ins_str(buf1); // insert the new number
- xfree(buf1);
endpos = curwin->w_cursor;
if (curwin->w_cursor.col) {
curwin->w_cursor.col--;
@@ -4860,6 +4949,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
}
theend:
+ xfree(buf1);
if (visual) {
curwin->w_cursor = save_cursor;
} else if (did_change) {
@@ -5114,8 +5204,7 @@ void write_reg_contents_lst(int name, char_u **strings,
/// write_reg_contents_ex - store `str` in register `name`
///
-/// If `str` ends in '\n' or '\r', use linewise, otherwise use
-/// characterwise.
+/// If `str` ends in '\n' or '\r', use linewise, otherwise use charwise.
///
/// @warning when `name` is '/', `len` and `must_append` are ignored. This
/// means that `str` MUST be NUL-terminated.
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 3ccc67eb14..e48ed201e8 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -133,6 +133,7 @@ static char_u *p_cms;
static char_u *p_cpt;
static char_u *p_cfu;
static char_u *p_ofu;
+static char_u *p_tfu;
static int p_eol;
static int p_fixeol;
static int p_et;
@@ -2183,6 +2184,7 @@ static void didset_options(void)
(void)opt_strings_flags(p_tc, p_tc_values, &tc_flags, false);
(void)opt_strings_flags(p_ve, p_ve_values, &ve_flags, true);
(void)opt_strings_flags(p_wop, p_wop_values, &wop_flags, true);
+ (void)opt_strings_flags(p_jop, p_jop_values, &jop_flags, true);
(void)spell_check_msm();
(void)spell_check_sps();
(void)compile_cap_prog(curwin->w_s);
@@ -2204,10 +2206,10 @@ static void didset_options2(void)
(void)opt_strings_flags(p_cb, p_cb_values, &cb_flags, true);
// Parse default for 'fillchars'.
- (void)set_chars_option(curwin, &curwin->w_p_fcs);
+ (void)set_chars_option(curwin, &curwin->w_p_fcs, true);
// Parse default for 'listchars'.
- (void)set_chars_option(curwin, &curwin->w_p_lcs);
+ (void)set_chars_option(curwin, &curwin->w_p_lcs, true);
// Parse default for 'wildmode'.
check_opt_wim();
@@ -2273,6 +2275,7 @@ void check_buf_options(buf_T *buf)
check_string_option(&buf->b_p_ep);
check_string_option(&buf->b_p_path);
check_string_option(&buf->b_p_tags);
+ check_string_option(&buf->b_p_tfu);
check_string_option(&buf->b_p_tc);
check_string_option(&buf->b_p_dict);
check_string_option(&buf->b_p_tsr);
@@ -2630,6 +2633,10 @@ did_set_string_option(
if (strcmp((char *)(*varp), HIGHLIGHT_INIT) != 0) {
errmsg = e_unsupportedoption;
}
+ } else if (varp == &p_jop) { // 'jumpoptions'
+ if (opt_strings_flags(p_jop, p_jop_values, &jop_flags, true) != OK) {
+ errmsg = e_invarg;
+ }
} else if (gvarp == &p_nf) { // 'nrformats'
if (check_opt_strings(*varp, p_nf_values, true) != OK) {
errmsg = e_invarg;
@@ -2661,11 +2668,11 @@ did_set_string_option(
errmsg = e_invarg;
} else {
FOR_ALL_TAB_WINDOWS(tp, wp) {
- if (set_chars_option(wp, &wp->w_p_lcs) != NULL) {
+ if (set_chars_option(wp, &wp->w_p_lcs, true) != NULL) {
errmsg = (char_u *)_("E834: Conflicts with value of 'listchars'");
goto ambw_end;
}
- if (set_chars_option(wp, &wp->w_p_fcs) != NULL) {
+ if (set_chars_option(wp, &wp->w_p_fcs, true) != NULL) {
errmsg = (char_u *)_("E835: Conflicts with value of 'fillchars'");
goto ambw_end;
}
@@ -2866,10 +2873,26 @@ ambw_end:
}
s = skip_to_option_part(s);
}
- } else if (varp == &curwin->w_p_lcs) { // 'listchars'
- errmsg = set_chars_option(curwin, varp);
- } else if (varp == &curwin->w_p_fcs) { // 'fillchars'
- errmsg = set_chars_option(curwin, varp);
+ } else if (varp == &p_lcs) { // 'listchars'
+ errmsg = set_chars_option(curwin, varp, false);
+ if (!errmsg) {
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ set_chars_option(wp, &wp->w_p_lcs, true);
+ }
+ }
+ redraw_all_later(NOT_VALID);
+ } else if (varp == &curwin->w_p_lcs) { // local 'listchars'
+ errmsg = set_chars_option(curwin, varp, true);
+ } else if (varp == &p_fcs) { // 'fillchars'
+ errmsg = set_chars_option(curwin, varp, false);
+ if (!errmsg) {
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ set_chars_option(wp, &wp->w_p_fcs, true);
+ }
+ }
+ redraw_all_later(NOT_VALID);
+ } else if (varp == &curwin->w_p_fcs) { // local 'fillchars'
+ errmsg = set_chars_option(curwin, varp, true);
} else if (varp == &p_cedit) { // 'cedit'
errmsg = check_cedit();
} else if (varp == &p_vfile) { // 'verbosefile'
@@ -3499,7 +3522,7 @@ skip:
///
/// @param varp either &curwin->w_p_lcs or &curwin->w_p_fcs
/// @return error message, NULL if it's OK.
-static char_u *set_chars_option(win_T *wp, char_u **varp)
+static char_u *set_chars_option(win_T *wp, char_u **varp, bool set)
{
int round, i, len, entries;
char_u *p, *s;
@@ -3534,12 +3557,18 @@ static char_u *set_chars_option(win_T *wp, char_u **varp)
{ &wp->w_p_lcs_chars.conceal, "conceal", NUL },
};
- if (varp == &wp->w_p_lcs) {
+ if (varp == &p_lcs || varp == &wp->w_p_lcs) {
tab = lcs_tab;
entries = ARRAY_SIZE(lcs_tab);
+ if (varp == &wp->w_p_lcs && wp->w_p_lcs[0] == NUL) {
+ varp = &p_lcs;
+ }
} else {
tab = fcs_tab;
entries = ARRAY_SIZE(fcs_tab);
+ if (varp == &wp->w_p_fcs && wp->w_p_fcs[0] == NUL) {
+ varp = &p_fcs;
+ }
if (*p_ambw == 'd') {
// XXX: If ambiwidth=double then "|" and "·" take 2 columns, which is
// forbidden (TUI limitation?). Set old defaults.
@@ -3552,7 +3581,7 @@ static char_u *set_chars_option(win_T *wp, char_u **varp)
}
// first round: check for valid value, second round: assign values
- for (round = 0; round <= 1; round++) {
+ for (round = 0; round <= set ? 1 : 0; round++) {
if (round > 0) {
// After checking that the value is valid: set defaults
for (i = 0; i < entries; i++) {
@@ -3560,7 +3589,7 @@ static char_u *set_chars_option(win_T *wp, char_u **varp)
*(tab[i].cp) = tab[i].def;
}
}
- if (varp == &wp->w_p_lcs) {
+ if (varp == &p_lcs || varp == &wp->w_p_lcs) {
wp->w_p_lcs_chars.tab1 = NUL;
wp->w_p_lcs_chars.tab3 = NUL;
}
@@ -4306,6 +4335,10 @@ static char *set_num_option(int opt_idx, char_u *varp, long value,
if (value < 0) {
errmsg = e_positive;
}
+ } else if (pp == &p_wd) {
+ if (value < 0) {
+ errmsg = e_positive;
+ }
}
// Don't change the value and return early if validation failed.
@@ -5167,6 +5200,13 @@ void ui_refresh_options(void)
}
ui_call_option_set(name, value);
}
+ if (p_mouse != NULL) {
+ if (*p_mouse == NUL) {
+ ui_call_mouse_off();
+ } else {
+ setmouse();
+ }
+ }
}
/*
@@ -5556,6 +5596,16 @@ void unset_global_local_option(char *name, void *from)
case PV_MENC:
clear_string_option(&buf->b_p_menc);
break;
+ case PV_LCS:
+ clear_string_option(&((win_T *)from)->w_p_lcs);
+ set_chars_option((win_T *)from, &((win_T *)from)->w_p_lcs, true);
+ redraw_win_later((win_T *)from, NOT_VALID);
+ break;
+ case PV_FCS:
+ clear_string_option(&((win_T *)from)->w_p_fcs);
+ set_chars_option((win_T *)from, &((win_T *)from)->w_p_fcs, true);
+ redraw_win_later((win_T *)from, NOT_VALID);
+ break;
}
}
@@ -5586,11 +5636,14 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags)
case PV_INC: return (char_u *)&(curbuf->b_p_inc);
case PV_DICT: return (char_u *)&(curbuf->b_p_dict);
case PV_TSR: return (char_u *)&(curbuf->b_p_tsr);
+ case PV_TFU: return (char_u *)&(curbuf->b_p_tfu);
case PV_STL: return (char_u *)&(curwin->w_p_stl);
case PV_UL: return (char_u *)&(curbuf->b_p_ul);
case PV_LW: return (char_u *)&(curbuf->b_p_lw);
case PV_BKC: return (char_u *)&(curbuf->b_p_bkc);
case PV_MENC: return (char_u *)&(curbuf->b_p_menc);
+ case PV_FCS: return (char_u *)&(curwin->w_p_fcs);
+ case PV_LCS: return (char_u *)&(curwin->w_p_lcs);
}
return NULL; // "cannot happen"
}
@@ -5649,6 +5702,10 @@ static char_u *get_varp(vimoption_T *p)
? (char_u *)&(curbuf->b_p_lw) : p->var;
case PV_MENC: return *curbuf->b_p_menc != NUL
? (char_u *)&(curbuf->b_p_menc) : p->var;
+ case PV_FCS: return *curwin->w_p_fcs != NUL
+ ? (char_u *)&(curwin->w_p_fcs) : p->var;
+ case PV_LCS: return *curwin->w_p_lcs != NUL
+ ? (char_u *)&(curwin->w_p_lcs) : p->var;
case PV_ARAB: return (char_u *)&(curwin->w_p_arab);
case PV_LIST: return (char_u *)&(curwin->w_p_list);
@@ -5738,6 +5795,7 @@ static char_u *get_varp(vimoption_T *p)
case PV_SPF: return (char_u *)&(curwin->w_s->b_p_spf);
case PV_SPL: return (char_u *)&(curwin->w_s->b_p_spl);
case PV_SW: return (char_u *)&(curbuf->b_p_sw);
+ case PV_TFU: return (char_u *)&(curbuf->b_p_tfu);
case PV_TS: return (char_u *)&(curbuf->b_p_ts);
case PV_TW: return (char_u *)&(curbuf->b_p_tw);
case PV_UDF: return (char_u *)&(curbuf->b_p_udf);
@@ -5745,8 +5803,6 @@ static char_u *get_varp(vimoption_T *p)
case PV_KMAP: return (char_u *)&(curbuf->b_p_keymap);
case PV_SCL: return (char_u *)&(curwin->w_p_scl);
case PV_WINHL: return (char_u *)&(curwin->w_p_winhl);
- case PV_FCS: return (char_u *)&(curwin->w_p_fcs);
- case PV_LCS: return (char_u *)&(curwin->w_p_lcs);
case PV_WINBL: return (char_u *)&(curwin->w_p_winbl);
default: IEMSG(_("E356: get_varp ERROR"));
}
@@ -5888,8 +5944,8 @@ void didset_window_options(win_T *wp)
{
check_colorcolumn(wp);
briopt_check(wp);
- set_chars_option(wp, &wp->w_p_fcs);
- set_chars_option(wp, &wp->w_p_lcs);
+ set_chars_option(wp, &wp->w_p_fcs, true);
+ set_chars_option(wp, &wp->w_p_lcs, true);
parse_winhl_opt(wp); // sets w_hl_needs_update also for w_p_winbl
wp->w_grid.blending = wp->w_p_winbl > 0;
}
@@ -6000,6 +6056,7 @@ void buf_copy_options(buf_T *buf, int flags)
buf->b_p_cpt = vim_strsave(p_cpt);
buf->b_p_cfu = vim_strsave(p_cfu);
buf->b_p_ofu = vim_strsave(p_ofu);
+ buf->b_p_tfu = vim_strsave(p_tfu);
buf->b_p_sts = p_sts;
buf->b_p_sts_nopaste = p_sts_nopaste;
buf->b_p_com = vim_strsave(p_com);
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index 08df495250..4f9f32794b 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -473,6 +473,12 @@ EXTERN char_u *p_isf; // 'isfname'
EXTERN char_u *p_isi; // 'isident'
EXTERN char_u *p_isp; // 'isprint'
EXTERN int p_js; // 'joinspaces'
+EXTERN char_u *p_jop; // 'jumpooptions'
+EXTERN unsigned jop_flags;
+#ifdef IN_OPTION_C
+static char *(p_jop_values[]) = { "stack", NULL };
+#endif
+#define JOP_STACK 0x01
EXTERN char_u *p_kp; // 'keywordprg'
EXTERN char_u *p_km; // 'keymodel'
EXTERN char_u *p_langmap; // 'langmap'
@@ -484,6 +490,7 @@ EXTERN long p_linespace; // 'linespace'
EXTERN char_u *p_lispwords; // 'lispwords'
EXTERN long p_ls; // 'laststatus'
EXTERN long p_stal; // 'showtabline'
+EXTERN char_u *p_lcs; // 'listchars'
EXTERN int p_lz; // 'lazyredraw'
EXTERN int p_lpl; // 'loadplugins'
@@ -518,10 +525,18 @@ EXTERN long p_pyx; // 'pyxversion'
EXTERN char_u *p_rdb; // 'redrawdebug'
EXTERN unsigned rdb_flags;
# ifdef IN_OPTION_C
-static char *(p_rdb_values[]) = { "compositor", "nothrottle", NULL };
+static char *(p_rdb_values[]) = {
+ "compositor",
+ "nothrottle",
+ "invalid",
+ "nodelta",
+ NULL
+};
# endif
# define RDB_COMPOSITOR 0x001
# define RDB_NOTHROTTLE 0x002
+# define RDB_INVALID 0x004
+# define RDB_NODELTA 0x008
EXTERN long p_rdt; // 'redrawtime'
EXTERN int p_remap; // 'remap'
@@ -603,13 +618,14 @@ EXTERN char_u *p_swb; // 'switchbuf'
EXTERN unsigned swb_flags;
#ifdef IN_OPTION_C
static char *(p_swb_values[]) =
- { "useopen", "usetab", "split", "newtab", "vsplit", NULL };
+ { "useopen", "usetab", "split", "newtab", "vsplit", "uselast", NULL };
#endif
#define SWB_USEOPEN 0x001
#define SWB_USETAB 0x002
#define SWB_SPLIT 0x004
#define SWB_NEWTAB 0x008
#define SWB_VSPLIT 0x010
+#define SWB_USELAST 0x020
EXTERN int p_tbs; ///< 'tagbsearch'
EXTERN char_u *p_tc; ///< 'tagcase'
EXTERN unsigned tc_flags; ///< flags from 'tagcase'
@@ -644,6 +660,7 @@ EXTERN long p_ul; ///< 'undolevels'
EXTERN long p_ur; ///< 'undoreload'
EXTERN long p_uc; ///< 'updatecount'
EXTERN long p_ut; ///< 'updatetime'
+EXTERN char_u *p_fcs; ///< 'fillchar'
EXTERN char_u *p_shada; ///< 'shada'
EXTERN char *p_shadafile; ///< 'shadafile'
EXTERN char_u *p_vdir; ///< 'viewdir'
@@ -772,6 +789,7 @@ enum {
, BV_SUA
, BV_SW
, BV_SWF
+ , BV_TFU
, BV_TAGS
, BV_TC
, BV_TS
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 52e788944b..93bfc1c0b1 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -612,7 +612,7 @@ return {
alloced=true,
redraw={'current_window'},
varname='p_dip',
- defaults={if_true={vi="internal,filler"}}
+ defaults={if_true={vi="internal,filler,closeoff"}}
},
{
full_name='digraph', abbreviation='dg',
@@ -804,11 +804,12 @@ return {
},
{
full_name='fillchars', abbreviation='fcs',
- type='string', list='onecomma', scope={'window'},
+ type='string', list='onecomma', scope={'global', 'window'},
deny_duplicates=true,
vi_def=true,
alloced=true,
redraw={'current_window'},
+ varname='p_fcs',
defaults={if_true={vi=''}}
},
{
@@ -1299,6 +1300,14 @@ return {
defaults={if_true={vi=true}}
},
{
+ full_name='jumpoptions', abbreviation='jop',
+ type='string', list='onecomma', scope={'global'},
+ deny_duplicates=true,
+ varname='p_jop',
+ vim=true,
+ defaults={if_true={vim=''}}
+ },
+ {
full_name='keymap', abbreviation='kmp',
type='string', scope={'buffer'},
normal_fname_chars=true,
@@ -1420,11 +1429,12 @@ return {
},
{
full_name='listchars', abbreviation='lcs',
- type='string', list='onecomma', scope={'window'},
+ type='string', list='onecomma', scope={'global', 'window'},
deny_duplicates=true,
vim=true,
alloced=true,
redraw={'current_window'},
+ varname='p_lcs',
defaults={if_true={vi="eol:$", vim="tab:> ,trail:-,nbsp:+"}}
},
{
@@ -2323,9 +2333,9 @@ return {
full_name='startofline', abbreviation='sol',
type='bool', scope={'global'},
vi_def=true,
- vim=true,
+ vim=false,
varname='p_sol',
- defaults={if_true={vi=true}}
+ defaults={if_true={vi=false}}
},
{
full_name='statusline', abbreviation='stl',
@@ -2389,6 +2399,14 @@ return {
defaults={if_true={vi=""}}
},
{
+ full_name='tagfunc', abbreviation='tfu',
+ type='string', scope={'buffer'},
+ vim=true,
+ vi_def=true,
+ varname='p_tfu',
+ defaults={if_true={vi=""}}
+ },
+ {
full_name='tabline', abbreviation='tal',
type='string', scope={'global'},
vi_def=true,
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
index f5dbf0694e..360609c50d 100644
--- a/src/nvim/os/env.c
+++ b/src/nvim/os/env.c
@@ -44,6 +44,16 @@ void env_init(void)
uv_mutex_init(&mutex);
}
+void os_env_var_lock(void)
+{
+ uv_mutex_lock(&mutex);
+}
+
+void os_env_var_unlock(void)
+{
+ uv_mutex_unlock(&mutex);
+}
+
/// Like getenv(), but returns NULL if the variable is empty.
/// @see os_env_exists
const char *os_getenv(const char *name)
@@ -55,6 +65,7 @@ const char *os_getenv(const char *name)
return NULL;
}
uv_mutex_lock(&mutex);
+ int r = 0;
if (pmap_has(cstr_t)(envmap, name)
&& !!(e = (char *)pmap_get(cstr_t)(envmap, name))) {
if (e[0] != '\0') {
@@ -67,7 +78,7 @@ const char *os_getenv(const char *name)
pmap_del2(envmap, name);
}
e = xmalloc(size);
- int r = uv_os_getenv(name, e, &size);
+ r = uv_os_getenv(name, e, &size);
if (r == UV_ENOBUFS) {
e = xrealloc(e, size);
r = uv_os_getenv(name, e, &size);
@@ -75,14 +86,15 @@ const char *os_getenv(const char *name)
if (r != 0 || size == 0 || e[0] == '\0') {
xfree(e);
e = NULL;
- if (r != 0 && r != UV_ENOENT && r != UV_UNKNOWN) {
- ELOG("uv_os_getenv(%s) failed: %d %s", name, r, uv_err_name(r));
- }
goto end;
}
pmap_put(cstr_t)(envmap, xstrdup(name), e);
end:
+ // Must do this before ELOG, log.c may call os_setenv.
uv_mutex_unlock(&mutex);
+ if (r != 0 && r != UV_ENOENT && r != UV_UNKNOWN) {
+ ELOG("uv_os_getenv(%s) failed: %d %s", name, r, uv_err_name(r));
+ }
return (e == NULL || size == 0 || e[0] == '\0') ? NULL : e;
}
@@ -102,9 +114,6 @@ bool os_env_exists(const char *name)
assert(r != UV_EINVAL);
if (r != 0 && r != UV_ENOENT && r != UV_ENOBUFS) {
ELOG("uv_os_getenv(%s) failed: %d %s", name, r, uv_err_name(r));
-#ifdef WIN32
- return (r == UV_UNKNOWN);
-#endif
}
return (r == 0 || r == UV_ENOBUFS);
}
@@ -135,15 +144,25 @@ int os_setenv(const char *name, const char *value, int overwrite)
}
#endif
uv_mutex_lock(&mutex);
- int r = uv_os_setenv(name, value);
+ int r;
+#ifdef WIN32
+ // libintl uses getenv() for LC_ALL/LANG/etc., so we must use _putenv_s().
+ if (striequal(name, "LC_ALL") || striequal(name, "LANGUAGE")
+ || striequal(name, "LANG") || striequal(name, "LC_MESSAGES")) {
+ r = _putenv_s(name, value); // NOLINT
+ assert(r == 0);
+ }
+#endif
+ r = uv_os_setenv(name, value);
assert(r != UV_EINVAL);
// Destroy the old map item. Do this AFTER uv_os_setenv(), because `value`
// could be a previous os_getenv() result.
pmap_del2(envmap, name);
+ // Must do this before ELOG, log.c may call os_setenv.
+ uv_mutex_unlock(&mutex);
if (r != 0) {
ELOG("uv_os_setenv(%s) failed: %d %s", name, r, uv_err_name(r));
}
- uv_mutex_unlock(&mutex);
return r == 0 ? 0 : -1;
}
@@ -157,46 +176,150 @@ int os_unsetenv(const char *name)
uv_mutex_lock(&mutex);
pmap_del2(envmap, name);
int r = uv_os_unsetenv(name);
+ // Must do this before ELOG, log.c may call os_setenv.
+ uv_mutex_unlock(&mutex);
if (r != 0) {
ELOG("uv_os_unsetenv(%s) failed: %d %s", name, r, uv_err_name(r));
}
- uv_mutex_unlock(&mutex);
return r == 0 ? 0 : -1;
}
+/// Returns number of variables in the current environment variables block
+size_t os_get_fullenv_size(void)
+{
+ size_t len = 0;
+#ifdef _WIN32
+ wchar_t *envstrings = GetEnvironmentStringsW();
+ wchar_t *p = envstrings;
+ size_t l;
+ if (!envstrings) {
+ return len;
+ }
+ // GetEnvironmentStringsW() result has this format:
+ // var1=value1\0var2=value2\0...varN=valueN\0\0
+ while ((l = wcslen(p)) != 0) {
+ p += l + 1;
+ len++;
+ }
+
+ FreeEnvironmentStringsW(envstrings);
+#else
+# if defined(HAVE__NSGETENVIRON)
+ char **environ = *_NSGetEnviron();
+# else
+ extern char **environ;
+# endif
+
+ while (environ[len] != NULL) {
+ len++;
+ }
+
+#endif
+ return len;
+}
+
+void os_free_fullenv(char **env)
+{
+ if (!env) { return; }
+ for (char **it = env; *it; it++) {
+ XFREE_CLEAR(*it);
+ }
+ xfree(env);
+}
+
+/// Copies the current environment variables into the given array, `env`. Each
+/// array element is of the form "NAME=VALUE".
+/// Result must be freed by the caller.
+///
+/// @param[out] env array to populate with environment variables
+/// @param env_size size of `env`, @see os_fullenv_size
+void os_copy_fullenv(char **env, size_t env_size)
+{
+#ifdef _WIN32
+ wchar_t *envstrings = GetEnvironmentStringsW();
+ if (!envstrings) {
+ return;
+ }
+ wchar_t *p = envstrings;
+ size_t i = 0;
+ size_t l;
+ // GetEnvironmentStringsW() result has this format:
+ // var1=value1\0var2=value2\0...varN=valueN\0\0
+ while ((l = wcslen(p)) != 0 && i < env_size) {
+ char *utf8_str;
+ int conversion_result = utf16_to_utf8(p, -1, &utf8_str);
+ if (conversion_result != 0) {
+ EMSG2("utf16_to_utf8 failed: %d", conversion_result);
+ break;
+ }
+ p += l + 1;
+
+ env[i] = utf8_str;
+ i++;
+ }
+
+ FreeEnvironmentStringsW(envstrings);
+#else
+# if defined(HAVE__NSGETENVIRON)
+ char **environ = *_NSGetEnviron();
+# else
+ extern char **environ;
+# endif
+
+ size_t i = 0;
+ while (environ[i] != NULL && i < env_size) {
+ env[i] = xstrdup(environ[i]);
+ i++;
+ }
+#endif
+}
+
+/// Copy value of the environment variable at `index` in the current
+/// environment variables block.
+/// Result must be freed by the caller.
+///
+/// @param index nth item in environment variables block
+/// @return [allocated] environment variable's value, or NULL
char *os_getenvname_at_index(size_t index)
{
#ifdef _WIN32
- wchar_t *env = GetEnvironmentStringsW();
- if (!env) {
+ wchar_t *envstrings = GetEnvironmentStringsW();
+ if (!envstrings) {
return NULL;
}
+ wchar_t *p = envstrings;
char *name = NULL;
- size_t current_index = 0;
+ size_t i = 0;
+ size_t l;
// GetEnvironmentStringsW() result has this format:
// var1=value1\0var2=value2\0...varN=valueN\0\0
- for (wchar_t *it = env; *it != L'\0' || *(it + 1) != L'\0'; it++) {
- if (index == current_index) {
+ while ((l = wcslen(p)) != 0 && i <= index) {
+ if (i == index) {
char *utf8_str;
- int conversion_result = utf16_to_utf8(it, -1, &utf8_str);
+ int conversion_result = utf16_to_utf8(p, -1, &utf8_str);
if (conversion_result != 0) {
EMSG2("utf16_to_utf8 failed: %d", conversion_result);
break;
}
- size_t namesize = 0;
- while (utf8_str[namesize] != '=' && utf8_str[namesize] != NUL) {
- namesize++;
- }
- name = (char *)vim_strnsave((char_u *)utf8_str, namesize);
+
+ // Some Windows env vars start with =, so skip over that to find the
+ // separator between name/value
+ const char * const end = strchr(utf8_str + (utf8_str[0] == '=' ? 1 : 0),
+ '=');
+ assert(end != NULL);
+ ptrdiff_t len = end - utf8_str;
+ assert(len > 0);
+ name = xstrndup(utf8_str, (size_t)len);
xfree(utf8_str);
break;
}
- if (*it == L'\0') {
- current_index++;
- }
+
+ // Advance past the name and NUL
+ p += l + 1;
+ i++;
}
- FreeEnvironmentStringsW(env);
+ FreeEnvironmentStringsW(envstrings);
return name;
#else
# if defined(HAVE__NSGETENVIRON)
@@ -204,19 +327,20 @@ char *os_getenvname_at_index(size_t index)
# else
extern char **environ;
# endif
- // Check if index is inside the environ array and is not the last element.
+
+ // check if index is inside the environ array
for (size_t i = 0; i <= index; i++) {
if (environ[i] == NULL) {
return NULL;
}
}
char *str = environ[index];
- size_t namesize = 0;
- while (str[namesize] != '=' && str[namesize] != NUL) {
- namesize++;
- }
- char *name = (char *)vim_strnsave((char_u *)str, namesize);
- return name;
+ assert(str != NULL);
+ const char * const end = strchr(str, '=');
+ assert(end != NULL);
+ ptrdiff_t len = end - str;
+ assert(len > 0);
+ return xstrndup(str, (size_t)len);
#endif
}
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c
index 6956410401..f4377b1457 100644
--- a/src/nvim/os/shell.c
+++ b/src/nvim/os/shell.c
@@ -423,10 +423,11 @@ static bool out_data_decide_throttle(size_t size)
pulse_msg[1] = (tick > 1) ? '.' : ' ';
pulse_msg[2] = (tick > 2) ? '.' : ' ';
if (visit == 1) {
- msg_putchar('\n');
+ msg_puts("[...]\n");
}
msg_putchar('\r'); // put cursor at start of line
msg_puts(pulse_msg);
+ msg_putchar('\r');
ui_flush();
return true;
}
diff --git a/src/nvim/os/tty.c b/src/nvim/os/tty.c
index bd5b9b4506..4f525bed9a 100644
--- a/src/nvim/os/tty.c
+++ b/src/nvim/os/tty.c
@@ -6,6 +6,7 @@
//
#include "nvim/os/os.h"
+#include "nvim/os/tty.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/tty.c.generated.h"
diff --git a/src/nvim/os/users.c b/src/nvim/os/users.c
index c6463c2c92..9374693550 100644
--- a/src/nvim/os/users.c
+++ b/src/nvim/os/users.c
@@ -13,6 +13,25 @@
#ifdef HAVE_PWD_H
# include <pwd.h>
#endif
+#ifdef WIN32
+# include <lm.h>
+#endif
+
+// Add a user name to the list of users in garray_T *users.
+// Do nothing if user name is NULL or empty.
+static void add_user(garray_T *users, char *user, bool need_copy)
+{
+ char *user_copy = (user != NULL && need_copy)
+ ? xstrdup(user) : user;
+
+ if (user_copy == NULL || *user_copy == NUL) {
+ if (need_copy) {
+ xfree(user);
+ }
+ return;
+ }
+ GA_APPEND(char *, users, user_copy);
+}
// Initialize users garray and fill it with os usernames.
// Return Ok for success, FAIL for failure.
@@ -24,16 +43,66 @@ int os_get_usernames(garray_T *users)
ga_init(users, sizeof(char *), 20);
# if defined(HAVE_GETPWENT) && defined(HAVE_PWD_H)
- struct passwd *pw;
+ {
+ struct passwd *pw;
+
+ setpwent();
+ while ((pw = getpwent()) != NULL) {
+ add_user(users, pw->pw_name, true);
+ }
+ endpwent();
+ }
+# elif defined(WIN32)
+ {
+ DWORD nusers = 0, ntotal = 0, i;
+ PUSER_INFO_0 uinfo;
+
+ if (NetUserEnum(NULL, 0, 0, (LPBYTE *)&uinfo, MAX_PREFERRED_LENGTH,
+ &nusers, &ntotal, NULL) == NERR_Success) {
+ for (i = 0; i < nusers; i++) {
+ char *user;
+ int conversion_result = utf16_to_utf8(uinfo[i].usri0_name, -1, &user);
+ if (conversion_result != 0) {
+ EMSG2("utf16_to_utf8 failed: %d", conversion_result);
+ break;
+ }
+ add_user(users, user, false);
+ }
+
+ NetApiBufferFree(uinfo);
+ }
+ }
+# endif
+# if defined(HAVE_GETPWNAM)
+ {
+ const char *user_env = os_getenv("USER");
+
+ // The $USER environment variable may be a valid remote user name (NIS,
+ // LDAP) not already listed by getpwent(), as getpwent() only lists
+ // local user names. If $USER is not already listed, check whether it
+ // is a valid remote user name using getpwnam() and if it is, add it to
+ // the list of user names.
+
+ if (user_env != NULL && *user_env != NUL) {
+ int i;
+
+ for (i = 0; i < users->ga_len; i++) {
+ char *local_user = ((char **)users->ga_data)[i];
+
+ if (STRCMP(local_user, user_env) == 0) {
+ break;
+ }
+ }
+
+ if (i == users->ga_len) {
+ struct passwd *pw = getpwnam(user_env); // NOLINT
- setpwent();
- while ((pw = getpwent()) != NULL) {
- // pw->pw_name shouldn't be NULL but just in case...
- if (pw->pw_name != NULL) {
- GA_APPEND(char *, users, xstrdup(pw->pw_name));
+ if (pw != NULL) {
+ add_user(users, pw->pw_name, true);
+ }
+ }
}
}
- endpwent();
# endif
return OK;
diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c
index ded575529f..0f44df2188 100644
--- a/src/nvim/os_unix.c
+++ b/src/nvim/os_unix.c
@@ -412,7 +412,7 @@ int mch_expand_wildcards(int num_pat, char_u **pat, int *num_file,
fseek(fd, 0L, SEEK_SET);
buffer = xmalloc(len + 1);
// fread() doesn't terminate buffer with NUL;
- // appropiate termination (not always NUL) is done below.
+ // appropriate termination (not always NUL) is done below.
size_t readlen = fread((char *)buffer, 1, len, fd);
fclose(fd);
os_remove((char *)tempname);
diff --git a/src/nvim/path.c b/src/nvim/path.c
index 1c787e3a1d..a53870acb8 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -51,9 +51,10 @@
/// expanded.
/// @param s2 Second file name.
/// @param checkname When both files don't exist, only compare their names.
+/// @param expandenv Whether to expand environment variables in file names.
/// @return Enum of type FileComparison. @see FileComparison.
FileComparison path_full_compare(char_u *const s1, char_u *const s2,
- const bool checkname)
+ const bool checkname, const bool expandenv)
{
assert(s1 && s2);
char_u exp1[MAXPATHL];
@@ -61,7 +62,11 @@ FileComparison path_full_compare(char_u *const s1, char_u *const s2,
char_u full2[MAXPATHL];
FileID file_id_1, file_id_2;
- expand_env(s1, exp1, MAXPATHL);
+ if (expandenv) {
+ expand_env(s1, exp1, MAXPATHL);
+ } else {
+ xstrlcpy((char *)exp1, (const char *)s1, MAXPATHL - 1);
+ }
bool id_ok_1 = os_fileid((char *)exp1, &file_id_1);
bool id_ok_2 = os_fileid((char *)s2, &file_id_2);
if (!id_ok_1 && !id_ok_2) {
@@ -333,9 +338,9 @@ int path_fnamencmp(const char *const fname1, const char *const fname2,
&& (p_fic ? (c1 != c2 && CH_FOLD(c1) != CH_FOLD(c2)) : c1 != c2)) {
break;
}
- len -= (size_t)MB_PTR2LEN((const char_u *)p1);
- p1 += MB_PTR2LEN((const char_u *)p1);
- p2 += MB_PTR2LEN((const char_u *)p2);
+ len -= (size_t)utfc_ptr2len((const char_u *)p1);
+ p1 += utfc_ptr2len((const char_u *)p1);
+ p2 += utfc_ptr2len((const char_u *)p2);
}
return c1 - c2;
#else
@@ -1203,7 +1208,7 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file,
}
} else {
// First expand environment variables, "~/" and "~user/".
- if (has_env_var(p) || *p == '~') {
+ if ((has_env_var(p) && !(flags & EW_NOTENV)) || *p == '~') {
p = expand_env_save_opt(p, true);
if (p == NULL)
p = pat[i];
@@ -1905,16 +1910,16 @@ int pathcmp(const char *p, const char *q, int maxlen)
: c1 - c2; // no match
}
- i += MB_PTR2LEN((char_u *)p + i);
- j += MB_PTR2LEN((char_u *)q + j);
+ i += utfc_ptr2len((char_u *)p + i);
+ j += utfc_ptr2len((char_u *)q + j);
}
if (s == NULL) { // "i" or "j" ran into "maxlen"
return 0;
}
c1 = PTR2CHAR((char_u *)s + i);
- c2 = PTR2CHAR((char_u *)s + i + MB_PTR2LEN((char_u *)s + i));
- /* ignore a trailing slash, but not "//" or ":/" */
+ c2 = PTR2CHAR((char_u *)s + i + utfc_ptr2len((char_u *)s + i));
+ // ignore a trailing slash, but not "//" or ":/"
if (c2 == NUL
&& i > 0
&& !after_pathsep((char *)s, (char *)s + i)
@@ -1923,10 +1928,12 @@ int pathcmp(const char *p, const char *q, int maxlen)
#else
&& c1 == '/'
#endif
- )
- return 0; /* match with trailing slash */
- if (s == q)
- return -1; /* no match */
+ ) {
+ return 0; // match with trailing slash
+ }
+ if (s == q) {
+ return -1; // no match
+ }
return 1;
}
diff --git a/src/nvim/path.h b/src/nvim/path.h
index 4e466d1b71..15abd19646 100644
--- a/src/nvim/path.h
+++ b/src/nvim/path.h
@@ -20,11 +20,12 @@
#define EW_KEEPDOLLAR 0x800 /* do not escape $, $var is expanded */
/* Note: mostly EW_NOTFOUND and EW_SILENT are mutually exclusive: EW_NOTFOUND
* is used when executing commands and EW_SILENT for interactive expanding. */
-#define EW_ALLLINKS 0x1000 // also links not pointing to existing file
-#define EW_SHELLCMD 0x2000 // called from expand_shellcmd(), don't check
- // if executable is in $PATH
-#define EW_DODOT 0x4000 // also files starting with a dot
-#define EW_EMPTYOK 0x8000 // no matches is not an error
+#define EW_ALLLINKS 0x1000 // also links not pointing to existing file
+#define EW_SHELLCMD 0x2000 // called from expand_shellcmd(), don't check
+ // if executable is in $PATH
+#define EW_DODOT 0x4000 // also files starting with a dot
+#define EW_EMPTYOK 0x8000 // no matches is not an error
+#define EW_NOTENV 0x10000 // do not expand environment variables
/// Return value for the comparison of two files. Also @see path_full_compare.
typedef enum file_comparison {
diff --git a/src/nvim/po/af.po b/src/nvim/po/af.po
index 61ad72d7e0..79048eac39 100644
--- a/src/nvim/po/af.po
+++ b/src/nvim/po/af.po
@@ -100,7 +100,7 @@ msgid "E88: Cannot go before first buffer"
msgstr "E88: Kan nie vóór eerste buffer gaan nie"
#, fuzzy, c-format
-#~ msgid "E89: %s will be killed(add ! to override)"
+#~ msgid "E89: %s will be killed (add ! to override)"
#~ msgstr "E189: \"%s\" bestaan (gebruik ! om te dwing)"
#, c-format
diff --git a/src/nvim/po/check.vim b/src/nvim/po/check.vim
index eae27ef74d..650c6155e2 100644
--- a/src/nvim/po/check.vim
+++ b/src/nvim/po/check.vim
@@ -47,6 +47,17 @@ let wsv = winsaveview()
let error = 0
while 1
+ let lnum = line('.')
+ if getline(lnum) =~ 'msgid "Text;.*;"'
+ if getline(lnum + 1) !~ '^msgstr "\([^;]\+;\)\+"'
+ echomsg 'Mismatching ; in line ' . (lnum + 1)
+ echomsg 'Did you forget the trailing semicolon?'
+ if error == 0
+ let error = lnum + 1
+ endif
+ endif
+ endif
+
if getline(line('.') - 1) !~ "no-c-format"
" go over the "msgid" and "msgid_plural" lines
let prevfromline = 'foobar'
diff --git a/src/nvim/po/fi.po b/src/nvim/po/fi.po
index 4612988c95..f568a34b3c 100644
--- a/src/nvim/po/fi.po
+++ b/src/nvim/po/fi.po
@@ -323,7 +323,7 @@ msgid "E88: Cannot go before first buffer"
msgstr "E88: Ensimmäisen puskurin ohi ei voi edetä"
#, fuzzy, c-format
-#~ msgid "E89: %s will be killed(add ! to override)"
+#~ msgid "E89: %s will be killed (add ! to override)"
#~ msgstr "E189: %s on jo olemassa (lisää komentoon ! ohittaaksesi)"
#, fuzzy, c-format
diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po
index 211d38e53a..19ea8e897a 100644
--- a/src/nvim/po/uk.po
+++ b/src/nvim/po/uk.po
@@ -87,8 +87,8 @@ msgid "E88: Cannot go before first buffer"
msgstr "E88: Це вже найперший буфер"
#, c-format
-msgid "E89: %s will be killed(add ! to override)"
-msgstr "E89: «%s» буде вбито(! щоб не зважати)"
+msgid "E89: %s will be killed (add ! to override)"
+msgstr "E89: «%s» буде вбито (! щоб не зважати)"
#, c-format
msgid ""
diff --git a/src/nvim/pos.h b/src/nvim/pos.h
index 47d253e083..8e86ea08c5 100644
--- a/src/nvim/pos.h
+++ b/src/nvim/pos.h
@@ -14,6 +14,10 @@ typedef int colnr_T;
enum { MAXLNUM = 0x7fffffff };
/// Maximal column number, 31 bits
enum { MAXCOL = 0x7fffffff };
+// Minimum line number
+enum { MINLNUM = 1 };
+// minimum column number
+enum { MINCOL = 1 };
/*
* position in file or buffer
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 301f72fa79..5d30ca624f 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -1,9 +1,7 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-/*
- * quickfix.c: functions for quickfix mode, using a file with error messages
- */
+// quickfix.c: functions for quickfix mode, using a file with error messages
#include <assert.h>
#include <inttypes.h>
@@ -53,9 +51,7 @@ struct dir_stack_T {
char_u *dirname;
};
-/*
- * For each error the next struct is allocated and linked in a list.
- */
+// For each error the next struct is allocated and linked in a list.
typedef struct qfline_S qfline_T;
struct qfline_S {
qfline_T *qf_next; ///< pointer to next error in the list
@@ -74,12 +70,19 @@ struct qfline_S {
char_u qf_valid; ///< valid error message detected
};
-/*
- * There is a stack of error lists.
- */
+// There is a stack of error lists.
#define LISTCOUNT 10
#define INVALID_QFIDX (-1)
+/// Quickfix list type.
+typedef enum
+{
+ QFLT_QUICKFIX, ///< Quickfix list - global list
+ QFLT_LOCATION, ///< Location list - per window list
+ QFLT_INTERNAL ///< Internal - Temporary list used by
+ // getqflist()/getloclist()
+} qfltype_T;
+
/// Quickfix/Location list definition
///
/// Usually the list contains one or more entries. But an empty list can be
@@ -87,6 +90,7 @@ struct qfline_S {
/// information and entries can be added later using setqflist()/setloclist().
typedef struct qf_list_S {
unsigned qf_id; ///< Unique identifier for this list
+ qfltype_T qfl_type;
qfline_T *qf_start; ///< pointer to the first error
qfline_T *qf_last; ///< pointer to the last error
qfline_T *qf_ptr; ///< pointer to the current error
@@ -110,16 +114,15 @@ typedef struct qf_list_S {
/// Quickfix/Location list stack definition
/// Contains a list of quickfix/location lists (qf_list_T)
struct qf_info_S {
- /*
- * Count of references to this list. Used only for location lists.
- * When a location list window reference this list, qf_refcount
- * will be 2. Otherwise, qf_refcount will be 1. When qf_refcount
- * reaches 0, the list is freed.
- */
+ // Count of references to this list. Used only for location lists.
+ // When a location list window reference this list, qf_refcount
+ // will be 2. Otherwise, qf_refcount will be 1. When qf_refcount
+ // reaches 0, the list is freed.
int qf_refcount;
- int qf_listcount; /* current number of lists */
- int qf_curlist; /* current error list */
+ int qf_listcount; // current number of lists
+ int qf_curlist; // current error list
qf_list_T qf_lists[LISTCOUNT];
+ qfltype_T qfl_type; // type of list
};
static qf_info_T ql_info; // global quickfix list
@@ -127,33 +130,38 @@ static unsigned last_qf_id = 0; // Last Used quickfix list id
#define FMT_PATTERNS 11 // maximum number of % recognized
-/*
- * Structure used to hold the info of one part of 'errorformat'
- */
+// Structure used to hold the info of one part of 'errorformat'
typedef struct efm_S efm_T;
struct efm_S {
- regprog_T *prog; /* pre-formatted part of 'errorformat' */
- efm_T *next; /* pointer to next (NULL if last) */
- char_u addr[FMT_PATTERNS]; /* indices of used % patterns */
- char_u prefix; /* prefix of this format line: */
- /* 'D' enter directory */
- /* 'X' leave directory */
- /* 'A' start of multi-line message */
- /* 'E' error message */
- /* 'W' warning message */
- /* 'I' informational message */
- /* 'C' continuation line */
- /* 'Z' end of multi-line message */
- /* 'G' general, unspecific message */
- /* 'P' push file (partial) message */
- /* 'Q' pop/quit file (partial) message */
- /* 'O' overread (partial) message */
- char_u flags; /* additional flags given in prefix */
- /* '-' do not include this line */
- /* '+' include whole line in message */
- int conthere; /* %> used */
+ regprog_T *prog; // pre-formatted part of 'errorformat'
+ efm_T *next; // pointer to next (NULL if last)
+ char_u addr[FMT_PATTERNS]; // indices of used % patterns
+ char_u prefix; // prefix of this format line:
+ // 'D' enter directory
+ // 'X' leave directory
+ // 'A' start of multi-line message
+ // 'E' error message
+ // 'W' warning message
+ // 'I' informational message
+ // 'C' continuation line
+ // 'Z' end of multi-line message
+ // 'G' general, unspecific message
+ // 'P' push file (partial) message
+ // 'Q' pop/quit file (partial) message
+ // 'O' overread (partial) message
+ char_u flags; // additional flags given in prefix
+ // '-' do not include this line
+ // '+' include whole line in message
+ int conthere; // %> used
};
+/// List of location lists to be deleted.
+/// Used to delay the deletion of locations lists by autocmds.
+typedef struct qf_delq_S {
+ struct qf_delq_S *next;
+ qf_info_T *qi;
+} qf_delq_T;
+
enum {
QF_FAIL = 0,
QF_OK = 1,
@@ -163,6 +171,8 @@ enum {
QF_MULTISCAN = 5,
};
+/// State information used to parse lines and add entries to a quickfix/location
+/// list.
typedef struct {
char_u *linebuf;
size_t linelen;
@@ -196,14 +206,19 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "quickfix.c.generated.h"
#endif
-/* Quickfix window check helper macro */
+
+static char_u *e_no_more_items = (char_u *)N_("E553: No more items");
+
+// Quickfix window check helper macro
#define IS_QF_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref == NULL)
-/* Location list window check helper macro */
+// Location list window check helper macro
#define IS_LL_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL)
// Quickfix and location list stack check helper macros
-#define IS_QF_STACK(qi) (qi == &ql_info)
-#define IS_LL_STACK(qi) (qi != &ql_info)
+#define IS_QF_STACK(qi) (qi->qfl_type == QFLT_QUICKFIX)
+#define IS_LL_STACK(qi) (qi->qfl_type == QFLT_LOCATION)
+#define IS_QF_LIST(qfl) (qfl->qfl_type == QFLT_QUICKFIX)
+#define IS_LL_LIST(qfl) (qfl->qfl_type == QFLT_LOCATION)
//
// Return location list for window 'wp'
@@ -211,6 +226,14 @@ typedef struct {
//
#define GET_LOC_LIST(wp) (IS_LL_WINDOW(wp) ? wp->w_llist_ref : wp->w_llist)
+// Macro to loop through all the items in a quickfix list
+// Quickfix item index starts from 1, so i below starts at 1
+#define FOR_ALL_QFL_ITEMS(qfl, qfp, i) \
+ for (i = 1, qfp = qfl->qf_start; /* NOLINT(readability/braces) */ \
+ !got_int && i <= qfl->qf_count && qfp != NULL; \
+ i++, qfp = qfp->qf_next)
+
+
// Looking up a buffer can be slow if there are many. Remember the last one
// to make this a lot faster if there are multiple matches in the same file.
static char_u *qf_last_bufname = NULL;
@@ -218,6 +241,50 @@ static bufref_T qf_last_bufref = { NULL, 0, 0 };
static char *e_loc_list_changed = N_("E926: Current location list was changed");
+// Counter to prevent autocmds from freeing up location lists when they are
+// still being used.
+static int quickfix_busy = 0;
+static qf_delq_T *qf_delq_head = NULL;
+
+/// Process the next line from a file/buffer/list/string and add it
+/// to the quickfix list 'qfl'.
+static int qf_init_process_nextline(qf_list_T *qfl,
+ efm_T *fmt_first,
+ qfstate_T *state,
+ qffields_T *fields)
+{
+ int status;
+
+ // Get the next line from a file/buffer/list/string
+ status = qf_get_nextline(state);
+ if (status != QF_OK) {
+ return status;
+ }
+
+ status = qf_parse_line(qfl, state->linebuf, state->linelen,
+ fmt_first, fields);
+ if (status != QF_OK) {
+ return status;
+ }
+
+ return qf_add_entry(qfl,
+ qfl->qf_directory,
+ (*fields->namebuf || qfl->qf_directory != NULL)
+ ? fields->namebuf
+ : ((qfl->qf_currfile != NULL && fields->valid)
+ ? qfl->qf_currfile : (char_u *)NULL),
+ fields->module,
+ 0,
+ fields->errmsg,
+ fields->lnum,
+ fields->col,
+ fields->use_viscol,
+ fields->pattern,
+ fields->enr,
+ fields->type,
+ fields->valid);
+}
+
/// Read the errorfile "efile" into memory, line by line, building the error
/// list. Set the error list's title to qf_title.
///
@@ -229,8 +296,9 @@ static char *e_loc_list_changed = N_("E926: Current location list was changed");
/// @params enc If non-NULL, encoding used to parse errors
///
/// @returns -1 for error, number of errors for success.
-int qf_init(win_T *wp, char_u *efile, char_u *errorformat, int newlist,
- char_u *qf_title, char_u *enc)
+int qf_init(win_T *wp, const char_u *restrict efile,
+ char_u *restrict errorformat, int newlist,
+ const char_u *restrict qf_title, char_u *restrict enc)
{
qf_info_T *qi = &ql_info;
@@ -264,15 +332,151 @@ static struct fmtpattern
{ 'o', ".\\+" }
};
+/// Convert an errorformat pattern to a regular expression pattern.
+/// See fmt_pat definition above for the list of supported patterns. The
+/// pattern specifier is supplied in "efmpat". The converted pattern is stored
+/// in "regpat". Returns a pointer to the location after the pattern.
+static char_u * efmpat_to_regpat(
+ const char_u *efmpat,
+ char_u *regpat,
+ efm_T *efminfo,
+ int idx,
+ int round,
+ char_u *errmsg,
+ size_t errmsglen)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (efminfo->addr[idx]) {
+ // Each errorformat pattern can occur only once
+ snprintf((char *)errmsg, errmsglen,
+ _("E372: Too many %%%c in format string"), *efmpat);
+ EMSG(errmsg);
+ return NULL;
+ }
+ if ((idx && idx < 6
+ && vim_strchr((char_u *)"DXOPQ", efminfo->prefix) != NULL)
+ || (idx == 6
+ && vim_strchr((char_u *)"OPQ", efminfo->prefix) == NULL)) {
+ snprintf((char *)errmsg, errmsglen,
+ _("E373: Unexpected %%%c in format string"), *efmpat);
+ EMSG(errmsg);
+ return NULL;
+ }
+ efminfo->addr[idx] = (char_u)++round;
+ *regpat++ = '\\';
+ *regpat++ = '(';
+#ifdef BACKSLASH_IN_FILENAME
+ if (*efmpat == 'f') {
+ // Also match "c:" in the file name, even when
+ // checking for a colon next: "%f:".
+ // "\%(\a:\)\="
+ STRCPY(regpat, "\\%(\\a:\\)\\=");
+ regpat += 10;
+ }
+#endif
+ if (*efmpat == 'f' && efmpat[1] != NUL) {
+ if (efmpat[1] != '\\' && efmpat[1] != '%') {
+ // A file name may contain spaces, but this isn't
+ // in "\f". For "%f:%l:%m" there may be a ":" in
+ // the file name. Use ".\{-1,}x" instead (x is
+ // the next character), the requirement that :999:
+ // follows should work.
+ STRCPY(regpat, ".\\{-1,}");
+ regpat += 7;
+ } else {
+ // File name followed by '\\' or '%': include as
+ // many file name chars as possible.
+ STRCPY(regpat, "\\f\\+");
+ regpat += 4;
+ }
+ } else {
+ char_u *srcptr = (char_u *)fmt_pat[idx].pattern;
+ while ((*regpat = *srcptr++) != NUL) {
+ regpat++;
+ }
+ }
+ *regpat++ = '\\';
+ *regpat++ = ')';
+
+ return regpat;
+}
+
+/// Convert a scanf like format in 'errorformat' to a regular expression.
+/// Returns a pointer to the location after the pattern.
+static char_u * scanf_fmt_to_regpat(
+ const char_u **pefmp,
+ const char_u *efm,
+ int len,
+ char_u *regpat,
+ char_u *errmsg,
+ size_t errmsglen)
+ FUNC_ATTR_NONNULL_ALL
+{
+ const char_u *efmp = *pefmp;
+
+ if (*efmp == '[' || *efmp == '\\') {
+ if ((*regpat++ = *efmp) == '[') { // %*[^a-z0-9] etc.
+ if (efmp[1] == '^') {
+ *regpat++ = *++efmp;
+ }
+ if (efmp < efm + len) {
+ *regpat++ = *++efmp; // could be ']'
+ while (efmp < efm + len && (*regpat++ = *++efmp) != ']') {
+ }
+ if (efmp == efm + len) {
+ EMSG(_("E374: Missing ] in format string"));
+ return NULL;
+ }
+ }
+ } else if (efmp < efm + len) { // %*\D, %*\s etc.
+ *regpat++ = *++efmp;
+ }
+ *regpat++ = '\\';
+ *regpat++ = '+';
+ } else {
+ // TODO(vim): scanf()-like: %*ud, %*3c, %*f, ... ?
+ snprintf((char *)errmsg, errmsglen,
+ _("E375: Unsupported %%%c in format string"), *efmp);
+ EMSG(errmsg);
+ return NULL;
+ }
+
+ *pefmp = efmp;
+
+ return regpat;
+}
+
+/// Analyze/parse an errorformat prefix.
+static const char_u *efm_analyze_prefix(const char_u *efmp, efm_T *efminfo,
+ char_u *errmsg, size_t errmsglen)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (vim_strchr((char_u *)"+-", *efmp) != NULL) {
+ efminfo->flags = *efmp++;
+ }
+ if (vim_strchr((char_u *)"DXAEWICZGOPQ", *efmp) != NULL) {
+ efminfo->prefix = *efmp;
+ } else {
+ snprintf((char *)errmsg, errmsglen,
+ _("E376: Invalid %%%c in format string prefix"), *efmp);
+ EMSG(errmsg);
+ return NULL;
+ }
+
+ return efmp;
+}
+
+
// Converts a 'errorformat' string to regular expression pattern
-static int efm_to_regpat(char_u *efm, int len, efm_T *fmt_ptr,
- char_u *regpat, char_u *errmsg)
+static int efm_to_regpat(const char_u *efm, int len, efm_T *fmt_ptr,
+ char_u *regpat, char_u *errmsg, size_t errmsglen)
+ FUNC_ATTR_NONNULL_ALL
{
// Build regexp pattern from current 'errorformat' option
char_u *ptr = regpat;
*ptr++ = '^';
int round = 0;
- for (char_u *efmp = efm; efmp < efm + len; efmp++) {
+ for (const char_u *efmp = efm; efmp < efm + len; efmp++) {
if (*efmp == '%') {
efmp++;
int idx;
@@ -282,90 +486,17 @@ static int efm_to_regpat(char_u *efm, int len, efm_T *fmt_ptr,
}
}
if (idx < FMT_PATTERNS) {
- if (fmt_ptr->addr[idx]) {
- snprintf((char *)errmsg, CMDBUFFSIZE + 1,
- _("E372: Too many %%%c in format string"), *efmp);
- EMSG(errmsg);
- return -1;
- }
- if ((idx
- && idx < 6
- && vim_strchr((char_u *)"DXOPQ", fmt_ptr->prefix) != NULL)
- || (idx == 6
- && vim_strchr((char_u *)"OPQ", fmt_ptr->prefix) == NULL)) {
- snprintf((char *)errmsg, CMDBUFFSIZE + 1,
- _("E373: Unexpected %%%c in format string"), *efmp);
- EMSG(errmsg);
- return -1;
+ ptr = efmpat_to_regpat(efmp, ptr, fmt_ptr, idx, round, errmsg,
+ errmsglen);
+ if (ptr == NULL) {
+ return FAIL;
}
round++;
- fmt_ptr->addr[idx] = (char_u)round;
- *ptr++ = '\\';
- *ptr++ = '(';
-#ifdef BACKSLASH_IN_FILENAME
- if (*efmp == 'f') {
- // Also match "c:" in the file name, even when
- // checking for a colon next: "%f:".
- // "\%(\a:\)\="
- STRCPY(ptr, "\\%(\\a:\\)\\=");
- ptr += 10;
- }
-#endif
- if (*efmp == 'f' && efmp[1] != NUL) {
- if (efmp[1] != '\\' && efmp[1] != '%') {
- // A file name may contain spaces, but this isn't
- // in "\f". For "%f:%l:%m" there may be a ":" in
- // the file name. Use ".\{-1,}x" instead (x is
- // the next character), the requirement that :999:
- // follows should work.
- STRCPY(ptr, ".\\{-1,}");
- ptr += 7;
- } else {
- // File name followed by '\\' or '%': include as
- // many file name chars as possible.
- STRCPY(ptr, "\\f\\+");
- ptr += 4;
- }
- } else {
- char_u *srcptr = (char_u *)fmt_pat[idx].pattern;
- while ((*ptr = *srcptr++) != NUL) {
- ptr++;
- }
- }
- *ptr++ = '\\';
- *ptr++ = ')';
} else if (*efmp == '*') {
- if (*++efmp == '[' || *efmp == '\\') {
- if ((*ptr++ = *efmp) == '[') { // %*[^a-z0-9] etc.
- if (efmp[1] == '^') {
- *ptr++ = *++efmp;
- }
- if (efmp < efm + len) {
- efmp++;
- *ptr++ = *efmp; // could be ']'
- while (efmp < efm + len) {
- efmp++;
- if ((*ptr++ = *efmp) == ']') {
- break;
- }
- }
- if (efmp == efm + len) {
- EMSG(_("E374: Missing ] in format string"));
- return -1;
- }
- }
- } else if (efmp < efm + len) { // %*\D, %*\s etc.
- efmp++;
- *ptr++ = *efmp;
- }
- *ptr++ = '\\';
- *ptr++ = '+';
- } else {
- // TODO(vim): scanf()-like: %*ud, %*3c, %*f, ... ?
- snprintf((char *)errmsg, CMDBUFFSIZE + 1,
- _("E375: Unsupported %%%c in format string"), *efmp);
- EMSG(errmsg);
- return -1;
+ efmp++;
+ ptr = scanf_fmt_to_regpat(&efmp, efm, len, ptr, errmsg, errmsglen);
+ if (ptr == NULL) {
+ return FAIL;
}
} else if (vim_strchr((char_u *)"%\\.^$~[", *efmp) != NULL) {
*ptr++ = *efmp; // regexp magic characters
@@ -374,22 +505,17 @@ static int efm_to_regpat(char_u *efm, int len, efm_T *fmt_ptr,
} else if (*efmp == '>') {
fmt_ptr->conthere = true;
} else if (efmp == efm + 1) { // analyse prefix
- if (vim_strchr((char_u *)"+-", *efmp) != NULL) {
- fmt_ptr->flags = *efmp++;
- }
- if (vim_strchr((char_u *)"DXAEWICZGOPQ", *efmp) != NULL) {
- fmt_ptr->prefix = *efmp;
- } else {
- snprintf((char *)errmsg, CMDBUFFSIZE + 1,
- _("E376: Invalid %%%c in format string prefix"), *efmp);
- EMSG(errmsg);
- return -1;
+ // prefix is allowed only at the beginning of the errorformat
+ // option part
+ efmp = efm_analyze_prefix(efmp, fmt_ptr, errmsg, errmsglen);
+ if (efmp == NULL) {
+ return FAIL;
}
} else {
snprintf((char *)errmsg, CMDBUFFSIZE + 1,
_("E377: Invalid %%%c in format string"), *efmp);
EMSG(errmsg);
- return -1;
+ return FAIL;
}
} else { // copy normal character
if (*efmp == '\\' && efmp + 1 < efm + len) {
@@ -405,7 +531,7 @@ static int efm_to_regpat(char_u *efm, int len, efm_T *fmt_ptr,
*ptr++ = '$';
*ptr = NUL;
- return 0;
+ return OK;
}
static efm_T *fmt_start = NULL; // cached across qf_parse_line() calls
@@ -421,7 +547,42 @@ static void free_efm_list(efm_T **efm_first)
fmt_start = NULL;
}
-// Parse 'errorformat' option
+/// Compute the size of the buffer used to convert a 'errorformat' pattern into
+/// a regular expression pattern.
+static size_t efm_regpat_bufsz(char_u *efm)
+{
+ size_t sz;
+
+ sz = (FMT_PATTERNS * 3) + (STRLEN(efm) << 2);
+ for (int i = FMT_PATTERNS - 1; i >= 0; ) {
+ sz += STRLEN(fmt_pat[i--].pattern);
+ }
+#ifdef BACKSLASH_IN_FILENAME
+ sz += 12; // "%f" can become twelve chars longer (see efm_to_regpat)
+#else
+ sz += 2; // "%f" can become two chars longer
+#endif
+
+ return sz;
+}
+
+/// Return the length of a 'errorformat' option part (separated by ",").
+static int efm_option_part_len(char_u *efm)
+{
+ int len;
+
+ for (len = 0; efm[len] != NUL && efm[len] != ','; len++) {
+ if (efm[len] == '\\' && efm[len + 1] != NUL) {
+ len++;
+ }
+ }
+
+ return len;
+}
+
+/// Parse the 'errorformat' option. Multiple parts in the 'errorformat' option
+/// are parsed and converted to regular expressions. Returns information about
+/// the parsed 'errorformat' option.
static efm_T * parse_efm_option(char_u *efm)
{
efm_T *fmt_ptr = NULL;
@@ -433,16 +594,8 @@ static efm_T * parse_efm_option(char_u *efm)
char_u *errmsg = xmalloc(errmsglen);
// Get some space to modify the format string into.
- size_t i = (FMT_PATTERNS * 3) + (STRLEN(efm) << 2);
- for (int round = FMT_PATTERNS - 1; round >= 0; ) {
- i += STRLEN(fmt_pat[round--].pattern);
- }
-#ifdef BACKSLASH_IN_FILENAME
- i += 12; // "%f" can become twelve chars longer (see efm_to_regpat)
-#else
- i += 2; // "%f" can become two chars longer
-#endif
- char_u *fmtstr = xmalloc(i);
+ size_t sz = efm_regpat_bufsz(efm);
+ char_u *fmtstr = xmalloc(sz);
while (efm[0] != NUL) {
// Allocate a new eformat structure and put it at the end of the list
@@ -455,13 +608,9 @@ static efm_T * parse_efm_option(char_u *efm)
fmt_last = fmt_ptr;
// Isolate one part in the 'errorformat' option
- for (len = 0; efm[len] != NUL && efm[len] != ','; len++) {
- if (efm[len] == '\\' && efm[len + 1] != NUL) {
- len++;
- }
- }
+ len = efm_option_part_len(efm);
- if (efm_to_regpat(efm, len, fmt_ptr, fmtstr, errmsg) == -1) {
+ if (efm_to_regpat(efm, len, fmt_ptr, fmtstr, errmsg, errmsglen) == FAIL) {
goto parse_efm_error;
}
if ((fmt_ptr->prog = vim_regcomp(fmtstr, RE_MAGIC + RE_STRING)) == NULL) {
@@ -487,6 +636,7 @@ parse_efm_end:
return fmt_first;
}
+/// Allocate more memory for the line buffer used for parsing lines.
static char_u *qf_grow_linebuf(qfstate_T *state, size_t newsz)
{
// If the line exceeds LINE_MAXLEN exclude the last
@@ -728,25 +878,41 @@ static int qf_get_nextline(qfstate_T *state)
return QF_OK;
}
-// Returns true if the specified quickfix/location list is empty.
-static bool qf_list_empty(const qf_info_T *qi, int qf_idx)
+/// Returns true if the specified quickfix/location stack is empty
+static bool qf_stack_empty(const qf_info_T *qi)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- if (qi == NULL || qf_idx < 0 || qf_idx >= LISTCOUNT) {
- return true;
- }
- return qi->qf_lists[qf_idx].qf_count <= 0;
+ return qi == NULL || qi->qf_listcount <= 0;
+}
+
+/// Returns true if the specified quickfix/location list is empty.
+static bool qf_list_empty(qf_list_T *qfl)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return qfl == NULL || qfl->qf_count <= 0;
+}
+
+/// Returns true if the specified quickfix/location list is not empty and
+/// has valid entries.
+static bool qf_list_has_valid_entries(qf_list_T *qfl)
+{
+ return !qf_list_empty(qfl) && !qfl->qf_nonevalid;
+}
+
+/// Return a pointer to a list in the specified quickfix stack
+static qf_list_T * qf_get_list(qf_info_T *qi, int idx)
+{
+ return &qi->qf_lists[idx];
}
/// Parse a line and get the quickfix fields.
/// Return the QF_ status.
-static int qf_parse_line(qf_info_T *qi, int qf_idx, char_u *linebuf,
+static int qf_parse_line(qf_list_T *qfl, char_u *linebuf,
size_t linelen, efm_T *fmt_first, qffields_T *fields)
{
efm_T *fmt_ptr;
int idx = 0;
char_u *tail = NULL;
- qf_list_T *qfl = &qi->qf_lists[qf_idx];
int status;
restofline:
@@ -802,7 +968,7 @@ restofline:
qfl->qf_multiignore = false; // reset continuation
} else if (vim_strchr((char_u *)"CZ", idx) != NULL) {
// continuation of multi-line msg
- status = qf_parse_multiline_pfx(qi, qf_idx, idx, qfl, fields);
+ status = qf_parse_multiline_pfx(idx, qfl, fields);
if (status != QF_OK) {
return status;
}
@@ -825,6 +991,79 @@ restofline:
return QF_OK;
}
+// Allocate the fields used for parsing lines and populating a quickfix list.
+static void qf_alloc_fields(qffields_T *pfields)
+ FUNC_ATTR_NONNULL_ALL
+{
+ pfields->namebuf = xmalloc(CMDBUFFSIZE + 1);
+ pfields->module = xmalloc(CMDBUFFSIZE + 1);
+ pfields->errmsglen = CMDBUFFSIZE + 1;
+ pfields->errmsg = xmalloc(pfields->errmsglen);
+ pfields->pattern = xmalloc(CMDBUFFSIZE + 1);
+}
+
+// Free the fields used for parsing lines and populating a quickfix list.
+static void qf_free_fields(qffields_T *pfields)
+ FUNC_ATTR_NONNULL_ALL
+{
+ xfree(pfields->namebuf);
+ xfree(pfields->module);
+ xfree(pfields->errmsg);
+ xfree(pfields->pattern);
+}
+
+// Setup the state information used for parsing lines and populating a
+// quickfix list.
+static int qf_setup_state(
+ qfstate_T *pstate,
+ char_u *restrict enc,
+ const char_u *restrict efile,
+ typval_T *tv,
+ buf_T *buf,
+ linenr_T lnumfirst,
+ linenr_T lnumlast)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ pstate->vc.vc_type = CONV_NONE;
+ if (enc != NULL && *enc != NUL) {
+ convert_setup(&pstate->vc, enc, p_enc);
+ }
+
+ if (efile != NULL
+ && (pstate->fd = os_fopen((const char *)efile, "r")) == NULL) {
+ EMSG2(_(e_openerrf), efile);
+ return FAIL;
+ }
+
+ if (tv != NULL) {
+ if (tv->v_type == VAR_STRING) {
+ pstate->p_str = tv->vval.v_string;
+ } else if (tv->v_type == VAR_LIST) {
+ pstate->p_li = tv_list_first(tv->vval.v_list);
+ }
+ pstate->tv = tv;
+ }
+ pstate->buf = buf;
+ pstate->buflnum = lnumfirst;
+ pstate->lnumlast = lnumlast;
+
+ return OK;
+}
+
+// Cleanup the state information used for parsing lines and populating a
+// quickfix list.
+static void qf_cleanup_state(qfstate_T *pstate)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (pstate->fd != NULL) {
+ fclose(pstate->fd);
+ }
+ xfree(pstate->growbuf);
+ if (pstate->vc.vc_type != CONV_NONE) {
+ convert_setup(&pstate->vc, NULL, NULL);
+ }
+}
+
// Read the errorfile "efile" into memory, line by line, building the error
// list.
// Alternative: when "efile" is NULL read errors from buffer "buf".
@@ -837,19 +1076,20 @@ static int
qf_init_ext(
qf_info_T *qi,
int qf_idx,
- char_u *efile,
+ const char_u *restrict efile,
buf_T *buf,
typval_T *tv,
- char_u *errorformat,
- int newlist, // TRUE: start a new error list
+ char_u *restrict errorformat,
+ bool newlist, // true: start a new error list
linenr_T lnumfirst, // first line number to use
linenr_T lnumlast, // last line number to use
- char_u *qf_title,
- char_u *enc
+ const char_u *restrict qf_title,
+ char_u *restrict enc
)
+ FUNC_ATTR_NONNULL_ARG(1)
{
- qfstate_T state;
- qffields_T fields;
+ qfstate_T state = { 0 };
+ qffields_T fields = { 0 };
qfline_T *old_last = NULL;
bool adding = false;
static efm_T *fmt_first = NULL;
@@ -861,21 +1101,9 @@ qf_init_ext(
// Do not used the cached buffer, it may have been wiped out.
XFREE_CLEAR(qf_last_bufname);
- memset(&state, 0, sizeof(state));
- memset(&fields, 0, sizeof(fields));
- state.vc.vc_type = CONV_NONE;
- if (enc != NULL && *enc != NUL) {
- convert_setup(&state.vc, enc, p_enc);
- }
-
- fields.namebuf = xmalloc(CMDBUFFSIZE + 1);
- fields.module = xmalloc(CMDBUFFSIZE + 1);
- fields.errmsglen = CMDBUFFSIZE + 1;
- fields.errmsg = xmalloc(fields.errmsglen);
- fields.pattern = xmalloc(CMDBUFFSIZE + 1);
-
- if (efile != NULL && (state.fd = os_fopen((char *)efile, "r")) == NULL) {
- EMSG2(_(e_openerrf), efile);
+ qf_alloc_fields(&fields);
+ if (qf_setup_state(&state, enc, efile, tv, buf,
+ lnumfirst, lnumlast) == FAIL) {
goto qf_init_end;
}
@@ -886,12 +1114,12 @@ qf_init_ext(
} else {
// Adding to existing list, use last entry.
adding = true;
- if (qi->qf_lists[qf_idx].qf_count > 0) {
+ if (!qf_list_empty(qf_get_list(qi, qf_idx) )) {
old_last = qi->qf_lists[qf_idx].qf_last;
}
}
- qf_list_T *qfl = &qi->qf_lists[qf_idx];
+ qf_list_T *qfl = qf_get_list(qi, qf_idx);
// Use the local value of 'errorformat' if it's set.
if (errorformat == p_efm && tv == NULL && buf && *buf->b_p_efm != NUL) {
@@ -918,63 +1146,21 @@ qf_init_ext(
goto error2;
}
- /*
- * got_int is reset here, because it was probably set when killing the
- * ":make" command, but we still want to read the errorfile then.
- */
- got_int = FALSE;
+ // got_int is reset here, because it was probably set when killing the
+ // ":make" command, but we still want to read the errorfile then.
+ got_int = false;
- if (tv != NULL) {
- if (tv->v_type == VAR_STRING) {
- state.p_str = tv->vval.v_string;
- } else if (tv->v_type == VAR_LIST) {
- state.p_list = tv->vval.v_list;
- state.p_li = tv_list_first(tv->vval.v_list);
- }
- state.tv = tv;
- }
- state.buf = buf;
- state.buflnum = lnumfirst;
- state.lnumlast = lnumlast;
-
- /*
- * Read the lines in the error file one by one.
- * Try to recognize one of the error formats in each line.
- */
+ // Read the lines in the error file one by one.
+ // Try to recognize one of the error formats in each line.
while (!got_int) {
- // Get the next line from a file/buffer/list/string
- status = qf_get_nextline(&state);
+ status = qf_init_process_nextline(qfl, fmt_first, &state, &fields);
if (status == QF_END_OF_INPUT) { // end of input
break;
}
-
- status = qf_parse_line(qi, qf_idx, state.linebuf, state.linelen,
- fmt_first, &fields);
if (status == QF_FAIL) {
goto error2;
}
- if (status == QF_IGNORE_LINE) {
- continue;
- }
- if (qf_add_entry(qi,
- qf_idx,
- qfl->qf_directory,
- (*fields.namebuf || qfl->qf_directory)
- ? fields.namebuf : ((qfl->qf_currfile && fields.valid)
- ? qfl->qf_currfile : (char_u *)NULL),
- fields.module,
- 0,
- fields.errmsg,
- fields.lnum,
- fields.col,
- fields.use_viscol,
- fields.pattern,
- fields.enr,
- fields.type,
- fields.valid) == FAIL) {
- goto error2;
- }
line_breakcheck();
}
if (state.fd == NULL || !ferror(state.fd)) {
@@ -997,45 +1183,34 @@ qf_init_ext(
error2:
if (!adding) {
// Error when creating a new list. Free the new list
- qf_free(qi, qi->qf_curlist);
+ qf_free(qfl);
qi->qf_listcount--;
if (qi->qf_curlist > 0) {
qi->qf_curlist--;
}
}
qf_init_end:
- if (state.fd != NULL) {
- fclose(state.fd);
- }
- xfree(fields.namebuf);
- xfree(fields.module);
- xfree(fields.errmsg);
- xfree(fields.pattern);
- xfree(state.growbuf);
-
if (qf_idx == qi->qf_curlist) {
qf_update_buffer(qi, old_last);
}
-
- if (state.vc.vc_type != CONV_NONE) {
- convert_setup(&state.vc, NULL, NULL);
- }
+ qf_cleanup_state(&state);
+ qf_free_fields(&fields);
return retval;
}
/// Set the title of the specified quickfix list. Frees the previous title.
/// Prepends ':' to the title.
-static void qf_store_title(qf_info_T *qi, int qf_idx, const char_u *title)
+static void qf_store_title(qf_list_T *qfl, const char_u *title)
FUNC_ATTR_NONNULL_ARG(1)
{
- XFREE_CLEAR(qi->qf_lists[qf_idx].qf_title);
+ XFREE_CLEAR(qfl->qf_title);
if (title != NULL) {
size_t len = STRLEN(title) + 1;
char_u *p = xmallocz(len);
- qi->qf_lists[qf_idx].qf_title = p;
+ qfl->qf_title = p;
xstrlcpy((char *)p, (const char *)title, len + 1);
}
}
@@ -1053,35 +1228,254 @@ static char_u * qf_cmdtitle(char_u *cmd)
return qftitle_str;
}
-// Prepare for adding a new quickfix list. If the current list is in the
-// middle of the stack, then all the following lists are freed and then
-// the new list is added.
-static void qf_new_list(qf_info_T *qi, char_u *qf_title)
+/// Return a pointer to the current list in the specified quickfix stack
+static qf_list_T * qf_get_curlist(qf_info_T *qi)
+{
+ return qf_get_list(qi, qi->qf_curlist);
+}
+
+/// Prepare for adding a new quickfix list. If the current list is in the
+/// middle of the stack, then all the following lists are freed and then
+/// the new list is added.
+static void qf_new_list(qf_info_T *qi, const char_u *qf_title)
{
int i;
+ qf_list_T *qfl;
// If the current entry is not the last entry, delete entries beyond
// the current entry. This makes it possible to browse in a tree-like
// way with ":grep'.
- while (qi->qf_listcount > qi->qf_curlist + 1)
- qf_free(qi, --qi->qf_listcount);
+ while (qi->qf_listcount > qi->qf_curlist + 1) {
+ qf_free(&qi->qf_lists[--qi->qf_listcount]);
+ }
- /*
- * When the stack is full, remove to oldest entry
- * Otherwise, add a new entry.
- */
+ // When the stack is full, remove to oldest entry
+ // Otherwise, add a new entry.
if (qi->qf_listcount == LISTCOUNT) {
- qf_free(qi, 0);
- for (i = 1; i < LISTCOUNT; ++i)
+ qf_free(&qi->qf_lists[0]);
+ for (i = 1; i < LISTCOUNT; i++) {
qi->qf_lists[i - 1] = qi->qf_lists[i];
+ }
qi->qf_curlist = LISTCOUNT - 1;
} else
qi->qf_curlist = qi->qf_listcount++;
- memset(&qi->qf_lists[qi->qf_curlist], 0, (size_t)(sizeof(qf_list_T)));
- qf_store_title(qi, qi->qf_curlist, qf_title);
- qi->qf_lists[qi->qf_curlist].qf_id = ++last_qf_id;
+ qfl = qf_get_curlist(qi);
+ memset(qfl, 0, sizeof(qf_list_T));
+ qf_store_title(qfl, qf_title);
+ qfl->qfl_type = qi->qfl_type;
+ qfl->qf_id = ++last_qf_id;
+}
+
+/// Parse the match for filename ('%f') pattern in regmatch.
+/// Return the matched value in "fields->namebuf".
+static int qf_parse_fmt_f(regmatch_T *rmp,
+ int midx,
+ qffields_T *fields,
+ int prefix)
+{
+ char_u c;
+
+ if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) {
+ return QF_FAIL;
+ }
+
+ // Expand ~/file and $HOME/file to full path.
+ c = *rmp->endp[midx];
+ *rmp->endp[midx] = NUL;
+ expand_env(rmp->startp[midx], fields->namebuf, CMDBUFFSIZE);
+ *rmp->endp[midx] = c;
+
+ // For separate filename patterns (%O, %P and %Q), the specified file
+ // should exist.
+ if (vim_strchr((char_u *)"OPQ", prefix) != NULL
+ && !os_path_exists(fields->namebuf)) {
+ return QF_FAIL;
+ }
+
+ return QF_OK;
+}
+
+/// Parse the match for error number ('%n') pattern in regmatch.
+/// Return the matched value in "fields->enr".
+static int qf_parse_fmt_n(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ if (rmp->startp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ fields->enr = (int)atol((char *)rmp->startp[midx]);
+ return QF_OK;
+}
+
+/// Parse the match for line number (%l') pattern in regmatch.
+/// Return the matched value in "fields->lnum".
+static int qf_parse_fmt_l(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ if (rmp->startp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ fields->lnum = atol((char *)rmp->startp[midx]);
+ return QF_OK;
+}
+
+/// Parse the match for column number ('%c') pattern in regmatch.
+/// Return the matched value in "fields->col".
+static int qf_parse_fmt_c(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ if (rmp->startp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ fields->col = (int)atol((char *)rmp->startp[midx]);
+ return QF_OK;
+}
+
+/// Parse the match for error type ('%t') pattern in regmatch.
+/// Return the matched value in "fields->type".
+static int qf_parse_fmt_t(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ if (rmp->startp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ fields->type = *rmp->startp[midx];
+ return QF_OK;
+}
+
+/// Parse the match for '%+' format pattern. The whole matching line is included
+/// in the error string. Return the matched line in "fields->errmsg".
+static int qf_parse_fmt_plus(char_u *linebuf,
+ size_t linelen,
+ qffields_T *fields)
+{
+ if (linelen >= fields->errmsglen) {
+ // linelen + null terminator
+ fields->errmsg = xrealloc(fields->errmsg, linelen + 1);
+ fields->errmsglen = linelen + 1;
+ }
+ STRLCPY(fields->errmsg, linebuf, linelen + 1);
+ return QF_OK;
+}
+
+/// Parse the match for error message ('%m') pattern in regmatch.
+/// Return the matched value in "fields->errmsg".
+static int qf_parse_fmt_m(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ size_t len;
+
+ if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ len = (size_t)(rmp->endp[midx] - rmp->startp[midx]);
+ if (len >= fields->errmsglen) {
+ // len + null terminator
+ fields->errmsg = xrealloc(fields->errmsg, len + 1);
+ fields->errmsglen = len + 1;
+ }
+ STRLCPY(fields->errmsg, rmp->startp[midx], len + 1);
+ return QF_OK;
}
+/// Parse the match for rest of a single-line file message ('%r') pattern.
+/// Return the matched value in "tail".
+static int qf_parse_fmt_r(regmatch_T *rmp, int midx, char_u **tail)
+{
+ if (rmp->startp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ *tail = rmp->startp[midx];
+ return QF_OK;
+}
+
+/// Parse the match for the pointer line ('%p') pattern in regmatch.
+/// Return the matched value in "fields->col".
+static int qf_parse_fmt_p(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ char_u *match_ptr;
+
+ if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ fields->col = 0;
+ for (match_ptr = rmp->startp[midx]; match_ptr != rmp->endp[midx];
+ match_ptr++) {
+ fields->col++;
+ if (*match_ptr == TAB) {
+ fields->col += 7;
+ fields->col -= fields->col % 8;
+ }
+ }
+ fields->col++;
+ fields->use_viscol = true;
+ return QF_OK;
+}
+
+/// Parse the match for the virtual column number ('%v') pattern in regmatch.
+/// Return the matched value in "fields->col".
+static int qf_parse_fmt_v(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ if (rmp->startp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ fields->col = (int)atol((char *)rmp->startp[midx]);
+ fields->use_viscol = true;
+ return QF_OK;
+}
+
+/// Parse the match for the search text ('%s') pattern in regmatch.
+/// Return the matched value in "fields->pattern".
+static int qf_parse_fmt_s(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ size_t len;
+
+ if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ len = (size_t)(rmp->endp[midx] - rmp->startp[midx]);
+ if (len > CMDBUFFSIZE - 5) {
+ len = CMDBUFFSIZE - 5;
+ }
+ STRCPY(fields->pattern, "^\\V");
+ xstrlcat((char *)fields->pattern, (char *)rmp->startp[midx], len + 4);
+ fields->pattern[len + 3] = '\\';
+ fields->pattern[len + 4] = '$';
+ fields->pattern[len + 5] = NUL;
+ return QF_OK;
+}
+
+/// Parse the match for the module ('%o') pattern in regmatch.
+/// Return the matched value in "fields->module".
+static int qf_parse_fmt_o(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ size_t len;
+ size_t dsize;
+
+ if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ len = (size_t)(rmp->endp[midx] - rmp->startp[midx]);
+ dsize = STRLEN(fields->module) + len + 1;
+ if (dsize > CMDBUFFSIZE) {
+ dsize = CMDBUFFSIZE;
+ }
+ xstrlcat((char *)fields->module, (char *)rmp->startp[midx], dsize);
+ return QF_OK;
+}
+
+/// 'errorformat' format pattern parser functions.
+/// The '%f' and '%r' formats are parsed differently from other formats.
+/// See qf_parse_match() for details.
+static int (*qf_parse_fmt[FMT_PATTERNS])(regmatch_T *, int, qffields_T *) = {
+ NULL,
+ qf_parse_fmt_n,
+ qf_parse_fmt_l,
+ qf_parse_fmt_c,
+ qf_parse_fmt_t,
+ qf_parse_fmt_m,
+ NULL,
+ qf_parse_fmt_p,
+ qf_parse_fmt_v,
+ qf_parse_fmt_s,
+ qf_parse_fmt_o
+};
+
/// Parse the error format matches in 'regmatch' and set the values in 'fields'.
/// fmt_ptr contains the 'efm' format specifiers/prefixes that have a match.
/// Returns QF_OK if all the matches are successfully parsed. On failure,
@@ -1092,7 +1486,8 @@ static int qf_parse_match(char_u *linebuf, size_t linelen, efm_T *fmt_ptr,
{
char_u idx = fmt_ptr->prefix;
int i;
- size_t len;
+ int midx;
+ int status;
if ((idx == 'C' || idx == 'Z') && !qf_multiline) {
return QF_FAIL;
@@ -1106,118 +1501,26 @@ static int qf_parse_match(char_u *linebuf, size_t linelen, efm_T *fmt_ptr,
// Extract error message data from matched line.
// We check for an actual submatch, because "\[" and "\]" in
// the 'errorformat' may cause the wrong submatch to be used.
- if ((i = (int)fmt_ptr->addr[0]) > 0) { // %f
- if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) {
- return QF_FAIL;
- }
-
- // Expand ~/file and $HOME/file to full path.
- char_u c = *regmatch->endp[i];
- *regmatch->endp[i] = NUL;
- expand_env(regmatch->startp[i], fields->namebuf, CMDBUFFSIZE);
- *regmatch->endp[i] = c;
-
- if (vim_strchr((char_u *)"OPQ", idx) != NULL
- && !os_path_exists(fields->namebuf)) {
- return QF_FAIL;
- }
- }
- if ((i = (int)fmt_ptr->addr[1]) > 0) { // %n
- if (regmatch->startp[i] == NULL) {
- return QF_FAIL;
- }
- fields->enr = (int)atol((char *)regmatch->startp[i]);
- }
- if ((i = (int)fmt_ptr->addr[2]) > 0) { // %l
- if (regmatch->startp[i] == NULL) {
- return QF_FAIL;
- }
- fields->lnum = atol((char *)regmatch->startp[i]);
- }
- if ((i = (int)fmt_ptr->addr[3]) > 0) { // %c
- if (regmatch->startp[i] == NULL) {
- return QF_FAIL;
- }
- fields->col = (int)atol((char *)regmatch->startp[i]);
- }
- if ((i = (int)fmt_ptr->addr[4]) > 0) { // %t
- if (regmatch->startp[i] == NULL) {
- return QF_FAIL;
- }
- fields->type = *regmatch->startp[i];
- }
- if (fmt_ptr->flags == '+' && !qf_multiscan) { // %+
- if (linelen >= fields->errmsglen) {
- // linelen + null terminator
- fields->errmsg = xrealloc(fields->errmsg, linelen + 1);
- fields->errmsglen = linelen + 1;
- }
- STRLCPY(fields->errmsg, linebuf, linelen + 1);
- } else if ((i = (int)fmt_ptr->addr[5]) > 0) { // %m
- if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) {
- return QF_FAIL;
- }
- len = (size_t)(regmatch->endp[i] - regmatch->startp[i]);
- if (len >= fields->errmsglen) {
- // len + null terminator
- fields->errmsg = xrealloc(fields->errmsg, len + 1);
- fields->errmsglen = len + 1;
- }
- STRLCPY(fields->errmsg, regmatch->startp[i], len + 1);
- }
- if ((i = (int)fmt_ptr->addr[6]) > 0) { // %r
- if (regmatch->startp[i] == NULL) {
- return QF_FAIL;
- }
- *tail = regmatch->startp[i];
- }
- if ((i = (int)fmt_ptr->addr[7]) > 0) { // %p
- if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) {
- return QF_FAIL;
- }
- fields->col = 0;
- char_u *match_ptr;
- for (match_ptr = regmatch->startp[i]; match_ptr != regmatch->endp[i];
- match_ptr++) {
- fields->col++;
- if (*match_ptr == TAB) {
- fields->col += 7;
- fields->col -= fields->col % 8;
+ for (i = 0; i < FMT_PATTERNS; i++) {
+ status = QF_OK;
+ midx = (int)fmt_ptr->addr[i];
+ if (i == 0 && midx > 0) { // %f
+ status = qf_parse_fmt_f(regmatch, midx, fields, idx);
+ } else if (i == 5) {
+ if (fmt_ptr->flags == '+' && !qf_multiscan) { // %+
+ status = qf_parse_fmt_plus(linebuf, linelen, fields);
+ } else if (midx > 0) { // %m
+ status = qf_parse_fmt_m(regmatch, midx, fields);
}
+ } else if (i == 6 && midx > 0) { // %r
+ status = qf_parse_fmt_r(regmatch, midx, tail);
+ } else if (midx > 0) { // others
+ status = (qf_parse_fmt[i])(regmatch, midx, fields);
}
- fields->col++;
- fields->use_viscol = true;
- }
- if ((i = (int)fmt_ptr->addr[8]) > 0) { // %v
- if (regmatch->startp[i] == NULL) {
- return QF_FAIL;
- }
- fields->col = (int)atol((char *)regmatch->startp[i]);
- fields->use_viscol = true;
- }
- if ((i = (int)fmt_ptr->addr[9]) > 0) { // %s
- if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) {
- return QF_FAIL;
- }
- len = (size_t)(regmatch->endp[i] - regmatch->startp[i]);
- if (len > CMDBUFFSIZE - 5) {
- len = CMDBUFFSIZE - 5;
- }
- STRCPY(fields->pattern, "^\\V");
- STRNCAT(fields->pattern, regmatch->startp[i], len);
- fields->pattern[len + 3] = '\\';
- fields->pattern[len + 4] = '$';
- fields->pattern[len + 5] = NUL;
- }
- if ((i = (int)fmt_ptr->addr[10]) > 0) { // %o
- if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) {
- return QF_FAIL;
- }
- len = (size_t)(regmatch->endp[i] - regmatch->startp[i]);
- if (len > CMDBUFFSIZE) {
- len = CMDBUFFSIZE;
+
+ if (status != QF_OK) {
+ return status;
}
- STRNCAT(fields->module, regmatch->startp[i], len);
}
return QF_OK;
@@ -1329,8 +1632,7 @@ static int qf_parse_line_nomatch(char_u *linebuf, size_t linelen,
}
/// Parse multi-line error format prefixes (%C and %Z)
-static int qf_parse_multiline_pfx(qf_info_T *qi, int qf_idx, int idx,
- qf_list_T *qfl, qffields_T *fields)
+static int qf_parse_multiline_pfx(int idx, qf_list_T *qfl, qffields_T *fields)
{
if (!qfl->qf_multiignore) {
qfline_T *qfprev = qfl->qf_last;
@@ -1361,7 +1663,7 @@ static int qf_parse_multiline_pfx(qf_info_T *qi, int qf_idx, int idx,
}
qfprev->qf_viscol = fields->use_viscol;
if (!qfprev->qf_fnum) {
- qfprev->qf_fnum = qf_get_fnum(qi, qf_idx, qfl->qf_directory,
+ qfprev->qf_fnum = qf_get_fnum(qfl, qfl->qf_directory,
*fields->namebuf || qfl->qf_directory
? fields->namebuf
: qfl->qf_currfile && fields->valid
@@ -1376,7 +1678,18 @@ static int qf_parse_multiline_pfx(qf_info_T *qi, int qf_idx, int idx,
return QF_IGNORE_LINE;
}
-/// Free a location list.
+/// Queue location list stack delete request.
+static void locstack_queue_delreq(qf_info_T *qi)
+{
+ qf_delq_T *q;
+
+ q = xmalloc(sizeof(qf_delq_T));
+ q->qi = qi;
+ q->next = qf_delq_head;
+ qf_delq_head = q;
+}
+
+/// Free a location list stack
static void ll_free_all(qf_info_T **pqi)
{
int i;
@@ -1385,14 +1698,21 @@ static void ll_free_all(qf_info_T **pqi)
qi = *pqi;
if (qi == NULL)
return;
- *pqi = NULL; /* Remove reference to this list */
+ *pqi = NULL; // Remove reference to this list
qi->qf_refcount--;
if (qi->qf_refcount < 1) {
- /* No references to this location list */
- for (i = 0; i < qi->qf_listcount; ++i)
- qf_free(qi, i);
- xfree(qi);
+ // No references to this location list.
+ // If the location list is still in use, then queue the delete request
+ // to be processed later.
+ if (quickfix_busy > 0) {
+ locstack_queue_delreq(qi);
+ } else {
+ for (i = 0; i < qi->qf_listcount; i++) {
+ qf_free(qf_get_list(qi, i));
+ }
+ xfree(qi);
+ }
}
}
@@ -1403,19 +1723,64 @@ void qf_free_all(win_T *wp)
qf_info_T *qi = &ql_info;
if (wp != NULL) {
- /* location list */
+ // location list
ll_free_all(&wp->w_llist);
ll_free_all(&wp->w_llist_ref);
- } else
- /* quickfix list */
- for (i = 0; i < qi->qf_listcount; ++i)
- qf_free(qi, i);
+ } else {
+ // quickfix list
+ for (i = 0; i < qi->qf_listcount; i++) {
+ qf_free(qf_get_list(qi, i));
+ }
+ }
+}
+
+/// Delay freeing of location list stacks when the quickfix code is running.
+/// Used to avoid problems with autocmds freeing location list stacks when the
+/// quickfix code is still referencing the stack.
+/// Must always call decr_quickfix_busy() exactly once after this.
+static void incr_quickfix_busy(void)
+{
+ quickfix_busy++;
+}
+
+/// Safe to free location list stacks. Process any delayed delete requests.
+static void decr_quickfix_busy(void)
+{
+ quickfix_busy--;
+ if (quickfix_busy == 0) {
+ // No longer referencing the location lists. Process all the pending
+ // delete requests.
+ while (qf_delq_head != NULL) {
+ qf_delq_T *q = qf_delq_head;
+
+ qf_delq_head = q->next;
+ ll_free_all(&q->qi);
+ xfree(q);
+ }
+ }
+#ifdef ABORT_ON_INTERNAL_ERROR
+ if (quickfix_busy < 0) {
+ EMSG("quickfix_busy has become negative");
+ abort();
+ }
+#endif
+}
+
+#if defined(EXITFREE)
+void check_quickfix_busy(void)
+{
+ if (quickfix_busy != 0) {
+ EMSGN("quickfix_busy not zero on exit: %ld", (long)quickfix_busy);
+# ifdef ABORT_ON_INTERNAL_ERROR
+ abort();
+# endif
+ }
}
+#endif
/// Add an entry to the end of the list of errors.
///
-/// @param qi quickfix list
-/// @param qf_idx list index
+/// @param qfl quickfix list entry
/// @param dir optional directory name
/// @param fname file name or NULL
/// @param module module name or NULL
@@ -1429,8 +1794,8 @@ void qf_free_all(win_T *wp)
/// @param type type character
/// @param valid valid entry
///
-/// @returns OK or FAIL.
-static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname,
+/// @returns QF_OK or QF_FAIL.
+static int qf_add_entry(qf_list_T *qfl, char_u *dir, char_u *fname,
char_u *module, int bufnum, char_u *mesg, long lnum,
int col, char_u vis_col, char_u *pattern, int nr,
char_u type, char_u valid)
@@ -1444,10 +1809,10 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname,
qfp->qf_fnum = bufnum;
if (buf != NULL) {
buf->b_has_qf_entry |=
- IS_QF_STACK(qi) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY;
+ IS_QF_LIST(qfl) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY;
}
} else {
- qfp->qf_fnum = qf_get_fnum(qi, qf_idx, dir, fname);
+ qfp->qf_fnum = qf_get_fnum(qfl, dir, fname);
}
qfp->qf_text = vim_strsave(mesg);
qfp->qf_lnum = lnum;
@@ -1470,12 +1835,12 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname,
qfp->qf_type = (char_u)type;
qfp->qf_valid = valid;
- lastp = &qi->qf_lists[qf_idx].qf_last;
- if (qi->qf_lists[qf_idx].qf_count == 0) {
+ lastp = &qfl->qf_last;
+ if (qf_list_empty(qfl)) {
// first element in the list
- qi->qf_lists[qf_idx].qf_start = qfp;
- qi->qf_lists[qf_idx].qf_ptr = qfp;
- qi->qf_lists[qf_idx].qf_index = 0;
+ qfl->qf_start = qfp;
+ qfl->qf_ptr = qfp;
+ qfl->qf_index = 0;
qfp->qf_prev = NULL;
} else {
assert(*lastp);
@@ -1485,164 +1850,211 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname,
qfp->qf_next = NULL;
qfp->qf_cleared = false;
*lastp = qfp;
- qi->qf_lists[qf_idx].qf_count++;
- if (qi->qf_lists[qf_idx].qf_index == 0 && qfp->qf_valid) {
+ qfl->qf_count++;
+ if (qfl->qf_index == 0 && qfp->qf_valid) {
// first valid entry
- qi->qf_lists[qf_idx].qf_index = qi->qf_lists[qf_idx].qf_count;
- qi->qf_lists[qf_idx].qf_ptr = qfp;
+ qfl->qf_index = qfl->qf_count;
+ qfl->qf_ptr = qfp;
}
- return OK;
+ return QF_OK;
}
-/*
- * Allocate a new location list
- */
-static qf_info_T *ll_new_list(void)
+/// Allocate a new quickfix/location list stack
+static qf_info_T *qf_alloc_stack(qfltype_T qfltype)
+ FUNC_ATTR_NONNULL_RET
{
qf_info_T *qi = xcalloc(1, sizeof(qf_info_T));
qi->qf_refcount++;
+ qi->qfl_type = qfltype;
return qi;
}
-/*
- * Return the location list for window 'wp'.
- * If not present, allocate a location list
- */
+/// Return the location list stack for window 'wp'.
+/// If not present, allocate a location list stack
static qf_info_T *ll_get_or_alloc_list(win_T *wp)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
- if (IS_LL_WINDOW(wp))
- /* For a location list window, use the referenced location list */
+ if (IS_LL_WINDOW(wp)) {
+ // For a location list window, use the referenced location list
return wp->w_llist_ref;
+ }
- /*
- * For a non-location list window, w_llist_ref should not point to a
- * location list.
- */
+ // For a non-location list window, w_llist_ref should not point to a
+ // location list.
ll_free_all(&wp->w_llist_ref);
- if (wp->w_llist == NULL)
- wp->w_llist = ll_new_list(); /* new location list */
+ if (wp->w_llist == NULL) {
+ wp->w_llist = qf_alloc_stack(QFLT_LOCATION); // new location list
+ }
return wp->w_llist;
}
-/*
- * Copy the location list from window "from" to window "to".
- */
-void copy_loclist(win_T *from, win_T *to)
+/// Get the quickfix/location list stack to use for the specified Ex command.
+/// For a location list command, returns the stack for the current window. If
+/// the location list is not found, then returns NULL and prints an error
+/// message if 'print_emsg' is TRUE.
+static qf_info_T * qf_cmd_get_stack(exarg_T *eap, int print_emsg)
+{
+ qf_info_T *qi = &ql_info;
+
+ if (is_loclist_cmd(eap->cmdidx)) {
+ qi = GET_LOC_LIST(curwin);
+ if (qi == NULL) {
+ if (print_emsg) {
+ EMSG(_(e_loclist));
+ }
+ return NULL;
+ }
+ }
+
+ return qi;
+}
+
+/// Get the quickfix/location list stack to use for the specified Ex command.
+/// For a location list command, returns the stack for the current window.
+/// If the location list is not present, then allocates a new one.
+/// For a location list command, sets 'pwinp' to curwin.
+static qf_info_T *qf_cmd_get_or_alloc_stack(const exarg_T *eap, win_T **pwinp)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
+{
+ qf_info_T *qi = &ql_info;
+
+ if (is_loclist_cmd(eap->cmdidx)) {
+ qi = ll_get_or_alloc_list(curwin);
+ *pwinp = curwin;
+ }
+
+ return qi;
+}
+
+/// Copy location list entries from 'from_qfl' to 'to_qfl'.
+static int copy_loclist_entries(const qf_list_T *from_qfl, qf_list_T *to_qfl)
+ FUNC_ATTR_NONNULL_ALL
{
- qf_info_T *qi;
- int idx;
int i;
+ qfline_T *from_qfp;
+
+ // copy all the location entries in this list
+ FOR_ALL_QFL_ITEMS(from_qfl, from_qfp, i) {
+ if (qf_add_entry(to_qfl,
+ NULL,
+ NULL,
+ from_qfp->qf_module,
+ 0,
+ from_qfp->qf_text,
+ from_qfp->qf_lnum,
+ from_qfp->qf_col,
+ from_qfp->qf_viscol,
+ from_qfp->qf_pattern,
+ from_qfp->qf_nr,
+ 0,
+ from_qfp->qf_valid) == QF_FAIL) {
+ return FAIL;
+ }
- /*
- * When copying from a location list window, copy the referenced
- * location list. For other windows, copy the location list for
- * that window.
- */
- if (IS_LL_WINDOW(from))
+ // qf_add_entry() will not set the qf_num field, as the
+ // directory and file names are not supplied. So the qf_fnum
+ // field is copied here.
+ qfline_T *const prevp = to_qfl->qf_last;
+ prevp->qf_fnum = from_qfp->qf_fnum; // file number
+ prevp->qf_type = from_qfp->qf_type; // error type
+ if (from_qfl->qf_ptr == from_qfp) {
+ to_qfl->qf_ptr = prevp; // current location
+ }
+ }
+
+ return OK;
+}
+
+/// Copy the specified location list 'from_qfl' to 'to_qfl'.
+static int copy_loclist(const qf_list_T *from_qfl, qf_list_T *to_qfl)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // Some of the fields are populated by qf_add_entry()
+ to_qfl->qfl_type = from_qfl->qfl_type;
+ to_qfl->qf_nonevalid = from_qfl->qf_nonevalid;
+ to_qfl->qf_count = 0;
+ to_qfl->qf_index = 0;
+ to_qfl->qf_start = NULL;
+ to_qfl->qf_last = NULL;
+ to_qfl->qf_ptr = NULL;
+ if (from_qfl->qf_title != NULL) {
+ to_qfl->qf_title = vim_strsave(from_qfl->qf_title);
+ } else {
+ to_qfl->qf_title = NULL;
+ }
+ if (from_qfl->qf_ctx != NULL) {
+ to_qfl->qf_ctx = xcalloc(1, sizeof(*to_qfl->qf_ctx));
+ tv_copy(from_qfl->qf_ctx, to_qfl->qf_ctx);
+ } else {
+ to_qfl->qf_ctx = NULL;
+ }
+
+ if (from_qfl->qf_count) {
+ if (copy_loclist_entries(from_qfl, to_qfl) == FAIL) {
+ return FAIL;
+ }
+ }
+
+ to_qfl->qf_index = from_qfl->qf_index; // current index in the list
+
+ // Assign a new ID for the location list
+ to_qfl->qf_id = ++last_qf_id;
+ to_qfl->qf_changedtick = 0L;
+
+ // When no valid entries are present in the list, qf_ptr points to
+ // the first item in the list
+ if (to_qfl->qf_nonevalid) {
+ to_qfl->qf_ptr = to_qfl->qf_start;
+ to_qfl->qf_index = 1;
+ }
+
+ return OK;
+}
+
+// Copy the location list stack 'from' window to 'to' window.
+void copy_loclist_stack(win_T *from, win_T *to)
+ FUNC_ATTR_NONNULL_ALL
+{
+ qf_info_T *qi;
+
+ // When copying from a location list window, copy the referenced
+ // location list. For other windows, copy the location list for
+ // that window.
+ if (IS_LL_WINDOW(from)) {
qi = from->w_llist_ref;
- else
+ } else {
qi = from->w_llist;
+ }
- if (qi == NULL) /* no location list to copy */
+ if (qi == NULL) { // no location list to copy
return;
+ }
- /* allocate a new location list */
- to->w_llist = ll_new_list();
+ // allocate a new location list
+ to->w_llist = qf_alloc_stack(QFLT_LOCATION);
to->w_llist->qf_listcount = qi->qf_listcount;
- /* Copy the location lists one at a time */
- for (idx = 0; idx < qi->qf_listcount; idx++) {
- qf_list_T *from_qfl;
- qf_list_T *to_qfl;
-
+ // Copy the location lists one at a time
+ for (int idx = 0; idx < qi->qf_listcount; idx++) {
to->w_llist->qf_curlist = idx;
- from_qfl = &qi->qf_lists[idx];
- to_qfl = &to->w_llist->qf_lists[idx];
-
- /* Some of the fields are populated by qf_add_entry() */
- to_qfl->qf_nonevalid = from_qfl->qf_nonevalid;
- to_qfl->qf_count = 0;
- to_qfl->qf_index = 0;
- to_qfl->qf_start = NULL;
- to_qfl->qf_last = NULL;
- to_qfl->qf_ptr = NULL;
- if (from_qfl->qf_title != NULL)
- to_qfl->qf_title = vim_strsave(from_qfl->qf_title);
- else
- to_qfl->qf_title = NULL;
-
- if (from_qfl->qf_ctx != NULL) {
- to_qfl->qf_ctx = xcalloc(1, sizeof(typval_T));
- tv_copy(from_qfl->qf_ctx, to_qfl->qf_ctx);
- } else {
- to_qfl->qf_ctx = NULL;
- }
-
- if (from_qfl->qf_count) {
- qfline_T *from_qfp;
- qfline_T *prevp;
-
- // copy all the location entries in this list
- for (i = 0, from_qfp = from_qfl->qf_start;
- i < from_qfl->qf_count && from_qfp != NULL;
- i++, from_qfp = from_qfp->qf_next) {
- if (qf_add_entry(to->w_llist,
- to->w_llist->qf_curlist,
- NULL,
- NULL,
- from_qfp->qf_module,
- 0,
- from_qfp->qf_text,
- from_qfp->qf_lnum,
- from_qfp->qf_col,
- from_qfp->qf_viscol,
- from_qfp->qf_pattern,
- from_qfp->qf_nr,
- 0,
- from_qfp->qf_valid) == FAIL) {
- qf_free_all(to);
- return;
- }
- /*
- * qf_add_entry() will not set the qf_num field, as the
- * directory and file names are not supplied. So the qf_fnum
- * field is copied here.
- */
- prevp = to->w_llist->qf_lists[to->w_llist->qf_curlist].qf_last;
- prevp->qf_fnum = from_qfp->qf_fnum; // file number
- prevp->qf_type = from_qfp->qf_type; // error type
- if (from_qfl->qf_ptr == from_qfp) {
- to_qfl->qf_ptr = prevp; // current location
- }
- }
- }
-
- to_qfl->qf_index = from_qfl->qf_index; /* current index in the list */
-
- // Assign a new ID for the location list
- to_qfl->qf_id = ++last_qf_id;
- to_qfl->qf_changedtick = 0L;
-
- /* When no valid entries are present in the list, qf_ptr points to
- * the first item in the list */
- if (to_qfl->qf_nonevalid) {
- to_qfl->qf_ptr = to_qfl->qf_start;
- to_qfl->qf_index = 1;
+ if (copy_loclist(qf_get_list(qi, idx),
+ qf_get_list(to->w_llist, idx)) == FAIL) {
+ qf_free_all(to);
+ return;
}
}
- to->w_llist->qf_curlist = qi->qf_curlist; /* current list */
+ to->w_llist->qf_curlist = qi->qf_curlist; // current list
}
-// Get buffer number for file "directory/fname".
-// Also sets the b_has_qf_entry flag.
-static int qf_get_fnum(qf_info_T *qi, int qf_idx, char_u *directory,
- char_u *fname)
+/// Get buffer number for file "directory/fname".
+/// Also sets the b_has_qf_entry flag.
+static int qf_get_fnum(qf_list_T *qfl, char_u *directory, char_u *fname )
{
char_u *ptr = NULL;
char_u *bufname;
@@ -1665,7 +2077,7 @@ static int qf_get_fnum(qf_info_T *qi, int qf_idx, char_u *directory,
// directory change.
if (!os_path_exists(ptr)) {
xfree(ptr);
- directory = qf_guess_filepath(qi, qf_idx, fname);
+ directory = qf_guess_filepath(qfl, fname);
if (directory) {
ptr = (char_u *)concat_fnames((char *)directory, (char *)fname, true);
} else {
@@ -1693,7 +2105,7 @@ static int qf_get_fnum(qf_info_T *qi, int qf_idx, char_u *directory,
return 0;
}
buf->b_has_qf_entry =
- IS_QF_STACK(qi) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY;
+ IS_QF_LIST(qfl) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY;
return buf->b_fnum;
}
@@ -1704,22 +2116,21 @@ static char_u *qf_push_dir(char_u *dirbuf, struct dir_stack_T **stackptr,
{
struct dir_stack_T *ds_ptr;
- /* allocate new stack element and hook it in */
+ // allocate new stack element and hook it in
struct dir_stack_T *ds_new = xmalloc(sizeof(struct dir_stack_T));
ds_new->next = *stackptr;
*stackptr = ds_new;
- /* store directory on the stack */
+ // store directory on the stack
if (vim_isAbsName(dirbuf)
|| (*stackptr)->next == NULL
- || (*stackptr && is_file_stack))
+ || (*stackptr && is_file_stack)) {
(*stackptr)->dirname = vim_strsave(dirbuf);
- else {
- /* Okay we don't have an absolute path.
- * dirbuf must be a subdir of one of the directories on the stack.
- * Let's search...
- */
+ } else {
+ // Okay we don't have an absolute path.
+ // dirbuf must be a subdir of one of the directories on the stack.
+ // Let's search...
ds_new = (*stackptr)->next;
(*stackptr)->dirname = NULL;
while (ds_new) {
@@ -1732,7 +2143,7 @@ static char_u *qf_push_dir(char_u *dirbuf, struct dir_stack_T **stackptr,
ds_new = ds_new->next;
}
- /* clean up all dirs we already left */
+ // clean up all dirs we already left
while ((*stackptr)->next != ds_new) {
ds_ptr = (*stackptr)->next;
(*stackptr)->next = (*stackptr)->next->next;
@@ -1740,7 +2151,7 @@ static char_u *qf_push_dir(char_u *dirbuf, struct dir_stack_T **stackptr,
xfree(ds_ptr);
}
- /* Nothing found -> it must be on top level */
+ // Nothing found -> it must be on top level
if (ds_new == NULL) {
xfree((*stackptr)->dirname);
(*stackptr)->dirname = vim_strsave(dirbuf);
@@ -1758,18 +2169,16 @@ static char_u *qf_push_dir(char_u *dirbuf, struct dir_stack_T **stackptr,
}
-/*
- * pop dirbuf from the directory stack and return previous directory or NULL if
- * stack is empty
- */
+// pop dirbuf from the directory stack and return previous directory or NULL if
+// stack is empty
static char_u *qf_pop_dir(struct dir_stack_T **stackptr)
{
struct dir_stack_T *ds_ptr;
- /* TODO: Should we check if dirbuf is the directory on top of the stack?
- * What to do if it isn't? */
+ // TODO(vim): Should we check if dirbuf is the directory on top of the stack?
+ // What to do if it isn't?
- /* pop top element and free it */
+ // pop top element and free it
if (*stackptr != NULL) {
ds_ptr = *stackptr;
*stackptr = (*stackptr)->next;
@@ -1777,13 +2186,11 @@ static char_u *qf_pop_dir(struct dir_stack_T **stackptr)
xfree(ds_ptr);
}
- /* return NEW top element as current dir or NULL if stack is empty*/
+ // return NEW top element as current dir or NULL if stack is empty
return *stackptr ? (*stackptr)->dirname : NULL;
}
-/*
- * clean up directory stack
- */
+// clean up directory stack
static void qf_clean_dir_stack(struct dir_stack_T **stackptr)
{
struct dir_stack_T *ds_ptr;
@@ -1795,32 +2202,29 @@ static void qf_clean_dir_stack(struct dir_stack_T **stackptr)
}
}
-/*
- * Check in which directory of the directory stack the given file can be
- * found.
- * Returns a pointer to the directory name or NULL if not found.
- * Cleans up intermediate directory entries.
- *
- * TODO: How to solve the following problem?
- * If we have the this directory tree:
- * ./
- * ./aa
- * ./aa/bb
- * ./bb
- * ./bb/x.c
- * and make says:
- * making all in aa
- * making all in bb
- * x.c:9: Error
- * Then qf_push_dir thinks we are in ./aa/bb, but we are in ./bb.
- * qf_guess_filepath will return NULL.
- */
-static char_u *qf_guess_filepath(qf_info_T *qi, int qf_idx, char_u *filename)
+/// Check in which directory of the directory stack the given file can be
+/// found.
+/// Returns a pointer to the directory name or NULL if not found.
+/// Cleans up intermediate directory entries.
+///
+/// TODO(vim): How to solve the following problem?
+/// If we have this directory tree:
+/// ./
+/// ./aa
+/// ./aa/bb
+/// ./bb
+/// ./bb/x.c
+/// and make says:
+/// making all in aa
+/// making all in bb
+/// x.c:9: Error
+/// Then qf_push_dir thinks we are in ./aa/bb, but we are in ./bb.
+/// qf_guess_filepath will return NULL.
+static char_u *qf_guess_filepath(qf_list_T *qfl, char_u *filename)
{
struct dir_stack_T *ds_ptr;
struct dir_stack_T *ds_tmp;
char_u *fullname;
- qf_list_T *qfl = &qi->qf_lists[qf_idx];
// no dirs on the stack - there's nothing we can do
if (qfl->qf_dir_stack == NULL) {
@@ -1876,24 +2280,21 @@ static bool qflist_valid(win_T *wp, unsigned int qf_id)
/// When loading a file from the quickfix, the autocommands may modify it.
/// This may invalidate the current quickfix entry. This function checks
-/// whether a entry is still present in the quickfix.
+/// whether an entry is still present in the quickfix list.
/// Similar to location list.
-static bool is_qf_entry_present(qf_info_T *qi, qfline_T *qf_ptr)
+static bool is_qf_entry_present(qf_list_T *qfl, qfline_T *qf_ptr)
{
- qf_list_T *qfl;
qfline_T *qfp;
int i;
- qfl = &qi->qf_lists[qi->qf_curlist];
-
// Search for the entry in the current list
- for (i = 0, qfp = qfl->qf_start; i < qfl->qf_count; i++, qfp = qfp->qf_next) {
- if (qfp == NULL || qfp == qf_ptr) {
+ FOR_ALL_QFL_ITEMS(qfl, qfp, i) {
+ if (qfp == qf_ptr) {
break;
}
}
- if (i == qfl->qf_count) { // Entry is not found
+ if (i > qfl->qf_count) { // Entry is not found
return false;
}
@@ -1902,20 +2303,19 @@ static bool is_qf_entry_present(qf_info_T *qi, qfline_T *qf_ptr)
/// Get the next valid entry in the current quickfix/location list. The search
/// starts from the current entry. Returns NULL on failure.
-static qfline_T *get_next_valid_entry(qf_info_T *qi, qfline_T *qf_ptr,
+static qfline_T *get_next_valid_entry(qf_list_T *qfl, qfline_T *qf_ptr,
int *qf_index, int dir)
{
int idx = *qf_index;
int old_qf_fnum = qf_ptr->qf_fnum;
do {
- if (idx == qi->qf_lists[qi->qf_curlist].qf_count
- || qf_ptr->qf_next == NULL) {
+ if (idx == qfl->qf_count || qf_ptr->qf_next == NULL) {
return NULL;
}
idx++;
qf_ptr = qf_ptr->qf_next;
- } while ((!qi->qf_lists[qi->qf_curlist].qf_nonevalid && !qf_ptr->qf_valid)
+ } while ((!qfl->qf_nonevalid && !qf_ptr->qf_valid)
|| (dir == FORWARD_FILE && qf_ptr->qf_fnum == old_qf_fnum));
*qf_index = idx;
@@ -1924,7 +2324,7 @@ static qfline_T *get_next_valid_entry(qf_info_T *qi, qfline_T *qf_ptr,
/// Get the previous valid entry in the current quickfix/location list. The
/// search starts from the current entry. Returns NULL on failure.
-static qfline_T *get_prev_valid_entry(qf_info_T *qi, qfline_T *qf_ptr,
+static qfline_T *get_prev_valid_entry(qf_list_T *qfl, qfline_T *qf_ptr,
int *qf_index, int dir)
{
int idx = *qf_index;
@@ -1936,7 +2336,7 @@ static qfline_T *get_prev_valid_entry(qf_info_T *qi, qfline_T *qf_ptr,
}
idx--;
qf_ptr = qf_ptr->qf_prev;
- } while ((!qi->qf_lists[qi->qf_curlist].qf_nonevalid && !qf_ptr->qf_valid)
+ } while ((!qfl->qf_nonevalid && !qf_ptr->qf_valid)
|| (dir == BACKWARD_FILE && qf_ptr->qf_fnum == old_qf_fnum));
*qf_index = idx;
@@ -1947,12 +2347,11 @@ static qfline_T *get_prev_valid_entry(qf_info_T *qi, qfline_T *qf_ptr,
/// the quickfix list.
/// dir == FORWARD or FORWARD_FILE: next valid entry
/// dir == BACKWARD or BACKWARD_FILE: previous valid entry
-static qfline_T *get_nth_valid_entry(qf_info_T *qi, int errornr,
+static qfline_T *get_nth_valid_entry(qf_list_T *qfl, int errornr,
qfline_T *qf_ptr, int *qf_index, int dir)
{
qfline_T *prev_qf_ptr;
int prev_index;
- static char_u *e_no_more_items = (char_u *)N_("E553: No more items");
char_u *err = e_no_more_items;
while (errornr--) {
@@ -1960,9 +2359,9 @@ static qfline_T *get_nth_valid_entry(qf_info_T *qi, int errornr,
prev_index = *qf_index;
if (dir == FORWARD || dir == FORWARD_FILE) {
- qf_ptr = get_next_valid_entry(qi, qf_ptr, qf_index, dir);
+ qf_ptr = get_next_valid_entry(qfl, qf_ptr, qf_index, dir);
} else {
- qf_ptr = get_prev_valid_entry(qi, qf_ptr, qf_index, dir);
+ qf_ptr = get_prev_valid_entry(qfl, qf_ptr, qf_index, dir);
}
if (qf_ptr == NULL) {
@@ -1982,7 +2381,7 @@ static qfline_T *get_nth_valid_entry(qf_info_T *qi, int errornr,
}
/// Get n'th (errornr) quickfix entry
-static qfline_T *get_nth_entry(qf_info_T *qi, int errornr, qfline_T *qf_ptr,
+static qfline_T *get_nth_entry(qf_list_T *qfl, int errornr, qfline_T *qf_ptr,
int *cur_qfidx)
{
int qf_idx = *cur_qfidx;
@@ -1995,7 +2394,7 @@ static qfline_T *get_nth_entry(qf_info_T *qi, int errornr, qfline_T *qf_ptr,
// New error number is greater than the current error number
while (errornr > qf_idx
- && qf_idx < qi->qf_lists[qi->qf_curlist].qf_count
+ && qf_idx < qfl->qf_count
&& qf_ptr->qf_next != NULL) {
qf_idx++;
qf_ptr = qf_ptr->qf_next;
@@ -2005,6 +2404,25 @@ static qfline_T *get_nth_entry(qf_info_T *qi, int errornr, qfline_T *qf_ptr,
return qf_ptr;
}
+// Find a window displaying a Vim help file.
+static win_T *qf_find_help_win(void)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (bt_help(wp->w_buffer)) {
+ return wp;
+ }
+ }
+ return NULL;
+}
+
+/// Set the location list for the specified window to 'qi'.
+static void win_set_loclist(win_T *wp, qf_info_T *qi)
+{
+ wp->w_llist = qi;
+ qi->qf_refcount++;
+}
+
/// Find a help window or open one.
static int jump_to_help_window(qf_info_T *qi, int *opened_window)
{
@@ -2013,12 +2431,7 @@ static int jump_to_help_window(qf_info_T *qi, int *opened_window)
if (cmdmod.tab != 0) {
wp = NULL;
} else {
- FOR_ALL_WINDOWS_IN_TAB(wp2, curtab) {
- if (bt_help(wp2->w_buffer)) {
- wp = wp2;
- break;
- }
- }
+ wp = qf_find_help_win();
}
if (wp != NULL && wp->w_buffer->b_nwindows > 0) {
@@ -2049,8 +2462,7 @@ static int jump_to_help_window(qf_info_T *qi, int *opened_window)
if (IS_LL_STACK(qi)) { // not a quickfix list
// The new window should use the supplied location list
- curwin->w_llist = qi;
- qi->qf_refcount++;
+ win_set_loclist(curwin, qi);
}
}
@@ -2061,146 +2473,197 @@ static int jump_to_help_window(qf_info_T *qi, int *opened_window)
return OK;
}
-/// Find a suitable window for opening a file (qf_fnum) and jump to it.
-/// If the file is already opened in a window, jump to it.
-static int qf_jump_to_usable_window(int qf_fnum, int *opened_window)
+// Find a non-quickfix window using the given location list.
+// Returns NULL if a matching window is not found.
+static win_T *qf_find_win_with_loclist(const qf_info_T *ll)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- win_T *usable_win_ptr = NULL;
- int usable_win;
- int flags;
- win_T *win;
- win_T *altwin;
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_llist == ll && !bt_quickfix(wp->w_buffer)) {
+ return wp;
+ }
+ }
+ return NULL;
+}
- usable_win = 0;
+// Find a window containing a normal buffer
+static win_T *qf_find_win_with_normal_buf(void)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (bt_normal(wp->w_buffer)) {
+ return wp;
+ }
+ }
+ return NULL;
+}
- qf_info_T *ll_ref = curwin->w_llist_ref;
+// Go to a window in any tabpage containing the specified file. Returns true
+// if successfully jumped to the window. Otherwise returns FALSE.
+static bool qf_goto_tabwin_with_file(int fnum)
+{
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ if (wp->w_buffer->b_fnum == fnum) {
+ goto_tabpage_win(tp, wp);
+ return true;
+ }
+ }
+ return false;
+}
+
+// Create a new window to show a file above the quickfix window. Called when
+// only the quickfix window is present.
+static int qf_open_new_file_win(qf_info_T *ll_ref)
+{
+ int flags = WSP_ABOVE;
if (ll_ref != NULL) {
- // Find a window using the same location list that is not a
- // quickfix window.
- FOR_ALL_WINDOWS_IN_TAB(usable_win_ptr2, curtab) {
- if (usable_win_ptr2->w_llist == ll_ref
- && !bt_quickfix(usable_win_ptr2->w_buffer)) {
- usable_win_ptr = usable_win_ptr2;
- usable_win = 1;
+ flags |= WSP_NEWLOC;
+ }
+ if (win_split(0, flags) == FAIL) {
+ return FAIL; // not enough room for window
+ }
+ p_swb = empty_option; // don't split again
+ swb_flags = 0;
+ RESET_BINDING(curwin);
+ if (ll_ref != NULL) {
+ // The new window should use the location list from the
+ // location list window
+ win_set_loclist(curwin, ll_ref);
+ }
+ return OK;
+}
+
+// Go to a window that shows the right buffer. If the window is not found, go
+// to the window just above the location list window. This is used for opening
+// a file from a location window and not from a quickfix window. If some usable
+// window is previously found, then it is supplied in 'use_win'.
+static void qf_goto_win_with_ll_file(win_T *use_win, int qf_fnum,
+ qf_info_T *ll_ref)
+{
+ win_T *win = use_win;
+
+ if (win == NULL) {
+ // Find the window showing the selected file
+ FOR_ALL_WINDOWS_IN_TAB(win2, curtab) {
+ if (win2->w_buffer->b_fnum == qf_fnum) {
+ win = win2;
break;
}
}
+ if (win == NULL) {
+ // Find a previous usable window
+ win = curwin;
+ do {
+ if (bt_normal(win->w_buffer)) {
+ break;
+ }
+ if (win->w_prev == NULL) {
+ win = lastwin; // wrap around the top
+ } else {
+ win = win->w_prev; // go to previous window
+ }
+ } while (win != curwin);
+ }
+ }
+ win_goto(win);
+
+ // If the location list for the window is not set, then set it
+ // to the location list from the location window
+ if (win->w_llist == NULL && ll_ref != NULL) {
+ // The new window should use the location list from the
+ // location list window
+ win_set_loclist(win, ll_ref);
+ }
+}
+
+// Go to a window that shows the specified file. If a window is not found, go
+// to the window just above the quickfix window. This is used for opening a
+// file from a quickfix window and not from a location window.
+static void qf_goto_win_with_qfl_file(int qf_fnum)
+{
+ win_T *win = curwin;
+ win_T *altwin = NULL;
+ for (;;) {
+ if (win->w_buffer->b_fnum == qf_fnum) {
+ break;
+ }
+ if (win->w_prev == NULL) {
+ win = lastwin; // wrap around the top
+ } else {
+ win = win->w_prev; // go to previous window
+ }
+
+ if (IS_QF_WINDOW(win)) {
+ // Didn't find it, go to the window before the quickfix
+ // window, unless 'switchbuf' contains 'uselast': in this case we
+ // try to jump to the previously used window first.
+ if ((swb_flags & SWB_USELAST) && win_valid(prevwin)) {
+ win = prevwin;
+ } else if (altwin != NULL) {
+ win = altwin;
+ } else if (curwin->w_prev != NULL) {
+ win = curwin->w_prev;
+ } else {
+ win = curwin->w_next;
+ }
+ break;
+ }
+
+ // Remember a usable window.
+ if (altwin == NULL
+ && !win->w_p_pvw
+ && bt_normal(win->w_buffer)) {
+ altwin = win;
+ }
+ }
+
+ win_goto(win);
+}
+
+// Find a suitable window for opening a file (qf_fnum) from the
+// quickfix/location list and jump to it. If the file is already opened in a
+// window, jump to it. Otherwise open a new window to display the file. This is
+// called from either a quickfix or a location list window.
+static int qf_jump_to_usable_window(int qf_fnum, int *opened_window)
+{
+ win_T *usable_win_ptr = NULL;
+ bool usable_win = false;
+
+ qf_info_T *ll_ref = curwin->w_llist_ref;
+ if (ll_ref != NULL) {
+ // Find a non-quickfix window with this location list
+ usable_win_ptr = qf_find_win_with_loclist(ll_ref);
+ if (usable_win_ptr != NULL) {
+ usable_win = true;
+ }
}
if (!usable_win) {
// Locate a window showing a normal buffer
- FOR_ALL_WINDOWS_IN_TAB(win2, curtab) {
- if (win2->w_buffer->b_p_bt[0] == NUL) {
- usable_win = 1;
- break;
- }
+ win_T *win = qf_find_win_with_normal_buf();
+ if (win != NULL) {
+ usable_win = true;
}
}
// If no usable window is found and 'switchbuf' contains "usetab"
// then search in other tabs.
if (!usable_win && (swb_flags & SWB_USETAB)) {
- FOR_ALL_TAB_WINDOWS(tp, wp) {
- if (wp->w_buffer->b_fnum == qf_fnum) {
- goto_tabpage_win(tp, wp);
- usable_win = 1;
- goto win_found;
- }
- }
+ usable_win = qf_goto_tabwin_with_file(qf_fnum);
}
-win_found:
// If there is only one window and it is the quickfix window, create a
// new one above the quickfix window.
if ((ONE_WINDOW && bt_quickfix(curbuf)) || !usable_win) {
- flags = WSP_ABOVE;
- if (ll_ref != NULL) {
- flags |= WSP_NEWLOC;
- }
- if (win_split(0, flags) == FAIL) {
- return FAIL; // not enough room for window
+ if (qf_open_new_file_win(ll_ref) != OK) {
+ return FAIL;
}
*opened_window = true; // close it when fail
- p_swb = empty_option; // don't split again
- swb_flags = 0;
- RESET_BINDING(curwin);
- if (ll_ref != NULL) {
- // The new window should use the location list from the
- // location list window
- curwin->w_llist = ll_ref;
- ll_ref->qf_refcount++;
- }
} else {
- if (curwin->w_llist_ref != NULL) {
- // In a location window
- win = usable_win_ptr;
- if (win == NULL) {
- // Find the window showing the selected file
- FOR_ALL_WINDOWS_IN_TAB(win2, curtab) {
- if (win2->w_buffer->b_fnum == qf_fnum) {
- win = win2;
- break;
- }
- }
- if (win == NULL) {
- // Find a previous usable window
- win = curwin;
- do {
- if (win->w_buffer->b_p_bt[0] == NUL) {
- break;
- }
- if (win->w_prev == NULL) {
- win = lastwin; // wrap around the top
- } else {
- win = win->w_prev; // go to previous window
- }
- } while (win != curwin);
- }
- }
- win_goto(win);
-
- // If the location list for the window is not set, then set it
- // to the location list from the location window
- if (win->w_llist == NULL) {
- win->w_llist = ll_ref;
- if (ll_ref != NULL) {
- ll_ref->qf_refcount++;
- }
- }
- } else {
- // Try to find a window that shows the right buffer.
- // Default to the window just above the quickfix buffer.
- win = curwin;
- altwin = NULL;
- for (;;) {
- if (win->w_buffer->b_fnum == qf_fnum) {
- break;
- }
- if (win->w_prev == NULL) {
- win = lastwin; // wrap around the top
- } else {
- win = win->w_prev; // go to previous window
- }
- if (IS_QF_WINDOW(win)) {
- // Didn't find it, go to the window before the quickfix window.
- if (altwin != NULL) {
- win = altwin;
- } else if (curwin->w_prev != NULL) {
- win = curwin->w_prev;
- } else {
- win = curwin->w_next;
- }
- break;
- }
-
- // Remember a usable window.
- if (altwin == NULL && !win->w_p_pvw
- && win->w_buffer->b_p_bt[0] == NUL) {
- altwin = win;
- }
- }
-
- win_goto(win);
+ if (curwin->w_llist_ref != NULL) { // In a location window
+ qf_goto_win_with_ll_file(usable_win_ptr, qf_fnum, ll_ref);
+ } else { // In a quickfix window
+ qf_goto_win_with_qfl_file(qf_fnum);
}
}
@@ -2211,6 +2674,8 @@ win_found:
static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit,
win_T *oldwin, int *opened_window, int *abort)
{
+ qf_list_T *qfl = qf_get_curlist(qi);
+ qfltype_T qfl_type = qfl->qfl_type;
int retval = OK;
if (qf_ptr->qf_type == 1) {
@@ -2225,12 +2690,12 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit,
oldwin == curwin ? curwin : NULL);
}
} else {
- unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id;
+ unsigned save_qfid = qfl->qf_id;
retval = buflist_getfile(qf_ptr->qf_fnum, (linenr_T)1,
GETF_SETMARK | GETF_SWITCH, forceit);
- if (IS_LL_STACK(qi)) {
+ if (qfl_type == QFLT_LOCATION) {
// Location list. Check whether the associated window is still
// present and the list is still valid.
if (!win_valid_any_tab(oldwin)) {
@@ -2241,8 +2706,8 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit,
EMSG(_(e_loc_list_changed));
*abort = true;
}
- } else if (!is_qf_entry_present(qi, qf_ptr)) {
- if (IS_QF_STACK(qi)) {
+ } else if (!is_qf_entry_present(qfl, qf_ptr)) {
+ if (qfl_type == QFLT_QUICKFIX) {
EMSG(_("E925: Current quickfix was changed"));
} else {
EMSG(_(e_loc_list_changed));
@@ -2258,15 +2723,12 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit,
return retval;
}
-/// Goto the error line in the current file using either line/column number or a
-/// search pattern.
+/// Go to the error line in the current file using either line/column number or
+/// a search pattern.
static void qf_jump_goto_line(linenr_T qf_lnum, int qf_col, char_u qf_viscol,
char_u *qf_pattern)
{
linenr_T i;
- char_u *line;
- colnr_T screen_col;
- colnr_T char_col;
if (qf_pattern == NULL) {
// Go to line with error, unless qf_lnum is 0.
@@ -2278,26 +2740,11 @@ static void qf_jump_goto_line(linenr_T qf_lnum, int qf_col, char_u qf_viscol,
curwin->w_cursor.lnum = i;
}
if (qf_col > 0) {
- curwin->w_cursor.col = qf_col - 1;
curwin->w_cursor.coladd = 0;
if (qf_viscol == true) {
- // Check each character from the beginning of the error
- // line up to the error column. For each tab character
- // found, reduce the error column value by the length of
- // a tab character.
- line = get_cursor_line_ptr();
- screen_col = 0;
- for (char_col = 0; char_col < curwin->w_cursor.col; char_col++) {
- if (*line == NUL) {
- break;
- }
- if (*line++ == '\t') {
- curwin->w_cursor.col -= 7 - (screen_col % 8);
- screen_col += 8 - (screen_col % 8);
- } else {
- screen_col++;
- }
- }
+ coladvance(qf_col - 1);
+ } else {
+ curwin->w_cursor.col = qf_col - 1;
}
curwin->w_set_curswant = true;
check_cursor();
@@ -2308,7 +2755,7 @@ static void qf_jump_goto_line(linenr_T qf_lnum, int qf_col, char_u qf_viscol,
// Move the cursor to the first line in the buffer
pos_T save_cursor = curwin->w_cursor;
curwin->w_cursor.lnum = 0;
- if (!do_search(NULL, '/', qf_pattern, (long)1, SEARCH_KEEP, NULL, NULL)) {
+ if (!do_search(NULL, '/', qf_pattern, (long)1, SEARCH_KEEP, NULL)) {
curwin->w_cursor = save_cursor;
}
}
@@ -2324,7 +2771,7 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr,
update_topline_redraw();
}
snprintf((char *)IObuff, IOSIZE, _("(%d of %d)%s%s: "), qf_index,
- qi->qf_lists[qi->qf_curlist].qf_count,
+ qf_get_curlist(qi)->qf_count,
qf_ptr->qf_cleared ? _(" (line deleted)") : "",
(char *)qf_types(qf_ptr->qf_type, qf_ptr->qf_nr));
// Add the message, skipping leading whitespace and newlines.
@@ -2354,6 +2801,7 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr,
/// else go to entry "errornr"
void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit)
{
+ qf_list_T *qfl;
qfline_T *qf_ptr;
qfline_T *old_qf_ptr;
int qf_index;
@@ -2371,32 +2819,34 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit)
if (qi == NULL)
qi = &ql_info;
- if (qi->qf_curlist >= qi->qf_listcount
- || qi->qf_lists[qi->qf_curlist].qf_count == 0) {
+ if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) {
EMSG(_(e_quickfix));
return;
}
- qf_ptr = qi->qf_lists[qi->qf_curlist].qf_ptr;
+ qfl = qf_get_curlist(qi);
+
+ qf_ptr = qfl->qf_ptr;
old_qf_ptr = qf_ptr;
- qf_index = qi->qf_lists[qi->qf_curlist].qf_index;
+ qf_index = qfl->qf_index;
old_qf_index = qf_index;
if (dir != 0) { // next/prev valid entry
- qf_ptr = get_nth_valid_entry(qi, errornr, qf_ptr, &qf_index, dir);
+ qf_ptr = get_nth_valid_entry(qfl, errornr, qf_ptr, &qf_index, dir);
if (qf_ptr == NULL) {
qf_ptr = old_qf_ptr;
qf_index = old_qf_index;
goto theend; // The horror... the horror...
}
} else if (errornr != 0) { // go to specified number
- qf_ptr = get_nth_entry(qi, errornr, qf_ptr, &qf_index);
+ qf_ptr = get_nth_entry(qfl, errornr, qf_ptr, &qf_index);
}
- qi->qf_lists[qi->qf_curlist].qf_index = qf_index;
- if (qf_win_pos_update(qi, old_qf_index))
- /* No need to print the error message if it's visible in the error
- * window */
- print_message = FALSE;
+ qfl->qf_index = qf_index;
+ if (qf_win_pos_update(qi, old_qf_index)) {
+ // No need to print the error message if it's visible in the error
+ // window
+ print_message = false;
+ }
// For ":helpgrep" find a help window or open one.
if (qf_ptr->qf_type == 1 && (!bt_help(curwin->w_buffer) || cmdmod.tab != 0)) {
@@ -2418,10 +2868,8 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit)
}
}
- /*
- * If there is a file name,
- * read the wanted file if needed, and check autowrite etc.
- */
+ // If there is a file name,
+ // read the wanted file if needed, and check autowrite etc.
old_curbuf = curbuf;
old_lnum = curwin->w_cursor.lnum;
@@ -2465,12 +2913,12 @@ failed:
}
theend:
if (qi != NULL) {
- qi->qf_lists[qi->qf_curlist].qf_ptr = qf_ptr;
- qi->qf_lists[qi->qf_curlist].qf_index = qf_index;
+ qfl->qf_ptr = qf_ptr;
+ qfl->qf_index = qf_index;
}
if (p_swb != old_swb && opened_window) {
- /* Restore old 'switchbuf' value, but not when an autocommand or
- * modeline has changed the value. */
+ // Restore old 'switchbuf' value, but not when an autocommand or
+ // modeline has changed the value.
if (p_swb == empty_option) {
p_swb = old_swb;
swb_flags = old_swb_flags;
@@ -2479,36 +2927,115 @@ theend:
}
}
-/*
- * ":clist": list all errors
- * ":llist": list all locations
- */
+
+// Highlight attributes used for displaying entries from the quickfix list.
+static int qfFileAttr;
+static int qfSepAttr;
+static int qfLineAttr;
+
+/// Display information about a single entry from the quickfix/location list.
+/// Used by ":clist/:llist" commands.
+/// 'cursel' will be set to true for the currently selected entry in the
+/// quickfix list.
+static void qf_list_entry(qfline_T *qfp, int qf_idx, bool cursel)
+{
+ char_u *fname;
+ buf_T *buf;
+
+ fname = NULL;
+ if (qfp->qf_module != NULL && *qfp->qf_module != NUL) {
+ vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", qf_idx,
+ (char *)qfp->qf_module);
+ } else {
+ if (qfp->qf_fnum != 0
+ && (buf = buflist_findnr(qfp->qf_fnum)) != NULL) {
+ fname = buf->b_fname;
+ if (qfp->qf_type == 1) { // :helpgrep
+ fname = path_tail(fname);
+ }
+ }
+ if (fname == NULL) {
+ snprintf((char *)IObuff, IOSIZE, "%2d", qf_idx);
+ } else {
+ vim_snprintf((char *)IObuff, IOSIZE, "%2d %s",
+ qf_idx, (char *)fname);
+ }
+ }
+
+ // Support for filtering entries using :filter /pat/ clist
+ // Match against the module name, file name, search pattern and
+ // text of the entry.
+ bool filter_entry = true;
+ if (qfp->qf_module != NULL && *qfp->qf_module != NUL) {
+ filter_entry &= message_filtered(qfp->qf_module);
+ }
+ if (filter_entry && fname != NULL) {
+ filter_entry &= message_filtered(fname);
+ }
+ if (filter_entry && qfp->qf_pattern != NULL) {
+ filter_entry &= message_filtered(qfp->qf_pattern);
+ }
+ if (filter_entry) {
+ filter_entry &= message_filtered(qfp->qf_text);
+ }
+ if (filter_entry) {
+ return;
+ }
+
+ msg_putchar('\n');
+ msg_outtrans_attr(IObuff, cursel ? HL_ATTR(HLF_QFL) : qfFileAttr);
+
+ if (qfp->qf_lnum != 0) {
+ msg_puts_attr(":", qfSepAttr);
+ }
+ if (qfp->qf_lnum == 0) {
+ IObuff[0] = NUL;
+ } else if (qfp->qf_col == 0) {
+ vim_snprintf((char *)IObuff, IOSIZE, "%" PRIdLINENR, qfp->qf_lnum);
+ } else {
+ vim_snprintf((char *)IObuff, IOSIZE, "%" PRIdLINENR " col %d",
+ qfp->qf_lnum, qfp->qf_col);
+ }
+ vim_snprintf((char *)IObuff + STRLEN(IObuff), IOSIZE, "%s",
+ (char *)qf_types(qfp->qf_type, qfp->qf_nr));
+ msg_puts_attr((const char *)IObuff, qfLineAttr);
+ msg_puts_attr(":", qfSepAttr);
+ if (qfp->qf_pattern != NULL) {
+ qf_fmt_text(qfp->qf_pattern, IObuff, IOSIZE);
+ msg_puts((const char *)IObuff);
+ msg_puts_attr(":", qfSepAttr);
+ }
+ msg_puts(" ");
+
+ // Remove newlines and leading whitespace from the text. For an
+ // unrecognized line keep the indent, the compiler may mark a word
+ // with ^^^^. */
+ qf_fmt_text((fname != NULL || qfp->qf_lnum != 0)
+ ? skipwhite(qfp->qf_text) : qfp->qf_text,
+ IObuff, IOSIZE);
+ msg_prt_line(IObuff, false);
+ ui_flush(); // show one line at a time
+}
+
+// ":clist": list all errors
+// ":llist": list all locations
void qf_list(exarg_T *eap)
{
- buf_T *buf;
- char_u *fname;
- qfline_T *qfp;
+ qf_list_T *qfl;
+ qfline_T *qfp;
int i;
int idx1 = 1;
int idx2 = -1;
char_u *arg = eap->arg;
- int qfFileAttr;
- int qfSepAttr;
- int qfLineAttr;
int all = eap->forceit; // if not :cl!, only show
// recognised errors
- qf_info_T *qi = &ql_info;
+ qf_info_T *qi;
- if (eap->cmdidx == CMD_llist) {
- qi = GET_LOC_LIST(curwin);
- if (qi == NULL) {
- EMSG(_(e_loclist));
- return;
- }
+ if ((qi = qf_cmd_get_stack(eap, true)) == NULL) {
+ return;
}
- if (qi->qf_curlist >= qi->qf_listcount
- || qi->qf_lists[qi->qf_curlist].qf_count == 0) {
+ if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) {
EMSG(_(e_quickfix));
return;
}
@@ -2522,12 +3049,13 @@ void qf_list(exarg_T *eap)
EMSG(_(e_trailing));
return;
}
+ qfl = qf_get_curlist(qi);
if (plus) {
- i = qi->qf_lists[qi->qf_curlist].qf_index;
+ i = qfl->qf_index;
idx2 = i + idx1;
idx1 = i;
} else {
- i = qi->qf_lists[qi->qf_curlist].qf_count;
+ i = qfl->qf_count;
if (idx1 < 0) {
idx1 = (-idx1 > i) ? 0 : idx1 + i + 1;
}
@@ -2554,107 +3082,25 @@ void qf_list(exarg_T *eap)
qfLineAttr = HL_ATTR(HLF_N);
}
- if (qi->qf_lists[qi->qf_curlist].qf_nonevalid) {
+ if (qfl->qf_nonevalid) {
all = true;
}
- qfp = qi->qf_lists[qi->qf_curlist].qf_start;
- for (i = 1; !got_int && i <= qi->qf_lists[qi->qf_curlist].qf_count; ) {
+ FOR_ALL_QFL_ITEMS(qfl, qfp, i) {
if ((qfp->qf_valid || all) && idx1 <= i && i <= idx2) {
- if (got_int) {
- break;
- }
-
- fname = NULL;
- if (qfp->qf_module != NULL && *qfp->qf_module != NUL) {
- vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", i,
- (char *)qfp->qf_module);
- } else {
- if (qfp->qf_fnum != 0 && (buf = buflist_findnr(qfp->qf_fnum)) != NULL) {
- fname = buf->b_fname;
- if (qfp->qf_type == 1) { // :helpgrep
- fname = path_tail(fname);
- }
- }
- if (fname == NULL) {
- snprintf((char *)IObuff, IOSIZE, "%2d", i);
- } else {
- vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", i, (char *)fname);
- }
- }
-
- // Support for filtering entries using :filter /pat/ clist
- // Match against the module name, file name, search pattern and
- // text of the entry.
- bool filter_entry = true;
- if (qfp->qf_module != NULL && *qfp->qf_module != NUL) {
- filter_entry &= message_filtered(qfp->qf_module);
- }
- if (filter_entry && fname != NULL) {
- filter_entry &= message_filtered(fname);
- }
- if (filter_entry && qfp->qf_pattern != NULL) {
- filter_entry &= message_filtered(qfp->qf_pattern);
- }
- if (filter_entry) {
- filter_entry &= message_filtered(qfp->qf_text);
- }
- if (filter_entry) {
- goto next_entry;
- }
- msg_putchar('\n');
- msg_outtrans_attr(IObuff, i == qi->qf_lists[qi->qf_curlist].qf_index
- ? HL_ATTR(HLF_QFL) : qfFileAttr);
-
- if (qfp->qf_lnum != 0) {
- msg_puts_attr(":", qfSepAttr);
- }
- if (qfp->qf_lnum == 0) {
- IObuff[0] = NUL;
- } else if (qfp->qf_col == 0) {
- vim_snprintf((char *)IObuff, IOSIZE, "%" PRIdLINENR, qfp->qf_lnum);
- } else {
- vim_snprintf((char *)IObuff, IOSIZE, "%" PRIdLINENR " col %d",
- qfp->qf_lnum, qfp->qf_col);
- }
- vim_snprintf((char *)IObuff + STRLEN(IObuff), IOSIZE, "%s",
- (char *)qf_types(qfp->qf_type, qfp->qf_nr));
- msg_puts_attr((const char *)IObuff, qfLineAttr);
- msg_puts_attr(":", qfSepAttr);
- if (qfp->qf_pattern != NULL) {
- qf_fmt_text(qfp->qf_pattern, IObuff, IOSIZE);
- msg_puts((const char *)IObuff);
- msg_puts_attr(":", qfSepAttr);
- }
- msg_puts(" ");
-
- /* Remove newlines and leading whitespace from the text. For an
- * unrecognized line keep the indent, the compiler may mark a word
- * with ^^^^. */
- qf_fmt_text((fname != NULL || qfp->qf_lnum != 0)
- ? skipwhite(qfp->qf_text) : qfp->qf_text,
- IObuff, IOSIZE);
- msg_prt_line(IObuff, FALSE);
- ui_flush(); /* show one line at a time */
+ qf_list_entry(qfp, i, i == qfl->qf_index);
}
-
-next_entry:
- qfp = qfp->qf_next;
- if (qfp == NULL) {
- break;
- }
- i++;
os_breakcheck();
}
}
-/*
- * Remove newlines and leading whitespace from an error message.
- * Put the result in "buf[bufsize]".
- */
-static void qf_fmt_text(char_u *text, char_u *buf, int bufsize)
+// Remove newlines and leading whitespace from an error message.
+// Put the result in "buf[bufsize]".
+static void qf_fmt_text(const char_u *restrict text, char_u *restrict buf,
+ int bufsize)
+ FUNC_ATTR_NONNULL_ALL
{
int i;
- char_u *p = text;
+ const char_u *p = text;
for (i = 0; *p != NUL && i < bufsize - 1; ++i) {
if (*p == '\n') {
@@ -2695,23 +3141,17 @@ static void qf_msg(qf_info_T *qi, int which, char *lead)
msg(buf);
}
-/*
- * ":colder [count]": Up in the quickfix stack.
- * ":cnewer [count]": Down in the quickfix stack.
- * ":lolder [count]": Up in the location list stack.
- * ":lnewer [count]": Down in the location list stack.
- */
+/// ":colder [count]": Up in the quickfix stack.
+/// ":cnewer [count]": Down in the quickfix stack.
+/// ":lolder [count]": Up in the location list stack.
+/// ":lnewer [count]": Down in the location list stack.
void qf_age(exarg_T *eap)
{
- qf_info_T *qi = &ql_info;
+ qf_info_T *qi;
int count;
- if (eap->cmdidx == CMD_lolder || eap->cmdidx == CMD_lnewer) {
- qi = GET_LOC_LIST(curwin);
- if (qi == NULL) {
- EMSG(_(e_loclist));
- return;
- }
+ if ((qi = qf_cmd_get_stack(eap, true)) == NULL) {
+ return;
}
if (eap->addr_count != 0) {
@@ -2742,14 +3182,10 @@ void qf_age(exarg_T *eap)
/// Display the information about all the quickfix/location lists in the stack.
void qf_history(exarg_T *eap)
{
- qf_info_T *qi = &ql_info;
+ qf_info_T *qi = qf_cmd_get_stack(eap, false);
int i;
- if (eap->cmdidx == CMD_lhistory) {
- qi = GET_LOC_LIST(curwin);
- }
- if (qi == NULL || (qi->qf_listcount == 0
- && qi->qf_lists[qi->qf_curlist].qf_count == 0)) {
+ if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) {
MSG(_("No entries"));
} else {
for (i = 0; i < qi->qf_listcount; i++) {
@@ -2760,12 +3196,11 @@ void qf_history(exarg_T *eap)
/// Free all the entries in the error list "idx". Note that other information
/// associated with the list like context and title are not freed.
-static void qf_free_items(qf_info_T *qi, int idx)
+static void qf_free_items(qf_list_T *qfl)
{
qfline_T *qfp;
qfline_T *qfpnext;
bool stop = false;
- qf_list_T *qfl = &qi->qf_lists[idx];
while (qfl->qf_count && qfl->qf_start != NULL) {
qfp = qfl->qf_start;
@@ -2806,10 +3241,9 @@ static void qf_free_items(qf_info_T *qi, int idx)
/// Free error list "idx". Frees all the entries in the quickfix list,
/// associated context information and the title.
-static void qf_free(qf_info_T *qi, int idx)
+static void qf_free(qf_list_T *qfl)
{
- qf_list_T *qfl = &qi->qf_lists[idx];
- qf_free_items(qi, idx);
+ qf_free_items(qfl);
XFREE_CLEAR(qfl->qf_title);
tv_free(qfl->qf_ctx);
@@ -2818,9 +3252,7 @@ static void qf_free(qf_info_T *qi, int idx)
qfl->qf_changedtick = 0L;
}
-/*
- * qf_mark_adjust: adjust marks
- */
+// qf_mark_adjust: adjust marks
bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount,
long amount_after)
{
@@ -2841,11 +3273,10 @@ bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount,
qi = wp->w_llist;
}
- for (idx = 0; idx < qi->qf_listcount; ++idx)
- if (qi->qf_lists[idx].qf_count)
- for (i = 0, qfp = qi->qf_lists[idx].qf_start;
- i < qi->qf_lists[idx].qf_count && qfp != NULL;
- i++, qfp = qfp->qf_next) {
+ for (idx = 0; idx < qi->qf_listcount; idx++) {
+ qf_list_T *qfl = qf_get_list(qi, idx);
+ if (!qf_list_empty(qfl)) {
+ FOR_ALL_QFL_ITEMS(qfl, qfp, i) {
if (qfp->qf_fnum == curbuf->b_fnum) {
found_one = true;
if (qfp->qf_lnum >= line1 && qfp->qf_lnum <= line2) {
@@ -2857,25 +3288,25 @@ bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount,
qfp->qf_lnum += amount_after;
}
}
+ }
+ }
return found_one;
}
-/*
- * Make a nice message out of the error character and the error number:
- * char number message
- * e or E 0 " error"
- * w or W 0 " warning"
- * i or I 0 " info"
- * 0 0 ""
- * other 0 " c"
- * e or E n " error n"
- * w or W n " warning n"
- * i or I n " info n"
- * 0 n " error n"
- * other n " c n"
- * 1 x "" :helpgrep
- */
+// Make a nice message out of the error character and the error number:
+// char number message
+// e or E 0 " error"
+// w or W 0 " warning"
+// i or I 0 " info"
+// 0 0 ""
+// other 0 " c"
+// e or E n " error n"
+// w or W n " warning n"
+// i or I n " info n"
+// 0 n " error n"
+// other n " c n"
+// 1 x "" :helpgrep
static char_u *qf_types(int c, int nr)
{
static char_u buf[20];
@@ -2916,7 +3347,7 @@ void qf_view_result(bool split)
if (IS_LL_WINDOW(curwin)) {
qi = GET_LOC_LIST(curwin);
}
- if (qf_list_empty(qi, qi->qf_curlist)) {
+ if (qf_list_empty(qf_get_curlist(qi))) {
EMSG(_(e_quickfix));
return;
}
@@ -2936,83 +3367,175 @@ void qf_view_result(bool split)
do_cmdline_cmd((IS_LL_WINDOW(curwin) ? ".ll" : ".cc"));
}
-/*
- * ":cwindow": open the quickfix window if we have errors to display,
- * close it if not.
- * ":lwindow": open the location list window if we have locations to display,
- * close it if not.
- */
+// ":cwindow": open the quickfix window if we have errors to display,
+// close it if not.
+// ":lwindow": open the location list window if we have locations to display,
+// close it if not.
void ex_cwindow(exarg_T *eap)
{
- qf_info_T *qi = &ql_info;
+ qf_info_T *qi;
+ qf_list_T *qfl;
win_T *win;
- if (eap->cmdidx == CMD_lwindow) {
- qi = GET_LOC_LIST(curwin);
- if (qi == NULL)
- return;
+ if ((qi = qf_cmd_get_stack(eap, true)) == NULL) {
+ return;
}
- /* Look for an existing quickfix window. */
+ qfl = qf_get_curlist(qi);
+
+ // Look for an existing quickfix window.
win = qf_find_win(qi);
- /*
- * If a quickfix window is open but we have no errors to display,
- * close the window. If a quickfix window is not open, then open
- * it if we have errors; otherwise, leave it closed.
- */
- if (qi->qf_lists[qi->qf_curlist].qf_nonevalid
- || qi->qf_lists[qi->qf_curlist].qf_count == 0
- || qi->qf_curlist >= qi->qf_listcount) {
- if (win != NULL)
+ // If a quickfix window is open but we have no errors to display,
+ // close the window. If a quickfix window is not open, then open
+ // it if we have errors; otherwise, leave it closed.
+ if (qf_stack_empty(qi)
+ || qfl->qf_nonevalid
+ || qf_list_empty(qf_get_curlist(qi))) {
+ if (win != NULL) {
ex_cclose(eap);
- } else if (win == NULL)
+ }
+ } else if (win == NULL) {
ex_copen(eap);
+ }
}
-/*
- * ":cclose": close the window showing the list of errors.
- * ":lclose": close the window showing the location list
- */
+// ":cclose": close the window showing the list of errors.
+// ":lclose": close the window showing the location list
void ex_cclose(exarg_T *eap)
{
- win_T *win = NULL;
- qf_info_T *qi = &ql_info;
+ win_T *win = NULL;
+ qf_info_T *qi;
- if (eap->cmdidx == CMD_lclose || eap->cmdidx == CMD_lwindow) {
- qi = GET_LOC_LIST(curwin);
- if (qi == NULL)
- return;
+ if ((qi = qf_cmd_get_stack(eap, false)) == NULL) {
+ return;
}
- /* Find existing quickfix window and close it. */
+ // Find existing quickfix window and close it.
win = qf_find_win(qi);
if (win != NULL) {
win_close(win, false);
}
}
-/*
- * ":copen": open a window that shows the list of errors.
- * ":lopen": open a window that shows the location list.
- */
+// Goto a quickfix or location list window (if present).
+// Returns OK if the window is found, FAIL otherwise.
+static int qf_goto_cwindow(const qf_info_T *qi, bool resize, int sz,
+ bool vertsplit)
+{
+ win_T *const win = qf_find_win(qi);
+ if (win == NULL) {
+ return FAIL;
+ }
+
+ win_goto(win);
+ if (resize) {
+ if (vertsplit) {
+ if (sz != win->w_width) {
+ win_setwidth(sz);
+ }
+ } else if (sz != win->w_height
+ && (win->w_height + win->w_status_height + tabline_height()
+ < cmdline_row)) {
+ win_setheight(sz);
+ }
+ }
+
+ return OK;
+}
+
+// Open a new quickfix or location list window, load the quickfix buffer and
+// set the appropriate options for the window.
+// Returns FAIL if the window could not be opened.
+static int qf_open_new_cwindow(const qf_info_T *qi, int height)
+{
+ win_T *oldwin = curwin;
+ const tabpage_T *const prevtab = curtab;
+ int flags = 0;
+
+ const buf_T *const qf_buf = qf_find_buf(qi);
+
+ // The current window becomes the previous window afterwards.
+ win_T *const win = curwin;
+
+ if (IS_QF_STACK(qi) && cmdmod.split == 0) {
+ // Create the new quickfix window at the very bottom, except when
+ // :belowright or :aboveleft is used.
+ win_goto(lastwin);
+ }
+ // Default is to open the window below the current window
+ if (cmdmod.split == 0) {
+ flags = WSP_BELOW;
+ }
+ flags |= WSP_NEWLOC;
+ if (win_split(height, flags) == FAIL) {
+ return FAIL; // not enough room for window
+ }
+ RESET_BINDING(curwin);
+
+ if (IS_LL_STACK(qi)) {
+ // For the location list window, create a reference to the
+ // location list from the window 'win'.
+ curwin->w_llist_ref = win->w_llist;
+ win->w_llist->qf_refcount++;
+ }
+
+ if (oldwin != curwin) {
+ oldwin = NULL; // don't store info when in another window
+ }
+ if (qf_buf != NULL) {
+ // Use the existing quickfix buffer
+ (void)do_ecmd(qf_buf->b_fnum, NULL, NULL, NULL, ECMD_ONE,
+ ECMD_HIDE + ECMD_OLDBUF, oldwin);
+ } else {
+ // Create a new quickfix buffer
+ (void)do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, oldwin);
+
+ // switch off 'swapfile'
+ set_option_value("swf", 0L, NULL, OPT_LOCAL);
+ set_option_value("bt", 0L, "quickfix", OPT_LOCAL);
+ set_option_value("bh", 0L, "wipe", OPT_LOCAL);
+ RESET_BINDING(curwin);
+ curwin->w_p_diff = false;
+ set_option_value("fdm", 0L, "manual", OPT_LOCAL);
+ }
+
+ // Only set the height when still in the same tab page and there is no
+ // window to the side.
+ if (curtab == prevtab && curwin->w_width == Columns) {
+ win_setheight(height);
+ }
+ curwin->w_p_wfh = true; // set 'winfixheight'
+ if (win_valid(win)) {
+ prevwin = win;
+ }
+ return OK;
+}
+
+/// Set "w:quickfix_title" if "qi" has a title.
+static void qf_set_title_var(qf_list_T *qfl)
+{
+ if (qfl->qf_title != NULL) {
+ set_internal_string_var((char_u *)"w:quickfix_title", qfl->qf_title);
+ }
+}
+
+/// ":copen": open a window that shows the list of errors.
+/// ":lopen": open a window that shows the location list.
void ex_copen(exarg_T *eap)
{
- qf_info_T *qi = &ql_info;
+ qf_info_T *qi;
+ qf_list_T *qfl;
int height;
- win_T *win;
- tabpage_T *prevtab = curtab;
- buf_T *qf_buf;
- win_T *oldwin = curwin;
+ int status = FAIL;
+ int lnum;
- if (eap->cmdidx == CMD_lopen || eap->cmdidx == CMD_lwindow) {
- qi = GET_LOC_LIST(curwin);
- if (qi == NULL) {
- EMSG(_(e_loclist));
- return;
- }
+ if ((qi = qf_cmd_get_stack(eap, true)) == NULL) {
+ return;
}
+ incr_quickfix_busy();
+
if (eap->addr_count != 0) {
assert(eap->line2 <= INT_MAX);
height = (int)eap->line2;
@@ -3021,94 +3544,33 @@ void ex_copen(exarg_T *eap)
}
reset_VIsual_and_resel(); // stop Visual mode
- /*
- * Find existing quickfix window, or open a new one.
- */
- win = qf_find_win(qi);
-
- if (win != NULL && cmdmod.tab == 0) {
- win_goto(win);
- if (eap->addr_count != 0) {
- if (cmdmod.split & WSP_VERT) {
- if (height != win->w_width) {
- win_setwidth(height);
- }
- } else {
- if (height != win->w_height) {
- win_setheight(height);
- }
- }
- }
- } else {
- int flags = 0;
-
- qf_buf = qf_find_buf(qi);
-
- /* The current window becomes the previous window afterwards. */
- win = curwin;
-
- if ((eap->cmdidx == CMD_copen || eap->cmdidx == CMD_cwindow)
- && cmdmod.split == 0)
- // Create the new quickfix window at the very bottom, except when
- // :belowright or :aboveleft is used.
- win_goto(lastwin);
- // Default is to open the window below the current window
- if (cmdmod.split == 0) {
- flags = WSP_BELOW;
- }
- flags |= WSP_NEWLOC;
- if (win_split(height, flags) == FAIL) {
- return; // not enough room for window
+ // Find an existing quickfix window, or open a new one.
+ if (cmdmod.tab == 0) {
+ status = qf_goto_cwindow(qi, eap->addr_count != 0, height,
+ cmdmod.split & WSP_VERT);
+ }
+ if (status == FAIL) {
+ if (qf_open_new_cwindow(qi, height) == FAIL) {
+ decr_quickfix_busy();
+ return;
}
- RESET_BINDING(curwin);
+ }
- if (eap->cmdidx == CMD_lopen || eap->cmdidx == CMD_lwindow) {
- /*
- * For the location list window, create a reference to the
- * location list from the window 'win'.
- */
- curwin->w_llist_ref = win->w_llist;
- win->w_llist->qf_refcount++;
- }
-
- if (oldwin != curwin)
- oldwin = NULL; /* don't store info when in another window */
- if (qf_buf != NULL)
- /* Use the existing quickfix buffer */
- (void)do_ecmd(qf_buf->b_fnum, NULL, NULL, NULL, ECMD_ONE,
- ECMD_HIDE + ECMD_OLDBUF, oldwin);
- else {
- /* Create a new quickfix buffer */
- (void)do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, oldwin);
- // Switch off 'swapfile'.
- set_option_value("swf", 0L, NULL, OPT_LOCAL);
- set_option_value("bt", 0L, "quickfix", OPT_LOCAL);
- set_option_value("bh", 0L, "wipe", OPT_LOCAL);
- RESET_BINDING(curwin);
- curwin->w_p_diff = false;
- set_option_value("fdm", 0L, "manual", OPT_LOCAL);
- }
-
- /* Only set the height when still in the same tab page and there is no
- * window to the side. */
- if (curtab == prevtab
- && curwin->w_width == Columns
- )
- win_setheight(height);
- curwin->w_p_wfh = TRUE; /* set 'winfixheight' */
- if (win_valid(win))
- prevwin = win;
- }
-
- qf_set_title_var(qi);
+ qfl = qf_get_curlist(qi);
+ qf_set_title_var(qfl);
+ // Save the current index here, as updating the quickfix buffer may free
+ // the quickfix list
+ lnum = qfl->qf_index;
// Fill the buffer with the quickfix list.
- qf_fill_buffer(qi, curbuf, NULL);
+ qf_fill_buffer(qfl, curbuf, NULL);
+
+ decr_quickfix_busy();
- curwin->w_cursor.lnum = qi->qf_lists[qi->qf_curlist].qf_index;
+ curwin->w_cursor.lnum = lnum;
curwin->w_cursor.col = 0;
check_cursor();
- update_topline(); /* scroll to show the line */
+ update_topline(); // scroll to show the line
}
// Move the cursor in the quickfix window to "lnum".
@@ -3129,17 +3591,13 @@ static void qf_win_goto(win_T *win, linenr_T lnum)
curbuf = curwin->w_buffer;
}
-// :cbottom/:lbottom command.
+/// :cbottom/:lbottom command.
void ex_cbottom(exarg_T *eap)
{
- qf_info_T *qi = &ql_info;
+ qf_info_T *qi;
- if (eap->cmdidx == CMD_lbottom) {
- qi = GET_LOC_LIST(curwin);
- if (qi == NULL) {
- EMSG(_(e_loclist));
- return;
- }
+ if ((qi = qf_cmd_get_stack(eap, true)) == NULL) {
+ return;
}
win_T *win = qf_find_win(qi);
@@ -3149,38 +3607,32 @@ void ex_cbottom(exarg_T *eap)
}
}
-/*
- * Return the number of the current entry (line number in the quickfix
- * window).
- */
+// Return the number of the current entry (line number in the quickfix
+// window).
linenr_T qf_current_entry(win_T *wp)
{
qf_info_T *qi = &ql_info;
- if (IS_LL_WINDOW(wp))
- /* In the location list window, use the referenced location list */
+ if (IS_LL_WINDOW(wp)) {
+ // In the location list window, use the referenced location list
qi = wp->w_llist_ref;
+ }
- return qi->qf_lists[qi->qf_curlist].qf_index;
+ return qf_get_curlist(qi)->qf_index;
}
-/*
- * Update the cursor position in the quickfix window to the current error.
- * Return TRUE if there is a quickfix window.
- */
-static int
-qf_win_pos_update (
+// Update the cursor position in the quickfix window to the current error.
+// Return TRUE if there is a quickfix window.
+static int qf_win_pos_update(
qf_info_T *qi,
- int old_qf_index /* previous qf_index or zero */
+ int old_qf_index // previous qf_index or zero
)
{
win_T *win;
- int qf_index = qi->qf_lists[qi->qf_curlist].qf_index;
+ int qf_index = qf_get_curlist(qi)->qf_index;
- /*
- * Put the cursor on the current error in the quickfix window, so that
- * it's viewable.
- */
+ // Put the cursor on the current error in the quickfix window, so that
+ // it's viewable.
win = qf_find_win(qi);
if (win != NULL
&& qf_index <= win->w_buffer->b_ml.ml_line_count
@@ -3198,8 +3650,9 @@ qf_win_pos_update (
}
/// Checks whether the given window is displaying the specified
-/// quickfix/location list buffer.
-static int is_qf_win(win_T *win, qf_info_T *qi)
+/// quickfix/location stack.
+static int is_qf_win(const win_T *win, const qf_info_T *qi)
+ FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
//
// A window displaying the quickfix buffer will have the w_llist_ref field
@@ -3217,9 +3670,10 @@ static int is_qf_win(win_T *win, qf_info_T *qi)
return false;
}
-/// Find a window displaying the quickfix/location list 'qi'
+/// Find a window displaying the quickfix/location stack 'qi'
/// Only searches in the current tabpage.
-static win_T *qf_find_win(qf_info_T *qi)
+static win_T *qf_find_win(const qf_info_T *qi)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
if (is_qf_win(win, qi)) {
@@ -3230,11 +3684,10 @@ static win_T *qf_find_win(qf_info_T *qi)
return NULL;
}
-/*
- * Find a quickfix buffer.
- * Searches in windows opened in all the tabs.
- */
-static buf_T *qf_find_buf(qf_info_T *qi)
+// Find a quickfix buffer.
+// Searches in windows opened in all the tabs.
+static buf_T *qf_find_buf(const qf_info_T *qi)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
FOR_ALL_TAB_WINDOWS(tp, win) {
if (is_qf_win(win, qi)) {
@@ -3253,21 +3706,19 @@ static void qf_update_win_titlevar(qf_info_T *qi)
if ((win = qf_find_win(qi)) != NULL) {
win_T *curwin_save = curwin;
curwin = win;
- qf_set_title_var(qi);
+ qf_set_title_var(qf_get_curlist(qi));
curwin = curwin_save;
}
}
-/*
- * Find the quickfix buffer. If it exists, update the contents.
- */
+// Find the quickfix buffer. If it exists, update the contents.
static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last)
{
buf_T *buf;
win_T *win;
aco_save_T aco;
- /* Check if a buffer for the quickfix list exists. Update it. */
+ // Check if a buffer for the quickfix list exists. Update it.
buf = qf_find_buf(qi);
if (buf != NULL) {
linenr_T old_line_count = buf->b_ml.ml_line_count;
@@ -3279,7 +3730,7 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last)
qf_update_win_titlevar(qi);
- qf_fill_buffer(qi, buf, old_last);
+ qf_fill_buffer(qf_get_curlist(qi), buf, old_last);
buf_inc_changedtick(buf);
if (old_last == NULL) {
@@ -3297,26 +3748,82 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last)
}
}
-// Set "w:quickfix_title" if "qi" has a title.
-static void qf_set_title_var(qf_info_T *qi)
+// Add an error line to the quickfix buffer.
+static int qf_buf_add_line(buf_T *buf, linenr_T lnum, const qfline_T *qfp,
+ char_u *dirname)
+ FUNC_ATTR_NONNULL_ALL
{
- if (qi->qf_lists[qi->qf_curlist].qf_title != NULL) {
- set_internal_string_var((char_u *)"w:quickfix_title",
- qi->qf_lists[qi->qf_curlist].qf_title);
+ int len;
+ buf_T *errbuf;
+
+ if (qfp->qf_module != NULL) {
+ STRCPY(IObuff, qfp->qf_module);
+ len = (int)STRLEN(IObuff);
+ } else if (qfp->qf_fnum != 0
+ && (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL
+ && errbuf->b_fname != NULL) {
+ if (qfp->qf_type == 1) { // :helpgrep
+ STRLCPY(IObuff, path_tail(errbuf->b_fname), sizeof(IObuff));
+ } else {
+ // shorten the file name if not done already
+ if (errbuf->b_sfname == NULL
+ || path_is_absolute(errbuf->b_sfname)) {
+ if (*dirname == NUL) {
+ os_dirname(dirname, MAXPATHL);
+ }
+ shorten_buf_fname(errbuf, dirname, false);
+ }
+ STRLCPY(IObuff, errbuf->b_fname, sizeof(IObuff));
+ }
+ len = (int)STRLEN(IObuff);
+ } else {
+ len = 0;
}
+ IObuff[len++] = '|';
+
+ if (qfp->qf_lnum > 0) {
+ snprintf((char *)IObuff + len, sizeof(IObuff), "%" PRId64,
+ (int64_t)qfp->qf_lnum);
+ len += (int)STRLEN(IObuff + len);
+
+ if (qfp->qf_col > 0) {
+ snprintf((char *)IObuff + len, sizeof(IObuff), " col %d", qfp->qf_col);
+ len += (int)STRLEN(IObuff + len);
+ }
+
+ snprintf((char *)IObuff + len, sizeof(IObuff), "%s",
+ (char *)qf_types(qfp->qf_type, qfp->qf_nr));
+ len += (int)STRLEN(IObuff + len);
+ } else if (qfp->qf_pattern != NULL) {
+ qf_fmt_text(qfp->qf_pattern, IObuff + len, IOSIZE - len);
+ len += (int)STRLEN(IObuff + len);
+ }
+ IObuff[len++] = '|';
+ IObuff[len++] = ' ';
+
+ // Remove newlines and leading whitespace from the text.
+ // For an unrecognized line keep the indent, the compiler may
+ // mark a word with ^^^^.
+ qf_fmt_text(len > 3 ? skipwhite(qfp->qf_text) : qfp->qf_text,
+ IObuff + len, IOSIZE - len);
+
+ if (ml_append_buf(buf, lnum, IObuff,
+ (colnr_T)STRLEN(IObuff) + 1, false) == FAIL) {
+ return FAIL;
+ }
+ return OK;
}
-// Fill current buffer with quickfix errors, replacing any previous contents.
-// curbuf must be the quickfix buffer!
-// If "old_last" is not NULL append the items after this one.
-// When "old_last" is NULL then "buf" must equal "curbuf"! Because ml_delete()
-// is used and autocommands will be triggered.
-static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last)
+/// Fill current buffer with quickfix errors, replacing any previous contents.
+/// curbuf must be the quickfix buffer!
+/// If "old_last" is not NULL append the items after this one.
+/// When "old_last" is NULL then "buf" must equal "curbuf"! Because ml_delete()
+/// is used and autocommands will be triggered.
+static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last)
+ FUNC_ATTR_NONNULL_ARG(2)
{
linenr_T lnum;
qfline_T *qfp;
- buf_T *errbuf;
- int len;
const bool old_KeyTyped = KeyTyped;
if (old_last == NULL) {
@@ -3331,73 +3838,22 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last)
}
}
- /* Check if there is anything to display */
- if (qi->qf_curlist < qi->qf_listcount) {
+ // Check if there is anything to display
+ if (qfl != NULL) {
char_u dirname[MAXPATHL];
*dirname = NUL;
// Add one line for each error
if (old_last == NULL) {
- qfp = qi->qf_lists[qi->qf_curlist].qf_start;
+ qfp = qfl->qf_start;
lnum = 0;
} else {
qfp = old_last->qf_next;
lnum = buf->b_ml.ml_line_count;
}
- while (lnum < qi->qf_lists[qi->qf_curlist].qf_count) {
- if (qfp->qf_module != NULL) {
- STRCPY(IObuff, qfp->qf_module);
- len = (int)STRLEN(IObuff);
- } else if (qfp->qf_fnum != 0
- && (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL
- && errbuf->b_fname != NULL) {
- if (qfp->qf_type == 1) { // :helpgrep
- STRLCPY(IObuff, path_tail(errbuf->b_fname), sizeof(IObuff));
- } else {
- // shorten the file name if not done already
- if (errbuf->b_sfname == NULL
- || path_is_absolute(errbuf->b_sfname)) {
- if (*dirname == NUL) {
- os_dirname(dirname, MAXPATHL);
- }
- shorten_buf_fname(errbuf, dirname, false);
- }
- STRLCPY(IObuff, errbuf->b_fname, sizeof(IObuff));
- }
- len = (int)STRLEN(IObuff);
- } else {
- len = 0;
- }
- IObuff[len++] = '|';
-
- if (qfp->qf_lnum > 0) {
- sprintf((char *)IObuff + len, "%" PRId64, (int64_t)qfp->qf_lnum);
- len += (int)STRLEN(IObuff + len);
-
- if (qfp->qf_col > 0) {
- sprintf((char *)IObuff + len, " col %d", qfp->qf_col);
- len += (int)STRLEN(IObuff + len);
- }
-
- sprintf((char *)IObuff + len, "%s",
- (char *)qf_types(qfp->qf_type, qfp->qf_nr));
- len += (int)STRLEN(IObuff + len);
- } else if (qfp->qf_pattern != NULL) {
- qf_fmt_text(qfp->qf_pattern, IObuff + len, IOSIZE - len);
- len += (int)STRLEN(IObuff + len);
- }
- IObuff[len++] = '|';
- IObuff[len++] = ' ';
-
- /* Remove newlines and leading whitespace from the text.
- * For an unrecognized line keep the indent, the compiler may
- * mark a word with ^^^^. */
- qf_fmt_text(len > 3 ? skipwhite(qfp->qf_text) : qfp->qf_text,
- IObuff + len, IOSIZE - len);
-
- if (ml_append_buf(buf, lnum, IObuff, (colnr_T)STRLEN(IObuff) + 1, false)
- == FAIL) {
+ while (lnum < qfl->qf_count) {
+ if (qf_buf_add_line(buf, lnum, qfp, dirname) == FAIL) {
break;
}
lnum++;
@@ -3435,13 +3891,13 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last)
redraw_curbuf_later(NOT_VALID);
}
- /* Restore KeyTyped, setting 'filetype' may reset it. */
+ // Restore KeyTyped, setting 'filetype' may reset it.
KeyTyped = old_KeyTyped;
}
-static void qf_list_changed(qf_info_T *qi, int qf_idx)
+static void qf_list_changed(qf_list_T *qfl)
{
- qi->qf_lists[qf_idx].qf_changedtick++;
+ qfl->qf_changedtick++;
}
/// Return the quickfix/location list number with the given identifier.
@@ -3457,15 +3913,15 @@ static int qf_id2nr(const qf_info_T *const qi, const unsigned qfid)
return INVALID_QFIDX;
}
-// If the current list is not "save_qfid" and we can find the list with that ID
-// then make it the current list.
-// This is used when autocommands may have changed the current list.
-// Returns OK if successfully restored the list. Returns FAIL if the list with
-// the specified identifier (save_qfid) is not found in the stack.
+/// If the current list is not "save_qfid" and we can find the list with that ID
+/// then make it the current list.
+/// This is used when autocommands may have changed the current list.
+/// Returns OK if successfully restored the list. Returns FAIL if the list with
+/// the specified identifier (save_qfid) is not found in the stack.
static int qf_restore_list(qf_info_T *qi, unsigned save_qfid)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- if (qi->qf_lists[qi->qf_curlist].qf_id != save_qfid) {
+ if (qf_get_curlist(qi)->qf_id != save_qfid) {
const int curlist = qf_id2nr(qi, save_qfid);
if (curlist < 0) {
// list is not present
@@ -3484,14 +3940,12 @@ static void qf_jump_first(qf_info_T *qi, unsigned save_qfid, int forceit)
return;
}
// Autocommands might have cleared the list, check for that
- if (!qf_list_empty(qi, qi->qf_curlist)) {
+ if (!qf_list_empty(qf_get_curlist(qi))) {
qf_jump(qi, 0, 0, forceit);
}
}
-/*
- * Return TRUE when using ":vimgrep" for ":grep".
- */
+// Return TRUE when using ":vimgrep" for ":grep".
int grep_internal(cmdidx_T cmdidx)
{
return (cmdidx == CMD_grep
@@ -3502,33 +3956,74 @@ int grep_internal(cmdidx_T cmdidx)
*curbuf->b_p_gp == NUL ? p_gp : curbuf->b_p_gp) == 0;
}
-/*
- * Used for ":make", ":lmake", ":grep", ":lgrep", ":grepadd", and ":lgrepadd"
- */
+// Return the make/grep autocmd name.
+static char_u *make_get_auname(cmdidx_T cmdidx)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ switch (cmdidx) {
+ case CMD_make:
+ return (char_u *)"make";
+ case CMD_lmake:
+ return (char_u *)"lmake";
+ case CMD_grep:
+ return (char_u *)"grep";
+ case CMD_lgrep:
+ return (char_u *)"lgrep";
+ case CMD_grepadd:
+ return (char_u *)"grepadd";
+ case CMD_lgrepadd:
+ return (char_u *)"lgrepadd";
+ default:
+ return NULL;
+ }
+}
+
+// Form the complete command line to invoke 'make'/'grep'. Quote the command
+// using 'shellquote' and append 'shellpipe'. Echo the fully formed command.
+static char *make_get_fullcmd(const char_u *makecmd, const char_u *fname)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
+{
+ size_t len = STRLEN(p_shq) * 2 + STRLEN(makecmd) + 1;
+ if (*p_sp != NUL) {
+ len += STRLEN(p_sp) + STRLEN(fname) + 3;
+ }
+ char *const cmd = xmalloc(len);
+ snprintf(cmd, len, "%s%s%s", (char *)p_shq, (char *)makecmd, (char *)p_shq);
+
+ // If 'shellpipe' empty: don't redirect to 'errorfile'.
+ if (*p_sp != NUL) {
+ append_redir(cmd, len, (char *)p_sp, (char *)fname);
+ }
+
+ // Display the fully formed command. Output a newline if there's something
+ // else than the :make command that was typed (in which case the cursor is
+ // in column 0).
+ if (msg_col == 0) {
+ msg_didout = false;
+ }
+ msg_start();
+ MSG_PUTS(":!");
+ msg_outtrans((char_u *)cmd); // show what we are doing
+
+ return cmd;
+}
+
+// Used for ":make", ":lmake", ":grep", ":lgrep", ":grepadd", and ":lgrepadd"
void ex_make(exarg_T *eap)
{
char_u *fname;
win_T *wp = NULL;
qf_info_T *qi = &ql_info;
int res;
- char_u *au_name = NULL;
char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc;
- /* Redirect ":grep" to ":vimgrep" if 'grepprg' is "internal". */
+ // Redirect ":grep" to ":vimgrep" if 'grepprg' is "internal".
if (grep_internal(eap->cmdidx)) {
ex_vimgrep(eap);
return;
}
- switch (eap->cmdidx) {
- case CMD_make: au_name = (char_u *)"make"; break;
- case CMD_lmake: au_name = (char_u *)"lmake"; break;
- case CMD_grep: au_name = (char_u *)"grep"; break;
- case CMD_lgrep: au_name = (char_u *)"lgrep"; break;
- case CMD_grepadd: au_name = (char_u *)"grepadd"; break;
- case CMD_lgrepadd: au_name = (char_u *)"lgrepadd"; break;
- default: break;
- }
+ char_u *const au_name = make_get_auname(eap->cmdidx);
if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name,
curbuf->b_fname, true, curbuf)) {
if (aborting()) {
@@ -3536,9 +4031,9 @@ void ex_make(exarg_T *eap)
}
}
- if (eap->cmdidx == CMD_lmake || eap->cmdidx == CMD_lgrep
- || eap->cmdidx == CMD_lgrepadd)
+ if (is_loclist_cmd(eap->cmdidx)) {
wp = curwin;
+ }
autowrite_all();
fname = get_mef_name();
@@ -3546,28 +4041,11 @@ void ex_make(exarg_T *eap)
return;
os_remove((char *)fname); // in case it's not unique
- // If 'shellpipe' empty: don't redirect to 'errorfile'.
- const size_t len = (STRLEN(p_shq) * 2 + STRLEN(eap->arg) + 1
- + (*p_sp == NUL
- ? 0
- : STRLEN(p_sp) + STRLEN(fname) + 3));
- char *const cmd = xmalloc(len);
- snprintf(cmd, len, "%s%s%s", (char *)p_shq, (char *)eap->arg,
- (char *)p_shq);
- if (*p_sp != NUL) {
- append_redir(cmd, len, (char *) p_sp, (char *) fname);
- }
- // Output a newline if there's something else than the :make command that
- // was typed (in which case the cursor is in column 0).
- if (msg_col == 0) {
- msg_didout = false;
- }
- msg_start();
- MSG_PUTS(":!");
- msg_outtrans((char_u *)cmd); // show what we are doing
+ char *const cmd = make_get_fullcmd(eap->arg, fname);
do_shell((char_u *)cmd, 0);
+ incr_quickfix_busy();
res = qf_init(wp, fname, (eap->cmdidx != CMD_make
&& eap->cmdidx != CMD_lmake) ? p_gefm : p_efm,
@@ -3580,11 +4058,11 @@ void ex_make(exarg_T *eap)
}
}
if (res >= 0) {
- qf_list_changed(qi, qi->qf_curlist);
+ qf_list_changed(qf_get_curlist(qi));
}
// Remember the current quickfix list identifier, so that we can
// check for autocommands changing the current quickfix list.
- unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id;
+ unsigned save_qfid = qf_get_curlist(qi)->qf_id;
if (au_name != NULL) {
apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, true,
curbuf);
@@ -3595,16 +4073,15 @@ void ex_make(exarg_T *eap)
}
cleanup:
+ decr_quickfix_busy();
os_remove((char *)fname);
xfree(fname);
xfree(cmd);
}
-/*
- * Return the name for the errorfile, in allocated memory.
- * Find a new unique name when 'makeef' contains "##".
- * Returns NULL for error.
- */
+// Return the name for the errorfile, in allocated memory.
+// Find a new unique name when 'makeef' contains "##".
+// Returns NULL for error.
static char_u *get_mef_name(void)
{
char_u *p;
@@ -3626,7 +4103,7 @@ static char_u *get_mef_name(void)
if (*p == NUL)
return vim_strsave(p_mef);
- /* Keep trying until the name doesn't exist yet. */
+ // Keep trying until the name doesn't exist yet.
for (;; ) {
if (start == -1) {
start = (int)os_get_pid();
@@ -3652,23 +4129,20 @@ static char_u *get_mef_name(void)
size_t qf_get_size(exarg_T *eap)
FUNC_ATTR_NONNULL_ALL
{
- qf_info_T *qi = &ql_info;
- if (eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo) {
- // Location list.
- qi = GET_LOC_LIST(curwin);
- if (qi == NULL) {
- return 0;
- }
+ qf_info_T *qi;
+ qf_list_T *qfl;
+
+ if ((qi = qf_cmd_get_stack(eap, false)) == NULL) {
+ return 0;
}
int prev_fnum = 0;
size_t sz = 0;
qfline_T *qfp;
- size_t i;
- assert(qi->qf_lists[qi->qf_curlist].qf_count >= 0);
- for (i = 0, qfp = qi->qf_lists[qi->qf_curlist].qf_start;
- i < (size_t)qi->qf_lists[qi->qf_curlist].qf_count && qfp != NULL;
- i++, qfp = qfp->qf_next) {
+ int i;
+ assert(qf_get_curlist(qi)->qf_count >= 0);
+ qfl = qf_get_curlist(qi);
+ FOR_ALL_QFL_ITEMS(qfl, qfp, i) {
if (!qfp->qf_valid) {
continue;
}
@@ -3691,18 +4165,14 @@ size_t qf_get_size(exarg_T *eap)
size_t qf_get_cur_idx(exarg_T *eap)
FUNC_ATTR_NONNULL_ALL
{
- qf_info_T *qi = &ql_info;
+ qf_info_T *qi;
- if (eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo) {
- // Location list.
- qi = GET_LOC_LIST(curwin);
- if (qi == NULL) {
- return 0;
- }
+ if ((qi = qf_cmd_get_stack(eap, false)) == NULL) {
+ return 0;
}
- assert(qi->qf_lists[qi->qf_curlist].qf_index >= 0);
- return (size_t)qi->qf_lists[qi->qf_curlist].qf_index;
+ assert(qf_get_curlist(qi)->qf_index >= 0);
+ return (size_t)qf_get_curlist(qi)->qf_index;
}
/// Returns the current index in the quickfix/location list,
@@ -3711,20 +4181,16 @@ size_t qf_get_cur_idx(exarg_T *eap)
int qf_get_cur_valid_idx(exarg_T *eap)
FUNC_ATTR_NONNULL_ALL
{
- qf_info_T *qi = &ql_info;
+ qf_info_T *qi;
- if (eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo) {
- // Location list.
- qi = GET_LOC_LIST(curwin);
- if (qi == NULL) {
- return 1;
- }
+ if ((qi = qf_cmd_get_stack(eap, false)) == NULL) {
+ return 1;
}
- qf_list_T *qfl = &qi->qf_lists[qi->qf_curlist];
+ qf_list_T *qfl = qf_get_curlist(qi);
// Check if the list has valid errors.
- if (qfl->qf_count <= 0 || qfl->qf_nonevalid) {
+ if (!qf_list_has_valid_entries(qfl)) {
return 1;
}
@@ -3759,24 +4225,20 @@ int qf_get_cur_valid_idx(exarg_T *eap)
/// Used by :cdo, :ldo, :cfdo and :lfdo commands.
/// For :cdo and :ldo, returns the 'n'th valid error entry.
/// For :cfdo and :lfdo, returns the 'n'th valid file entry.
-static size_t qf_get_nth_valid_entry(qf_info_T *qi, size_t n, bool fdo)
+static size_t qf_get_nth_valid_entry(qf_list_T *qfl, size_t n, int fdo)
FUNC_ATTR_NONNULL_ALL
{
- qf_list_T *qfl = &qi->qf_lists[qi->qf_curlist];
-
// Check if the list has valid errors.
- if (qfl->qf_count <= 0 || qfl->qf_nonevalid) {
+ if (!qf_list_has_valid_entries(qfl)) {
return 1;
}
int prev_fnum = 0;
size_t eidx = 0;
- size_t i;
+ int i;
qfline_T *qfp;
assert(qfl->qf_count >= 0);
- for (i = 1, qfp = qfl->qf_start;
- i <= (size_t)qfl->qf_count && qfp != NULL;
- i++, qfp = qfp->qf_next) {
+ FOR_ALL_QFL_ITEMS(qfl, qfp, i) {
if (qfp->qf_valid) {
if (fdo) {
if (qfp->qf_fnum > 0 && qfp->qf_fnum != prev_fnum) {
@@ -3794,41 +4256,39 @@ static size_t qf_get_nth_valid_entry(qf_info_T *qi, size_t n, bool fdo)
}
}
- return i <= (size_t)qfl->qf_count ? i : 1;
+ return i <= qfl->qf_count ? (size_t)i : 1;
}
-/*
- * ":cc", ":crewind", ":cfirst" and ":clast".
- * ":ll", ":lrewind", ":lfirst" and ":llast".
- * ":cdo", ":ldo", ":cfdo" and ":lfdo".
- */
+/// ":cc", ":crewind", ":cfirst" and ":clast".
+/// ":ll", ":lrewind", ":lfirst" and ":llast".
+/// ":cdo", ":ldo", ":cfdo" and ":lfdo".
void ex_cc(exarg_T *eap)
{
- qf_info_T *qi = &ql_info;
+ qf_info_T *qi;
- if (eap->cmdidx == CMD_ll
- || eap->cmdidx == CMD_lrewind
- || eap->cmdidx == CMD_lfirst
- || eap->cmdidx == CMD_llast
- || eap->cmdidx == CMD_ldo
- || eap->cmdidx == CMD_lfdo) {
- qi = GET_LOC_LIST(curwin);
- if (qi == NULL) {
- EMSG(_(e_loclist));
- return;
- }
+ if ((qi = qf_cmd_get_stack(eap, true)) == NULL) {
+ return;
}
int errornr;
if (eap->addr_count > 0) {
errornr = (int)eap->line2;
- } else if (eap->cmdidx == CMD_cc || eap->cmdidx == CMD_ll) {
- errornr = 0;
- } else if (eap->cmdidx == CMD_crewind || eap->cmdidx == CMD_lrewind
- || eap->cmdidx == CMD_cfirst || eap->cmdidx == CMD_lfirst) {
- errornr = 1;
} else {
- errornr = 32767;
+ switch (eap->cmdidx) {
+ case CMD_cc:
+ case CMD_ll:
+ errornr = 0;
+ break;
+ case CMD_crewind:
+ case CMD_lrewind:
+ case CMD_cfirst:
+ case CMD_lfirst:
+ errornr = 1;
+ break;
+ default:
+ errornr = 32767;
+ break;
+ }
}
// For cdo and ldo commands, jump to the nth valid error.
@@ -3842,8 +4302,9 @@ void ex_cc(exarg_T *eap)
} else {
n = 1;
}
- size_t valid_entry = qf_get_nth_valid_entry(qi, n,
- eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo);
+ size_t valid_entry = qf_get_nth_valid_entry(
+ qf_get_curlist(qi), n,
+ eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo);
assert(valid_entry <= INT_MAX);
errornr = (int)valid_entry;
}
@@ -3851,28 +4312,15 @@ void ex_cc(exarg_T *eap)
qf_jump(qi, 0, errornr, eap->forceit);
}
-/*
- * ":cnext", ":cnfile", ":cNext" and ":cprevious".
- * ":lnext", ":lNext", ":lprevious", ":lnfile", ":lNfile" and ":lpfile".
- * ":cdo", ":ldo", ":cfdo" and ":lfdo".
- */
+/// ":cnext", ":cnfile", ":cNext" and ":cprevious".
+/// ":lnext", ":lNext", ":lprevious", ":lnfile", ":lNfile" and ":lpfile".
+/// ":cdo", ":ldo", ":cfdo" and ":lfdo".
void ex_cnext(exarg_T *eap)
{
- qf_info_T *qi = &ql_info;
+ qf_info_T *qi;
- if (eap->cmdidx == CMD_lnext
- || eap->cmdidx == CMD_lNext
- || eap->cmdidx == CMD_lprevious
- || eap->cmdidx == CMD_lnfile
- || eap->cmdidx == CMD_lNfile
- || eap->cmdidx == CMD_lpfile
- || eap->cmdidx == CMD_ldo
- || eap->cmdidx == CMD_lfdo) {
- qi = GET_LOC_LIST(curwin);
- if (qi == NULL) {
- EMSG(_(e_loclist));
- return;
- }
+ if ((qi = qf_cmd_get_stack(eap, true)) == NULL) {
+ return;
}
int errornr;
@@ -3884,51 +4332,353 @@ void ex_cnext(exarg_T *eap)
errornr = 1;
}
- qf_jump(qi, (eap->cmdidx == CMD_cnext || eap->cmdidx == CMD_lnext
- || eap->cmdidx == CMD_cdo || eap->cmdidx == CMD_ldo)
- ? FORWARD
- : (eap->cmdidx == CMD_cnfile || eap->cmdidx == CMD_lnfile
- || eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo)
- ? FORWARD_FILE
- : (eap->cmdidx == CMD_cpfile || eap->cmdidx == CMD_lpfile
- || eap->cmdidx == CMD_cNfile || eap->cmdidx == CMD_lNfile)
- ? BACKWARD_FILE
- : BACKWARD,
- errornr, eap->forceit);
+ // Depending on the command jump to either next or previous entry/file.
+ Direction dir;
+ switch (eap->cmdidx) {
+ case CMD_cprevious:
+ case CMD_lprevious:
+ case CMD_cNext:
+ case CMD_lNext:
+ dir = BACKWARD;
+ break;
+ case CMD_cnfile:
+ case CMD_lnfile:
+ case CMD_cfdo:
+ case CMD_lfdo:
+ dir = FORWARD_FILE;
+ break;
+ case CMD_cpfile:
+ case CMD_lpfile:
+ case CMD_cNfile:
+ case CMD_lNfile:
+ dir = BACKWARD_FILE;
+ break;
+ case CMD_cnext:
+ case CMD_lnext:
+ case CMD_cdo:
+ case CMD_ldo:
+ default:
+ dir = FORWARD;
+ break;
+ }
+
+ qf_jump(qi, dir, errornr, eap->forceit);
+}
+
+/// Find the first entry in the quickfix list 'qfl' from buffer 'bnr'.
+/// The index of the entry is stored in 'errornr'.
+/// Returns NULL if an entry is not found.
+static qfline_T *qf_find_first_entry_in_buf(qf_list_T *qfl,
+ int bnr,
+ int *errornr)
+{
+ qfline_T *qfp = NULL;
+ int idx = 0;
+
+ // Find the first entry in this file
+ FOR_ALL_QFL_ITEMS(qfl, qfp, idx) {
+ if (qfp->qf_fnum == bnr) {
+ break;
+ }
+ }
+
+ *errornr = idx;
+ return qfp;
+}
+
+/// Find the first quickfix entry on the same line as 'entry'. Updates 'errornr'
+/// with the error number for the first entry. Assumes the entries are sorted in
+/// the quickfix list by line number.
+static qfline_T * qf_find_first_entry_on_line(qfline_T *entry, int *errornr)
+{
+ while (!got_int
+ && entry->qf_prev != NULL
+ && entry->qf_fnum == entry->qf_prev->qf_fnum
+ && entry->qf_lnum == entry->qf_prev->qf_lnum) {
+ entry = entry->qf_prev;
+ (*errornr)--;
+ }
+
+ return entry;
+}
+
+/// Find the last quickfix entry on the same line as 'entry'. Updates 'errornr'
+/// with the error number for the last entry. Assumes the entries are sorted in
+/// the quickfix list by line number.
+static qfline_T * qf_find_last_entry_on_line(qfline_T *entry, int *errornr)
+{
+ while (!got_int
+ && entry->qf_next != NULL
+ && entry->qf_fnum == entry->qf_next->qf_fnum
+ && entry->qf_lnum == entry->qf_next->qf_lnum) {
+ entry = entry->qf_next;
+ (*errornr)++;
+ }
+
+ return entry;
}
-/*
- * ":cfile"/":cgetfile"/":caddfile" commands.
- * ":lfile"/":lgetfile"/":laddfile" commands.
- */
+/// Find the first quickfix entry below line 'lnum' in buffer 'bnr'.
+/// 'qfp' points to the very first entry in the buffer and 'errornr' is the
+/// index of the very first entry in the quickfix list.
+/// Returns NULL if an entry is not found after 'lnum'.
+static qfline_T *qf_find_entry_on_next_line(int bnr,
+ linenr_T lnum,
+ qfline_T *qfp,
+ int *errornr)
+{
+ if (qfp->qf_lnum > lnum) {
+ // First entry is after line 'lnum'
+ return qfp;
+ }
+
+ // Find the entry just before or at the line 'lnum'
+ while (qfp->qf_next != NULL
+ && qfp->qf_next->qf_fnum == bnr
+ && qfp->qf_next->qf_lnum <= lnum) {
+ qfp = qfp->qf_next;
+ (*errornr)++;
+ }
+
+ if (qfp->qf_next == NULL || qfp->qf_next->qf_fnum != bnr) {
+ // No entries found after 'lnum'
+ return NULL;
+ }
+
+ // Use the entry just after line 'lnum'
+ qfp = qfp->qf_next;
+ (*errornr)++;
+
+ return qfp;
+}
+
+/// Find the first quickfix entry before line 'lnum' in buffer 'bnr'.
+/// 'qfp' points to the very first entry in the buffer and 'errornr' is the
+/// index of the very first entry in the quickfix list.
+/// Returns NULL if an entry is not found before 'lnum'.
+static qfline_T *qf_find_entry_on_prev_line(int bnr,
+ linenr_T lnum,
+ qfline_T *qfp,
+ int *errornr)
+{
+ // Find the entry just before the line 'lnum'
+ while (qfp->qf_next != NULL
+ && qfp->qf_next->qf_fnum == bnr
+ && qfp->qf_next->qf_lnum < lnum) {
+ qfp = qfp->qf_next;
+ (*errornr)++;
+ }
+
+ if (qfp->qf_lnum >= lnum) { // entry is after 'lnum'
+ return NULL;
+ }
+
+ // If multiple entries are on the same line, then use the first entry
+ qfp = qf_find_first_entry_on_line(qfp, errornr);
+
+ return qfp;
+}
+
+/// Find a quickfix entry in 'qfl' closest to line 'lnum' in buffer 'bnr' in
+/// the direction 'dir'.
+static qfline_T *qf_find_closest_entry(qf_list_T *qfl,
+ int bnr,
+ linenr_T lnum,
+ int dir,
+ int *errornr)
+{
+ qfline_T *qfp;
+
+ *errornr = 0;
+
+ // Find the first entry in this file
+ qfp = qf_find_first_entry_in_buf(qfl, bnr, errornr);
+ if (qfp == NULL) {
+ return NULL; // no entry in this file
+ }
+
+ if (dir == FORWARD) {
+ qfp = qf_find_entry_on_next_line(bnr, lnum, qfp, errornr);
+ } else {
+ qfp = qf_find_entry_on_prev_line(bnr, lnum, qfp, errornr);
+ }
+
+ return qfp;
+}
+
+/// Get the nth quickfix entry below the specified entry treating multiple
+/// entries on a single line as one. Searches forward in the list.
+static void qf_get_nth_below_entry(qfline_T *entry,
+ int *errornr,
+ linenr_T n)
+{
+ while (n-- > 0 && !got_int) {
+ qfline_T *first_entry = entry;
+ int first_errornr = *errornr;
+
+ // Treat all the entries on the same line in this file as one
+ entry = qf_find_last_entry_on_line(entry, errornr);
+
+ if (entry->qf_next == NULL
+ || entry->qf_next->qf_fnum != entry->qf_fnum) {
+ // If multiple entries are on the same line, then use the first
+ // entry
+ entry = first_entry;
+ *errornr = first_errornr;
+ break;
+ }
+
+ entry = entry->qf_next;
+ (*errornr)++;
+ }
+}
+
+/// Get the nth quickfix entry above the specified entry treating multiple
+/// entries on a single line as one. Searches backwards in the list.
+static void qf_get_nth_above_entry(qfline_T *entry,
+ int *errornr,
+ linenr_T n)
+{
+ while (n-- > 0 && !got_int) {
+ if (entry->qf_prev == NULL
+ || entry->qf_prev->qf_fnum != entry->qf_fnum) {
+ break;
+ }
+
+ entry = entry->qf_prev;
+ (*errornr)--;
+
+ // If multiple entries are on the same line, then use the first entry
+ entry = qf_find_first_entry_on_line(entry, errornr);
+ }
+}
+
+/// Find the n'th quickfix entry adjacent to line 'lnum' in buffer 'bnr' in the
+/// specified direction.
+/// Returns the error number in the quickfix list or 0 if an entry is not found.
+static int qf_find_nth_adj_entry(qf_list_T *qfl,
+ int bnr,
+ linenr_T lnum,
+ linenr_T n,
+ int dir)
+{
+ qfline_T *adj_entry;
+ int errornr;
+
+ // Find an entry closest to the specified line
+ adj_entry = qf_find_closest_entry(qfl, bnr, lnum, dir, &errornr);
+ if (adj_entry == NULL) {
+ return 0;
+ }
+
+ if (--n > 0) {
+ // Go to the n'th entry in the current buffer
+ if (dir == FORWARD) {
+ qf_get_nth_below_entry(adj_entry, &errornr, n);
+ } else {
+ qf_get_nth_above_entry(adj_entry, &errornr, n);
+ }
+ }
+
+ return errornr;
+}
+
+/// Jump to a quickfix entry in the current file nearest to the current line.
+/// ":cabove", ":cbelow", ":labove" and ":lbelow" commands
+void ex_cbelow(exarg_T *eap)
+{
+ qf_info_T *qi;
+ qf_list_T *qfl;
+ int dir;
+ int buf_has_flag;
+ int errornr = 0;
+
+ if (eap->addr_count > 0 && eap->line2 <= 0) {
+ EMSG(_(e_invrange));
+ return;
+ }
+
+ // Check whether the current buffer has any quickfix entries
+ if (eap->cmdidx == CMD_cabove || eap->cmdidx == CMD_cbelow) {
+ buf_has_flag = BUF_HAS_QF_ENTRY;
+ } else {
+ buf_has_flag = BUF_HAS_LL_ENTRY;
+ }
+ if (!(curbuf->b_has_qf_entry & buf_has_flag)) {
+ EMSG(_(e_quickfix));
+ return;
+ }
+
+ if ((qi = qf_cmd_get_stack(eap, true)) == NULL) {
+ return;
+ }
+
+ qfl = qf_get_curlist(qi);
+ // check if the list has valid errors
+ if (!qf_list_has_valid_entries(qfl)) {
+ EMSG(_(e_quickfix));
+ return;
+ }
+
+ if (eap->cmdidx == CMD_cbelow || eap->cmdidx == CMD_lbelow) {
+ dir = FORWARD;
+ } else {
+ dir = BACKWARD;
+ }
+
+ errornr = qf_find_nth_adj_entry(qfl, curbuf->b_fnum, curwin->w_cursor.lnum,
+ eap->addr_count > 0 ? eap->line2 : 0, dir);
+
+ if (errornr > 0) {
+ qf_jump(qi, 0, errornr, false);
+ } else {
+ EMSG(_(e_no_more_items));
+ }
+}
+
+
+/// Return the autocmd name for the :cfile Ex commands
+static char_u * cfile_get_auname(cmdidx_T cmdidx)
+{
+ switch (cmdidx) {
+ case CMD_cfile: return (char_u *)"cfile";
+ case CMD_cgetfile: return (char_u *)"cgetfile";
+ case CMD_caddfile: return (char_u *)"caddfile";
+ case CMD_lfile: return (char_u *)"lfile";
+ case CMD_lgetfile: return (char_u *)"lgetfile";
+ case CMD_laddfile: return (char_u *)"laddfile";
+ default: return NULL;
+ }
+}
+
+
+// ":cfile"/":cgetfile"/":caddfile" commands.
+// ":lfile"/":lgetfile"/":laddfile" commands.
void ex_cfile(exarg_T *eap)
{
win_T *wp = NULL;
qf_info_T *qi = &ql_info;
char_u *au_name = NULL;
- switch (eap->cmdidx) {
- case CMD_cfile: au_name = (char_u *)"cfile"; break;
- case CMD_cgetfile: au_name = (char_u *)"cgetfile"; break;
- case CMD_caddfile: au_name = (char_u *)"caddfile"; break;
- case CMD_lfile: au_name = (char_u *)"lfile"; break;
- case CMD_lgetfile: au_name = (char_u *)"lgetfile"; break;
- case CMD_laddfile: au_name = (char_u *)"laddfile"; break;
- default: break;
+ au_name = cfile_get_auname(eap->cmdidx);
+ if (au_name != NULL
+ && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, NULL, false, curbuf)) {
+ if (aborting()) {
+ return;
+ }
}
- if (au_name != NULL)
- apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, NULL, FALSE, curbuf);
- if (*eap->arg != NUL)
+ if (*eap->arg != NUL) {
set_string_option_direct((char_u *)"ef", -1, eap->arg, OPT_FREE, 0);
+ }
char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc;
- if (eap->cmdidx == CMD_lfile
- || eap->cmdidx == CMD_lgetfile
- || eap->cmdidx == CMD_laddfile) {
+ if (is_loclist_cmd(eap->cmdidx)) {
wp = curwin;
}
+ incr_quickfix_busy();
+
// This function is used by the :cfile, :cgetfile and :caddfile
// commands.
// :cfile always creates a new quickfix list and jumps to the
@@ -3943,13 +4693,14 @@ void ex_cfile(exarg_T *eap)
if (wp != NULL) {
qi = GET_LOC_LIST(wp);
if (qi == NULL) {
+ decr_quickfix_busy();
return;
}
}
if (res >= 0) {
- qf_list_changed(qi, qi->qf_curlist);
+ qf_list_changed(qf_get_curlist(qi));
}
- unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id;
+ unsigned save_qfid = qf_get_curlist(qi)->qf_id;
if (au_name != NULL) {
apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, NULL, false, curbuf);
}
@@ -3960,6 +4711,8 @@ void ex_cfile(exarg_T *eap)
// display the first error
qf_jump_first(qi, save_qfid, eap->forceit);
}
+
+ decr_quickfix_busy();
}
/// Return the vimgrep autocmd name.
@@ -4080,8 +4833,7 @@ static bool vgr_match_buflines(qf_info_T *qi, char_u *fname, buf_T *buf,
// Pass the buffer number so that it gets used even for a
// dummy buffer, unless duplicate_name is set, then the
// buffer will be wiped out below.
- if (qf_add_entry(qi,
- qi->qf_curlist,
+ if (qf_add_entry(qf_get_curlist(qi),
NULL, // dir
fname,
NULL,
@@ -4094,8 +4846,8 @@ static bool vgr_match_buflines(qf_info_T *qi, char_u *fname, buf_T *buf,
NULL, // search pattern
0, // nr
0, // type
- true // valid
- ) == FAIL) {
+ true) // valid
+ == QF_FAIL) {
got_int = true;
break;
}
@@ -4142,12 +4894,10 @@ static void vgr_jump_to_match(qf_info_T *qi, int forceit, int *redraw_for_dummy,
}
}
-/*
- * ":vimgrep {pattern} file(s)"
- * ":vimgrepadd {pattern} file(s)"
- * ":lvimgrep {pattern} file(s)"
- * ":lvimgrepadd {pattern} file(s)"
- */
+// ":vimgrep {pattern} file(s)"
+// ":vimgrepadd {pattern} file(s)"
+// ":lvimgrep {pattern} file(s)"
+// ":lvimgrepadd {pattern} file(s)"
void ex_vimgrep(exarg_T *eap)
{
regmmatch_T regmatch;
@@ -4157,7 +4907,7 @@ void ex_vimgrep(exarg_T *eap)
char_u *s;
char_u *p;
int fi;
- qf_info_T *qi = &ql_info;
+ qf_list_T *qfl;
win_T *wp = NULL;
buf_T *buf;
int duplicate_name = FALSE;
@@ -4182,20 +4932,14 @@ void ex_vimgrep(exarg_T *eap)
}
}
- if (eap->cmdidx == CMD_lgrep
- || eap->cmdidx == CMD_lvimgrep
- || eap->cmdidx == CMD_lgrepadd
- || eap->cmdidx == CMD_lvimgrepadd) {
- qi = ll_get_or_alloc_list(curwin);
- wp = curwin;
- }
+ qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp);
if (eap->addr_count > 0)
tomatch = eap->line2;
else
tomatch = MAXLNUM;
- /* Get the search pattern: either white-separated or enclosed in // */
+ // Get the search pattern: either white-separated or enclosed in //
regmatch.regprog = NULL;
char_u *title = vim_strsave(qf_cmdtitle(*eap->cmdlinep));
p = skip_vimgrep_pat(eap->arg, &s, &flags);
@@ -4217,14 +4961,15 @@ void ex_vimgrep(exarg_T *eap)
if ((eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd
&& eap->cmdidx != CMD_vimgrepadd && eap->cmdidx != CMD_lvimgrepadd)
- || qi->qf_curlist == qi->qf_listcount) {
+ || qf_stack_empty(qi)) {
// make place for a new list
qf_new_list(qi, title);
}
- /* parse the list of arguments */
- if (get_arglist_exp(p, &fcount, &fnames, true) == FAIL)
+ // parse the list of arguments
+ if (get_arglist_exp(p, &fcount, &fnames, true) == FAIL) {
goto theend;
+ }
if (fcount == 0) {
EMSG(_(e_nomatch));
goto theend;
@@ -4233,27 +4978,29 @@ void ex_vimgrep(exarg_T *eap)
dirname_start = xmalloc(MAXPATHL);
dirname_now = xmalloc(MAXPATHL);
- /* Remember the current directory, because a BufRead autocommand that does
- * ":lcd %:p:h" changes the meaning of short path names. */
+ // Remember the current directory, because a BufRead autocommand that does
+ // ":lcd %:p:h" changes the meaning of short path names.
os_dirname(dirname_start, MAXPATHL);
+ incr_quickfix_busy();
+
// Remember the current quickfix list identifier, so that we can check for
// autocommands changing the current quickfix list.
- unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id;
+ unsigned save_qfid = qf_get_curlist(qi)->qf_id;
seconds = (time_t)0;
for (fi = 0; fi < fcount && !got_int && tomatch > 0; fi++) {
fname = path_try_shorten_fname(fnames[fi]);
if (time(NULL) > seconds) {
- /* Display the file name every second or so, show the user we are
- * working on it. */
+ // Display the file name every second or so, show the user we are
+ // working on it.
seconds = time(NULL);
vgr_display_fname(fname);
}
buf = buflist_findname_exp(fnames[fi]);
if (buf == NULL || buf->b_ml.ml_mfp == NULL) {
- /* Remember that a buffer with this name already exists. */
+ // Remember that a buffer with this name already exists.
duplicate_name = (buf != NULL);
using_dummy = TRUE;
redraw_for_dummy = TRUE;
@@ -4268,9 +5015,10 @@ void ex_vimgrep(exarg_T *eap)
// buffer above, autocommands might have changed the quickfix list.
if (!vgr_qflist_valid(wp, qi, save_qfid, *eap->cmdlinep)) {
FreeWild(fcount, fnames);
+ decr_quickfix_busy();
goto theend;
}
- save_qfid = qi->qf_lists[qi->qf_curlist].qf_id;
+ save_qfid = qf_get_curlist(qi)->qf_id;
if (buf == NULL) {
if (!got_int)
@@ -4285,20 +5033,20 @@ void ex_vimgrep(exarg_T *eap)
if (found_match && first_match_buf == NULL)
first_match_buf = buf;
if (duplicate_name) {
- /* Never keep a dummy buffer if there is another buffer
- * with the same name. */
+ // Never keep a dummy buffer if there is another buffer
+ // with the same name.
wipe_dummy_buffer(buf, dirname_start);
buf = NULL;
} else if (!cmdmod.hide
- || buf->b_p_bh[0] == 'u' /* "unload" */
- || buf->b_p_bh[0] == 'w' /* "wipe" */
- || buf->b_p_bh[0] == 'd') { /* "delete" */
- /* When no match was found we don't need to remember the
- * buffer, wipe it out. If there was a match and it
- * wasn't the first one or we won't jump there: only
- * unload the buffer.
- * Ignore 'hidden' here, because it may lead to having too
- * many swap files. */
+ || buf->b_p_bh[0] == 'u' // "unload"
+ || buf->b_p_bh[0] == 'w' // "wipe"
+ || buf->b_p_bh[0] == 'd') { // "delete"
+ // When no match was found we don't need to remember the
+ // buffer, wipe it out. If there was a match and it
+ // wasn't the first one or we won't jump there: only
+ // unload the buffer.
+ // Ignore 'hidden' here, because it may lead to having too
+ // many swap files.
if (!found_match) {
wipe_dummy_buffer(buf, dirname_start);
buf = NULL;
@@ -4322,10 +5070,10 @@ void ex_vimgrep(exarg_T *eap)
target_dir = vim_strsave(dirname_now);
}
- /* The buffer is still loaded, the Filetype autocommands
- * need to be done now, in that buffer. And the modelines
- * need to be done (again). But not the window-local
- * options! */
+ // The buffer is still loaded, the Filetype autocommands
+ // need to be done now, in that buffer. And the modelines
+ // need to be done (again). But not the window-local
+ // options!
aucmd_prepbuf(&aco, buf);
apply_autocmds(EVENT_FILETYPE, buf->b_p_ft,
buf->b_fname, TRUE, buf);
@@ -4338,10 +5086,11 @@ void ex_vimgrep(exarg_T *eap)
FreeWild(fcount, fnames);
- qi->qf_lists[qi->qf_curlist].qf_nonevalid = FALSE;
- qi->qf_lists[qi->qf_curlist].qf_ptr = qi->qf_lists[qi->qf_curlist].qf_start;
- qi->qf_lists[qi->qf_curlist].qf_index = 1;
- qf_list_changed(qi, qi->qf_curlist);
+ qfl = qf_get_curlist(qi);
+ qfl->qf_nonevalid = false;
+ qfl->qf_ptr = qfl->qf_start;
+ qfl->qf_index = 1;
+ qf_list_changed(qfl);
qf_update_buffer(qi, NULL);
@@ -4351,16 +5100,14 @@ void ex_vimgrep(exarg_T *eap)
// The QuickFixCmdPost autocmd may free the quickfix list. Check the list
// is still valid.
- if (!qflist_valid(wp, save_qfid)) {
- goto theend;
- }
-
- if (qf_restore_list(qi, save_qfid) == FAIL) {
+ if (!qflist_valid(wp, save_qfid)
+ || qf_restore_list(qi, save_qfid) == FAIL) {
+ decr_quickfix_busy();
goto theend;
}
- /* Jump to first match. */
- if (qi->qf_lists[qi->qf_curlist].qf_count > 0) {
+ // Jump to first match.
+ if (!qf_list_empty(qf_get_curlist(qi))) {
if ((flags & VGR_NOJUMP) == 0) {
vgr_jump_to_match(qi, eap->forceit, &redraw_for_dummy, first_match_buf,
target_dir);
@@ -4368,8 +5115,10 @@ void ex_vimgrep(exarg_T *eap)
} else
EMSG2(_(e_nomatch2), s);
- /* If we loaded a dummy buffer into the current window, the autocommands
- * may have messed up things, need to redraw and recompute folds. */
+ decr_quickfix_busy();
+
+ // If we loaded a dummy buffer into the current window, the autocommands
+ // may have messed up things, need to redraw and recompute folds.
if (redraw_for_dummy) {
foldUpdateAll(curwin);
}
@@ -4382,18 +5131,16 @@ theend:
vim_regfree(regmatch.regprog);
}
-/*
- * Restore current working directory to "dirname_start" if they differ, taking
- * into account whether it is set locally or globally.
- */
+// Restore current working directory to "dirname_start" if they differ, taking
+// into account whether it is set locally or globally.
static void restore_start_dir(char_u *dirname_start)
{
char_u *dirname_now = xmalloc(MAXPATHL);
os_dirname(dirname_now, MAXPATHL);
if (STRCMP(dirname_start, dirname_now) != 0) {
- /* If the directory has changed, change it back by building up an
- * appropriate ex command and executing it. */
+ // If the directory has changed, change it back by building up an
+ // appropriate ex command and executing it.
exarg_T ea = {
.arg = dirname_start,
.cmdidx = (curwin->w_localdir == NULL) ? CMD_cd : CMD_lcd,
@@ -4403,23 +5150,21 @@ static void restore_start_dir(char_u *dirname_start)
xfree(dirname_now);
}
-/*
- * Load file "fname" into a dummy buffer and return the buffer pointer,
- * placing the directory resulting from the buffer load into the
- * "resulting_dir" pointer. "resulting_dir" must be allocated by the caller
- * prior to calling this function. Restores directory to "dirname_start" prior
- * to returning, if autocmds or the 'autochdir' option have changed it.
- *
- * If creating the dummy buffer does not fail, must call unload_dummy_buffer()
- * or wipe_dummy_buffer() later!
- *
- * Returns NULL if it fails.
- */
+// Load file "fname" into a dummy buffer and return the buffer pointer,
+// placing the directory resulting from the buffer load into the
+// "resulting_dir" pointer. "resulting_dir" must be allocated by the caller
+// prior to calling this function. Restores directory to "dirname_start" prior
+// to returning, if autocmds or the 'autochdir' option have changed it.
+//
+// If creating the dummy buffer does not fail, must call unload_dummy_buffer()
+// or wipe_dummy_buffer() later!
+//
+// Returns NULL if it fails.
static buf_T *
load_dummy_buffer (
char_u *fname,
- char_u *dirname_start, /* in: old directory */
- char_u *resulting_dir /* out: new directory */
+ char_u *dirname_start, // in: old directory
+ char_u *resulting_dir // out: new directory
)
{
buf_T *newbuf;
@@ -4436,24 +5181,24 @@ load_dummy_buffer (
}
set_bufref(&newbufref, newbuf);
- /* Init the options. */
+ // Init the options.
buf_copy_options(newbuf, BCO_ENTER | BCO_NOHELP);
- /* need to open the memfile before putting the buffer in a window */
+ // need to open the memfile before putting the buffer in a window
if (ml_open(newbuf) == OK) {
// Make sure this buffer isn't wiped out by autocommands.
newbuf->b_locked++;
// set curwin/curbuf to buf and save a few things
aucmd_prepbuf(&aco, newbuf);
- /* Need to set the filename for autocommands. */
- (void)setfname(curbuf, fname, NULL, FALSE);
+ // Need to set the filename for autocommands.
+ (void)setfname(curbuf, fname, NULL, false);
- /* Create swap file now to avoid the ATTENTION message. */
- check_need_swap(TRUE);
+ // Create swap file now to avoid the ATTENTION message.
+ check_need_swap(true);
- /* Remove the "dummy" flag, otherwise autocommands may not
- * work. */
+ // Remove the "dummy" flag, otherwise autocommands may not
+ // work.
curbuf->b_flags &= ~BF_DUMMY;
newbuf_to_wipe.br_buf = NULL;
@@ -4486,11 +5231,9 @@ load_dummy_buffer (
newbuf->b_flags |= BF_DUMMY;
}
- /*
- * When autocommands/'autochdir' option changed directory: go back.
- * Let the caller know what the resulting dir was first, in case it is
- * important.
- */
+ // When autocommands/'autochdir' option changed directory: go back.
+ // Let the caller know what the resulting dir was first, in case it is
+ // important.
os_dirname(resulting_dir, MAXPATHL);
restore_start_dir(dirname_start);
@@ -4504,55 +5247,98 @@ load_dummy_buffer (
return newbuf;
}
-/*
- * Wipe out the dummy buffer that load_dummy_buffer() created. Restores
- * directory to "dirname_start" prior to returning, if autocmds or the
- * 'autochdir' option have changed it.
- */
+// Wipe out the dummy buffer that load_dummy_buffer() created. Restores
+// directory to "dirname_start" prior to returning, if autocmds or the
+// 'autochdir' option have changed it.
static void wipe_dummy_buffer(buf_T *buf, char_u *dirname_start)
{
- if (curbuf != buf) { /* safety check */
+ if (curbuf != buf) { // safety check
cleanup_T cs;
- /* Reset the error/interrupt/exception state here so that aborting()
- * returns FALSE when wiping out the buffer. Otherwise it doesn't
- * work when got_int is set. */
+ // Reset the error/interrupt/exception state here so that aborting()
+ // returns FALSE when wiping out the buffer. Otherwise it doesn't
+ // work when got_int is set.
enter_cleanup(&cs);
wipe_buffer(buf, FALSE);
- /* Restore the error/interrupt/exception state if not discarded by a
- * new aborting error, interrupt, or uncaught exception. */
+ // Restore the error/interrupt/exception state if not discarded by a
+ // new aborting error, interrupt, or uncaught exception.
leave_cleanup(&cs);
- /* When autocommands/'autochdir' option changed directory: go back. */
+ // When autocommands/'autochdir' option changed directory: go back.
restore_start_dir(dirname_start);
}
}
-/*
- * Unload the dummy buffer that load_dummy_buffer() created. Restores
- * directory to "dirname_start" prior to returning, if autocmds or the
- * 'autochdir' option have changed it.
- */
+// Unload the dummy buffer that load_dummy_buffer() created. Restores
+// directory to "dirname_start" prior to returning, if autocmds or the
+// 'autochdir' option have changed it.
static void unload_dummy_buffer(buf_T *buf, char_u *dirname_start)
{
- if (curbuf != buf) { /* safety check */
- close_buffer(NULL, buf, DOBUF_UNLOAD, FALSE);
+ if (curbuf != buf) { // safety check
+ close_buffer(NULL, buf, DOBUF_UNLOAD, false);
- /* When autocommands/'autochdir' option changed directory: go back. */
+ // When autocommands/'autochdir' option changed directory: go back.
restore_start_dir(dirname_start);
}
}
+/// Copy the specified quickfix entry items into a new dict and appened the dict
+/// to 'list'. Returns OK on success.
+static int get_qfline_items(qfline_T *qfp, list_T *list)
+{
+ char_u buf[2];
+ int bufnum;
+
+ // Handle entries with a non-existing buffer number.
+ bufnum = qfp->qf_fnum;
+ if (bufnum != 0 && (buflist_findnr(bufnum) == NULL)) {
+ bufnum = 0;
+ }
+
+ dict_T *const dict = tv_dict_alloc();
+ tv_list_append_dict(list, dict);
+
+ buf[0] = qfp->qf_type;
+ buf[1] = NUL;
+ if (tv_dict_add_nr(dict, S_LEN("bufnr"), (varnumber_T)bufnum) == FAIL
+ || (tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)qfp->qf_lnum)
+ == FAIL)
+ || (tv_dict_add_nr(dict, S_LEN("col"), (varnumber_T)qfp->qf_col) == FAIL)
+ || (tv_dict_add_nr(dict, S_LEN("vcol"), (varnumber_T)qfp->qf_viscol)
+ == FAIL)
+ || (tv_dict_add_nr(dict, S_LEN("nr"), (varnumber_T)qfp->qf_nr) == FAIL)
+ || (tv_dict_add_str(
+ dict, S_LEN("module"),
+ (qfp->qf_module == NULL ? "" : (const char *)qfp->qf_module))
+ == FAIL)
+ || (tv_dict_add_str(
+ dict, S_LEN("pattern"),
+ (qfp->qf_pattern == NULL ? "" : (const char *)qfp->qf_pattern))
+ == FAIL)
+ || (tv_dict_add_str(
+ dict, S_LEN("text"),
+ (qfp->qf_text == NULL ? "" : (const char *)qfp->qf_text))
+ == FAIL)
+ || (tv_dict_add_str(dict, S_LEN("type"), (const char *)buf) == FAIL)
+ || (tv_dict_add_nr(dict, S_LEN("valid"), (varnumber_T)qfp->qf_valid)
+ == FAIL)) {
+ // tv_dict_add* fail only if key already exist, but this is a newly
+ // allocated dictionary which is thus guaranteed to have no existing keys.
+ assert(false);
+ }
+
+ return OK;
+}
+
/// Add each quickfix error to list "list" as a dictionary.
/// If qf_idx is -1, use the current list. Otherwise, use the specified list.
-int get_errorlist(const qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list)
+int get_errorlist(qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list)
{
- const qf_info_T *qi = qi_arg;
- char_u buf[2];
+ qf_info_T *qi = qi_arg;
+ qf_list_T *qfl;
qfline_T *qfp;
int i;
- int bufnum;
if (qi == NULL) {
qi = &ql_info;
@@ -4568,56 +5354,19 @@ int get_errorlist(const qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list)
qf_idx = qi->qf_curlist;
}
- if (qf_idx >= qi->qf_listcount
- || qi->qf_lists[qf_idx].qf_count == 0) {
+ if (qf_idx >= qi->qf_listcount) {
return FAIL;
}
- qfp = qi->qf_lists[qf_idx].qf_start;
- for (i = 1; !got_int && i <= qi->qf_lists[qf_idx].qf_count; i++) {
- // Handle entries with a non-existing buffer number.
- bufnum = qfp->qf_fnum;
- if (bufnum != 0 && (buflist_findnr(bufnum) == NULL))
- bufnum = 0;
-
- dict_T *const dict = tv_dict_alloc();
- tv_list_append_dict(list, dict);
-
- buf[0] = qfp->qf_type;
- buf[1] = NUL;
- if (tv_dict_add_nr(dict, S_LEN("bufnr"), (varnumber_T)bufnum) == FAIL
- || (tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)qfp->qf_lnum)
- == FAIL)
- || (tv_dict_add_nr(dict, S_LEN("col"), (varnumber_T)qfp->qf_col)
- == FAIL)
- || (tv_dict_add_nr(dict, S_LEN("vcol"), (varnumber_T)qfp->qf_viscol)
- == FAIL)
- || (tv_dict_add_nr(dict, S_LEN("nr"), (varnumber_T)qfp->qf_nr) == FAIL)
- || tv_dict_add_str(dict, S_LEN("module"),
- (qfp->qf_module == NULL
- ? ""
- : (const char *)qfp->qf_module)) == FAIL
- || tv_dict_add_str(dict, S_LEN("pattern"),
- (qfp->qf_pattern == NULL
- ? ""
- : (const char *)qfp->qf_pattern)) == FAIL
- || tv_dict_add_str(dict, S_LEN("text"),
- (qfp->qf_text == NULL
- ? ""
- : (const char *)qfp->qf_text)) == FAIL
- || tv_dict_add_str(dict, S_LEN("type"), (const char *)buf) == FAIL
- || (tv_dict_add_nr(dict, S_LEN("valid"), (varnumber_T)qfp->qf_valid)
- == FAIL)) {
- // tv_dict_add* fail only if key already exist, but this is a newly
- // allocated dictionary which is thus guaranteed to have no existing keys.
- assert(false);
- }
+ qfl = qf_get_list(qi, qf_idx);
+ if (qf_list_empty(qfl)) {
+ return FAIL;
+ }
- qfp = qfp->qf_next;
- if (qfp == NULL) {
- break;
- }
+ FOR_ALL_QFL_ITEMS(qfl, qfp, i) {
+ get_qfline_items(qfp, list);
}
+
return OK;
}
@@ -4633,7 +5382,8 @@ enum {
QF_GETLIST_IDX = 0x40,
QF_GETLIST_SIZE = 0x80,
QF_GETLIST_TICK = 0x100,
- QF_GETLIST_ALL = 0x1FF
+ QF_GETLIST_FILEWINID = 0x200,
+ QF_GETLIST_ALL = 0x3FF,
};
/// Parse text from 'di' and return the quickfix list items.
@@ -4657,12 +5407,12 @@ static int qf_get_list_from_lines(dict_T *what, dictitem_T *di, dict_T *retdict)
}
list_T *l = tv_list_alloc(kListLenMayKnow);
- qf_info_T *const qi = ll_new_list();
+ qf_info_T *const qi = qf_alloc_stack(QFLT_INTERNAL);
if (qf_init_ext(qi, 0, NULL, NULL, &di->di_tv, errorformat,
true, (linenr_T)0, (linenr_T)0, NULL, NULL) > 0) {
(void)get_errorlist(qi, NULL, 0, l);
- qf_free(qi, 0);
+ qf_free(&qi->qf_lists[0]);
}
xfree(qi);
@@ -4689,12 +5439,17 @@ static int qf_winid(qf_info_T *qi)
}
/// Convert the keys in 'what' to quickfix list property flags.
-static int qf_getprop_keys2flags(dict_T *what)
+static int qf_getprop_keys2flags(const dict_T *what, bool loclist)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
int flags = QF_GETLIST_NONE;
if (tv_dict_find(what, S_LEN("all")) != NULL) {
flags |= QF_GETLIST_ALL;
+ if (!loclist) {
+ // File window ID is applicable only to location list windows
+ flags &= ~QF_GETLIST_FILEWINID;
+ }
}
if (tv_dict_find(what, S_LEN("title")) != NULL) {
flags |= QF_GETLIST_TITLE;
@@ -4723,6 +5478,9 @@ static int qf_getprop_keys2flags(dict_T *what)
if (tv_dict_find(what, S_LEN("changedtick")) != NULL) {
flags |= QF_GETLIST_TICK;
}
+ if (loclist && tv_dict_find(what, S_LEN("filewinid")) != NULL) {
+ flags |= QF_GETLIST_FILEWINID;
+ }
return flags;
}
@@ -4776,7 +5534,10 @@ static int qf_getprop_qfidx(qf_info_T *qi, dict_T *what)
}
/// Return default values for quickfix list properties in retdict.
-static int qf_getprop_defaults(qf_info_T *qi, int flags, dict_T *retdict)
+static int qf_getprop_defaults(qf_info_T *qi,
+ int flags,
+ int locstack,
+ dict_T *retdict)
{
int status = OK;
@@ -4808,15 +5569,37 @@ static int qf_getprop_defaults(qf_info_T *qi, int flags, dict_T *retdict)
if ((status == OK) && (flags & QF_GETLIST_TICK)) {
status = tv_dict_add_nr(retdict, S_LEN("changedtick"), 0);
}
+ if ((status == OK) && locstack && (flags & QF_GETLIST_FILEWINID)) {
+ status = tv_dict_add_nr(retdict, S_LEN("filewinid"), 0);
+ }
return status;
}
/// Return the quickfix list title as 'title' in retdict
-static int qf_getprop_title(qf_info_T *qi, int qf_idx, dict_T *retdict)
+static int qf_getprop_title(qf_list_T *qfl, dict_T *retdict)
{
return tv_dict_add_str(retdict, S_LEN("title"),
- (const char *)qi->qf_lists[qf_idx].qf_title);
+ (const char *)qfl->qf_title);
+}
+
+// Returns the identifier of the window used to display files from a location
+// list. If there is no associated window, then returns 0. Useful only when
+// called from a location list window.
+static int qf_getprop_filewinid(const win_T *wp, const qf_info_T *qi,
+ dict_T *retdict)
+ FUNC_ATTR_NONNULL_ARG(3)
+{
+ handle_T winid = 0;
+
+ if (wp != NULL && IS_LL_WINDOW(wp)) {
+ win_T *ll_wp = qf_find_win_with_loclist(qi);
+ if (ll_wp != NULL) {
+ winid = ll_wp->handle;
+ }
+ }
+
+ return tv_dict_add_nr(retdict, S_LEN("filewinid"), winid);
}
/// Return the quickfix list items/entries as 'items' in retdict
@@ -4830,13 +5613,13 @@ static int qf_getprop_items(qf_info_T *qi, int qf_idx, dict_T *retdict)
}
/// Return the quickfix list context (if any) as 'context' in retdict.
-static int qf_getprop_ctx(qf_info_T *qi, int qf_idx, dict_T *retdict)
+static int qf_getprop_ctx(qf_list_T *qfl, dict_T *retdict)
{
int status;
- if (qi->qf_lists[qf_idx].qf_ctx != NULL) {
+ if (qfl->qf_ctx != NULL) {
dictitem_T *di = tv_dict_item_alloc_len(S_LEN("context"));
- tv_copy(qi->qf_lists[qf_idx].qf_ctx, &di->di_tv);
+ tv_copy(qfl->qf_ctx, &di->di_tv);
status = tv_dict_add(retdict, di);
if (status == FAIL) {
tv_dict_item_free(di);
@@ -4848,15 +5631,15 @@ static int qf_getprop_ctx(qf_info_T *qi, int qf_idx, dict_T *retdict)
return status;
}
-/// Return the quickfix list index as 'idx' in retdict
-static int qf_getprop_idx(qf_info_T *qi, int qf_idx, dict_T *retdict)
+/// Return the current quickfix list index as 'idx' in retdict
+static int qf_getprop_idx(qf_list_T *qfl, dict_T *retdict)
{
- int idx = qi->qf_lists[qf_idx].qf_index;
- if (qi->qf_lists[qf_idx].qf_count == 0) {
- // For empty lists, qf_index is set to 1
- idx = 0;
+ int curidx = qfl->qf_index;
+ if (qf_list_empty(qfl)) {
+ // For empty lists, current index is set to 0
+ curidx = 0;
}
- return tv_dict_add_nr(retdict, S_LEN("idx"), idx);
+ return tv_dict_add_nr(retdict, S_LEN("idx"), curidx);
}
/// Return quickfix/location list details (title) as a dictionary.
@@ -4865,9 +5648,10 @@ static int qf_getprop_idx(qf_info_T *qi, int qf_idx, dict_T *retdict)
int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
{
qf_info_T *qi = &ql_info;
+ qf_list_T *qfl;
dictitem_T *di = NULL;
int status = OK;
- int qf_idx;
+ int qf_idx = INVALID_QFIDX;
if ((di = tv_dict_find(what, S_LEN("lines"))) != NULL) {
return qf_get_list_from_lines(what, di, retdict);
@@ -4877,19 +5661,21 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
qi = GET_LOC_LIST(wp);
}
- int flags = qf_getprop_keys2flags(what);
+ const int flags = qf_getprop_keys2flags(what, wp != NULL);
- if (qi != NULL && qi->qf_listcount != 0) {
+ if (!qf_stack_empty(qi)) {
qf_idx = qf_getprop_qfidx(qi, what);
}
// List is not present or is empty
- if (qi == NULL || qi->qf_listcount == 0 || qf_idx == INVALID_QFIDX) {
- return qf_getprop_defaults(qi, flags, retdict);
+ if (qf_stack_empty(qi) || qf_idx == INVALID_QFIDX) {
+ return qf_getprop_defaults(qi, flags, wp != NULL, retdict);
}
+ qfl = qf_get_list(qi, qf_idx);
+
if (flags & QF_GETLIST_TITLE) {
- status = qf_getprop_title(qi, qf_idx, retdict);
+ status = qf_getprop_title(qfl, retdict);
}
if ((status == OK) && (flags & QF_GETLIST_NR)) {
status = tv_dict_add_nr(retdict, S_LEN("nr"), qf_idx + 1);
@@ -4901,21 +5687,101 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
status = qf_getprop_items(qi, qf_idx, retdict);
}
if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) {
- status = qf_getprop_ctx(qi, qf_idx, retdict);
+ status = qf_getprop_ctx(qfl, retdict);
}
if ((status == OK) && (flags & QF_GETLIST_ID)) {
- status = tv_dict_add_nr(retdict, S_LEN("id"), qi->qf_lists[qf_idx].qf_id);
+ status = tv_dict_add_nr(retdict, S_LEN("id"), qfl->qf_id);
}
if ((status == OK) && (flags & QF_GETLIST_IDX)) {
- status = qf_getprop_idx(qi, qf_idx, retdict);
+ status = qf_getprop_idx(qfl, retdict);
}
if ((status == OK) && (flags & QF_GETLIST_SIZE)) {
status = tv_dict_add_nr(retdict, S_LEN("size"),
- qi->qf_lists[qf_idx].qf_count);
+ qfl->qf_count);
}
if ((status == OK) && (flags & QF_GETLIST_TICK)) {
status = tv_dict_add_nr(retdict, S_LEN("changedtick"),
- qi->qf_lists[qf_idx].qf_changedtick);
+ qfl->qf_changedtick);
+ }
+ if ((status == OK) && (wp != NULL) && (flags & QF_GETLIST_FILEWINID)) {
+ status = qf_getprop_filewinid(wp, qi, retdict);
+ }
+
+ return status;
+}
+
+/// Add a new quickfix entry to list at 'qf_idx' in the stack 'qi' from the
+/// items in the dict 'd'. If it is a valid error entry, then set 'valid_entry'
+/// to true.
+static int qf_add_entry_from_dict(
+ qf_list_T *qfl,
+ const dict_T *d,
+ bool first_entry,
+ bool *valid_entry)
+ FUNC_ATTR_NONNULL_ALL
+{
+ static bool did_bufnr_emsg;
+
+ if (first_entry) {
+ did_bufnr_emsg = false;
+ }
+
+ char *const filename = tv_dict_get_string(d, "filename", true);
+ char *const module = tv_dict_get_string(d, "module", true);
+ int bufnum = (int)tv_dict_get_number(d, "bufnr");
+ const long lnum = (long)tv_dict_get_number(d, "lnum");
+ const int col = (int)tv_dict_get_number(d, "col");
+ const char_u vcol = (char_u)tv_dict_get_number(d, "vcol");
+ const int nr = (int)tv_dict_get_number(d, "nr");
+ const char *const type = tv_dict_get_string(d, "type", false);
+ char *const pattern = tv_dict_get_string(d, "pattern", true);
+ char *text = tv_dict_get_string(d, "text", true);
+ if (text == NULL) {
+ text = xcalloc(1, 1);
+ }
+ bool valid = true;
+ if ((filename == NULL && bufnum == 0)
+ || (lnum == 0 && pattern == NULL)) {
+ valid = false;
+ }
+
+ // Mark entries with non-existing buffer number as not valid. Give the
+ // error message only once.
+ if (bufnum != 0 && (buflist_findnr(bufnum) == NULL)) {
+ if (!did_bufnr_emsg) {
+ did_bufnr_emsg = true;
+ EMSGN(_("E92: Buffer %" PRId64 " not found"), bufnum);
+ }
+ valid = false;
+ bufnum = 0;
+ }
+
+ // If the 'valid' field is present it overrules the detected value.
+ if (tv_dict_find(d, "valid", -1) != NULL) {
+ valid = tv_dict_get_number(d, "valid");
+ }
+
+ const int status = qf_add_entry(qfl,
+ NULL, // dir
+ (char_u *)filename,
+ (char_u *)module,
+ bufnum,
+ (char_u *)text,
+ lnum,
+ col,
+ vcol, // vis_col
+ (char_u *)pattern, // search pattern
+ nr,
+ (char_u)(type == NULL ? NUL : *type),
+ valid);
+
+ xfree(filename);
+ xfree(module);
+ xfree(pattern);
+ xfree(text);
+
+ if (valid) {
+ *valid_entry = true;
}
return status;
@@ -4926,21 +5792,22 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list,
char_u *title, int action)
{
- dict_T *d;
+ qf_list_T *qfl = qf_get_list(qi, qf_idx);
qfline_T *old_last = NULL;
int retval = OK;
- bool did_bufnr_emsg = false;
+ bool valid_entry = false;
if (action == ' ' || qf_idx == qi->qf_listcount) {
// make place for a new list
qf_new_list(qi, title);
qf_idx = qi->qf_curlist;
- } else if (action == 'a' && qi->qf_lists[qf_idx].qf_count > 0) {
+ qfl = qf_get_list(qi, qf_idx);
+ } else if (action == 'a' && !qf_list_empty(qfl)) {
// Adding to existing list, use last entry.
- old_last = qi->qf_lists[qf_idx].qf_last;
+ old_last = qfl->qf_last;
} else if (action == 'r') {
- qf_free_items(qi, qf_idx);
- qf_store_title(qi, qf_idx, title);
+ qf_free_items(qfl);
+ qf_store_title(qfl, title);
}
TV_LIST_ITER_CONST(list, li, {
@@ -4948,83 +5815,35 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list,
continue; // Skip non-dict items.
}
- d = TV_LIST_ITEM_TV(li)->vval.v_dict;
+ const dict_T *const d = TV_LIST_ITEM_TV(li)->vval.v_dict;
if (d == NULL) {
continue;
}
- char *const filename = tv_dict_get_string(d, "filename", true);
- char *const module = tv_dict_get_string(d, "module", true);
- int bufnum = (int)tv_dict_get_number(d, "bufnr");
- long lnum = (long)tv_dict_get_number(d, "lnum");
- int col = (int)tv_dict_get_number(d, "col");
- char_u vcol = (char_u)tv_dict_get_number(d, "vcol");
- int nr = (int)tv_dict_get_number(d, "nr");
- const char *type_str = tv_dict_get_string(d, "type", false);
- const char_u type = (char_u)(uint8_t)(type_str == NULL ? NUL : *type_str);
- char *const pattern = tv_dict_get_string(d, "pattern", true);
- char *text = tv_dict_get_string(d, "text", true);
- if (text == NULL) {
- text = xcalloc(1, 1);
- }
- bool valid = true;
- if ((filename == NULL && bufnum == 0) || (lnum == 0 && pattern == NULL)) {
- valid = false;
- }
-
- /* Mark entries with non-existing buffer number as not valid. Give the
- * error message only once. */
- if (bufnum != 0 && (buflist_findnr(bufnum) == NULL)) {
- if (!did_bufnr_emsg) {
- did_bufnr_emsg = TRUE;
- EMSGN(_("E92: Buffer %" PRId64 " not found"), bufnum);
- }
- valid = false;
- bufnum = 0;
- }
-
- // If the 'valid' field is present it overrules the detected value.
- if (tv_dict_find(d, "valid", -1) != NULL) {
- valid = (int)tv_dict_get_number(d, "valid");
- }
-
- int status = qf_add_entry(qi,
- qf_idx,
- NULL, // dir
- (char_u *)filename,
- (char_u *)module,
- bufnum,
- (char_u *)text,
- lnum,
- col,
- vcol, // vis_col
- (char_u *)pattern, // search pattern
- nr,
- type,
- valid);
-
- xfree(filename);
- xfree(module);
- xfree(pattern);
- xfree(text);
-
- if (status == FAIL) {
- retval = FAIL;
+ retval = qf_add_entry_from_dict(qfl, d, li == tv_list_first(list),
+ &valid_entry);
+ if (retval == QF_FAIL) {
break;
}
});
- if (qi->qf_lists[qf_idx].qf_index == 0) {
- // no valid entry
- qi->qf_lists[qf_idx].qf_nonevalid = true;
- } else {
- qi->qf_lists[qf_idx].qf_nonevalid = false;
+ // Check if any valid error entries are added to the list.
+ if (valid_entry) {
+ qfl->qf_nonevalid = false;
+ } else if (qfl->qf_index == 0) {
+ qfl->qf_nonevalid = true;
}
+
+ // If not appending to the list, set the current error to the first entry
if (action != 'a') {
- qi->qf_lists[qf_idx].qf_ptr = qi->qf_lists[qf_idx].qf_start;
- if (qi->qf_lists[qf_idx].qf_count > 0) {
- qi->qf_lists[qf_idx].qf_index = 1;
- }
+ qfl->qf_ptr = qfl->qf_start;
+ }
+
+ // Update the current error index if not appending to the list or if the
+ // list was empty before and it is not empty now.
+ if ((action != 'a' || qfl->qf_index == 0)
+ && !qf_list_empty(qfl)) {
+ qfl->qf_index = 1;
}
// Don't update the cursor in quickfix window when appending entries
@@ -5033,7 +5852,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list,
return retval;
}
-// Get the quickfix list index from 'nr' or 'id'
+/// Get the quickfix list index from 'nr' or 'id'
static int qf_setprop_get_qfidx(
const qf_info_T *qi,
const dict_T *what,
@@ -5057,7 +5876,7 @@ static int qf_setprop_get_qfidx(
// non-available list and add the new list at the end of the
// stack.
*newlist = true;
- qf_idx = qi->qf_listcount > 0 ? qi->qf_listcount - 1 : 0;
+ qf_idx = qf_stack_empty(qi) ? 0 : qi->qf_listcount - 1;
} else if (qf_idx < 0 || qf_idx >= qi->qf_listcount) {
return INVALID_QFIDX;
} else if (action != ' ') {
@@ -5065,7 +5884,7 @@ static int qf_setprop_get_qfidx(
}
} else if (di->di_tv.v_type == VAR_STRING
&& strequal((const char *)di->di_tv.vval.v_string, "$")) {
- if (qi->qf_listcount > 0) {
+ if (!qf_stack_empty(qi)) {
qf_idx = qi->qf_listcount - 1;
} else if (*newlist) {
qf_idx = 0;
@@ -5093,13 +5912,13 @@ static int qf_setprop_title(qf_info_T *qi, int qf_idx, const dict_T *what,
const dictitem_T *di)
FUNC_ATTR_NONNULL_ALL
{
+ qf_list_T *qfl = qf_get_list(qi, qf_idx);
if (di->di_tv.v_type != VAR_STRING) {
return FAIL;
}
- xfree(qi->qf_lists[qf_idx].qf_title);
- qi->qf_lists[qf_idx].qf_title =
- (char_u *)tv_dict_get_string(what, "title", true);
+ xfree(qfl->qf_title);
+ qfl->qf_title = (char_u *)tv_dict_get_string(what, "title", true);
if (qf_idx == qi->qf_curlist) {
qf_update_win_titlevar(qi);
}
@@ -5153,7 +5972,7 @@ static int qf_setprop_items_from_lines(
}
if (action == 'r') {
- qf_free_items(qi, qf_idx);
+ qf_free_items(&qi->qf_lists[qf_idx]);
}
if (qf_init_ext(qi, qf_idx, NULL, NULL, &di->di_tv, errorformat,
false, (linenr_T)0, (linenr_T)0, NULL, NULL) > 0) {
@@ -5164,27 +5983,28 @@ static int qf_setprop_items_from_lines(
}
// Set quickfix list context.
-static int qf_setprop_context(qf_info_T *qi, int qf_idx, dictitem_T *di)
+static int qf_setprop_context(qf_list_T *qfl, dictitem_T *di)
FUNC_ATTR_NONNULL_ALL
{
- tv_free(qi->qf_lists[qf_idx].qf_ctx);
+ tv_free(qfl->qf_ctx);
typval_T *ctx = xcalloc(1, sizeof(typval_T));
tv_copy(&di->di_tv, ctx);
- qi->qf_lists[qf_idx].qf_ctx = ctx;
+ qfl->qf_ctx = ctx;
return OK;
}
-// Set quickfix/location list properties (title, items, context).
-// Also used to add items from parsing a list of lines.
-// Used by the setqflist() and setloclist() VimL functions.
+/// Set quickfix/location list properties (title, items, context).
+/// Also used to add items from parsing a list of lines.
+/// Used by the setqflist() and setloclist() Vim script functions.
static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action,
char_u *title)
FUNC_ATTR_NONNULL_ALL
{
+ qf_list_T *qfl;
dictitem_T *di;
int retval = FAIL;
- bool newlist = action == ' ' || qi->qf_curlist == qi->qf_listcount;
+ bool newlist = action == ' ' || qf_stack_empty(qi);
int qf_idx = qf_setprop_get_qfidx(qi, what, action, &newlist);
if (qf_idx == INVALID_QFIDX) { // List not found
return FAIL;
@@ -5196,6 +6016,7 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action,
qf_idx = qi->qf_curlist;
}
+ qfl = qf_get_list(qi, qf_idx);
if ((di = tv_dict_find(what, S_LEN("title"))) != NULL) {
retval = qf_setprop_title(qi, qf_idx, what, di);
}
@@ -5206,17 +6027,18 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action,
retval = qf_setprop_items_from_lines(qi, qf_idx, what, di, action);
}
if ((di = tv_dict_find(what, S_LEN("context"))) != NULL) {
- retval = qf_setprop_context(qi, qf_idx, di);
+ retval = qf_setprop_context(qfl, di);
}
if (retval == OK) {
- qf_list_changed(qi, qf_idx);
+ qf_list_changed(qfl);
}
return retval;
}
-// Find the non-location list window with the specified location list.
+/// Find the non-location list window with the specified location list stack in
+/// the current tabpage.
static win_T * find_win_with_ll(qf_info_T *qi)
{
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
@@ -5237,7 +6059,7 @@ static void qf_free_stack(win_T *wp, qf_info_T *qi)
if (qfwin != NULL) {
// If the quickfix/location list window is open, then clear it
if (qi->qf_curlist < qi->qf_listcount) {
- qf_free(qi, qi->qf_curlist);
+ qf_free(qf_get_curlist(qi));
}
qf_update_buffer(qi, NULL);
}
@@ -5261,15 +6083,14 @@ static void qf_free_stack(win_T *wp, qf_info_T *qi)
} else if (IS_LL_WINDOW(orig_wp)) {
// If the location list window is open, then create a new empty location
// list
- qf_info_T *new_ll = ll_new_list();
+ qf_info_T *new_ll = qf_alloc_stack(QFLT_LOCATION);
// first free the list reference in the location list window
ll_free_all(&orig_wp->w_llist_ref);
orig_wp->w_llist_ref = new_ll;
if (llwin != NULL) {
- llwin->w_llist = new_ll;
- new_ll->qf_refcount++;
+ win_set_loclist(wp, new_ll);
}
}
}
@@ -5290,15 +6111,22 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title,
if (action == 'f') {
// Free the entire quickfix or location list stack
qf_free_stack(wp, qi);
- } else if (what != NULL) {
+ return OK;
+ }
+
+ incr_quickfix_busy();
+
+ if (what != NULL) {
retval = qf_set_properties(qi, what, action, title);
} else {
retval = qf_add_entries(qi, qi->qf_curlist, list, title, action);
if (retval == OK) {
- qf_list_changed(qi, qi->qf_curlist);
+ qf_list_changed(qf_get_curlist(qi));
}
}
+ decr_quickfix_busy();
+
return retval;
}
@@ -5349,45 +6177,79 @@ bool set_ref_in_quickfix(int copyID)
return abort;
}
-/*
- * ":[range]cbuffer [bufnr]" command.
- * ":[range]caddbuffer [bufnr]" command.
- * ":[range]cgetbuffer [bufnr]" command.
- * ":[range]lbuffer [bufnr]" command.
- * ":[range]laddbuffer [bufnr]" command.
- * ":[range]lgetbuffer [bufnr]" command.
- */
-void ex_cbuffer(exarg_T *eap)
+/// Return the autocmd name for the :cbuffer Ex commands
+static char_u * cbuffer_get_auname(cmdidx_T cmdidx)
+{
+ switch (cmdidx) {
+ case CMD_cbuffer: return (char_u *)"cbuffer";
+ case CMD_cgetbuffer: return (char_u *)"cgetbuffer";
+ case CMD_caddbuffer: return (char_u *)"caddbuffer";
+ case CMD_lbuffer: return (char_u *)"lbuffer";
+ case CMD_lgetbuffer: return (char_u *)"lgetbuffer";
+ case CMD_laddbuffer: return (char_u *)"laddbuffer";
+ default: return NULL;
+ }
+}
+
+/// Process and validate the arguments passed to the :cbuffer, :caddbuffer,
+/// :cgetbuffer, :lbuffer, :laddbuffer, :lgetbuffer Ex commands.
+static int cbuffer_process_args(exarg_T *eap,
+ buf_T **bufp,
+ linenr_T *line1,
+ linenr_T *line2)
{
buf_T *buf = NULL;
- qf_info_T *qi = &ql_info;
- const char *au_name = NULL;
- win_T *wp = NULL;
- switch (eap->cmdidx) {
- case CMD_cbuffer:
- au_name = "cbuffer";
- break;
- case CMD_cgetbuffer:
- au_name = "cgetbuffer";
- break;
- case CMD_caddbuffer:
- au_name = "caddbuffer";
- break;
- case CMD_lbuffer:
- au_name = "lbuffer";
- break;
- case CMD_lgetbuffer:
- au_name = "lgetbuffer";
- break;
- case CMD_laddbuffer:
- au_name = "laddbuffer";
- break;
- default:
- break;
+ if (*eap->arg == NUL)
+ buf = curbuf;
+ else if (*skipwhite(skipdigits(eap->arg)) == NUL)
+ buf = buflist_findnr(atoi((char *)eap->arg));
+
+ if (buf == NULL) {
+ EMSG(_(e_invarg));
+ return FAIL;
+ }
+
+ if (buf->b_ml.ml_mfp == NULL) {
+ EMSG(_("E681: Buffer is not loaded"));
+ return FAIL;
+ }
+
+ if (eap->addr_count == 0) {
+ eap->line1 = 1;
+ eap->line2 = buf->b_ml.ml_line_count;
+ }
+
+ if (eap->line1 < 1 || eap->line1 > buf->b_ml.ml_line_count
+ || eap->line2 < 1 || eap->line2 > buf->b_ml.ml_line_count) {
+ EMSG(_(e_invrange));
+ return FAIL;
}
- if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, (char_u *)au_name,
+ *line1 = eap->line1;
+ *line2 = eap->line2;
+ *bufp = buf;
+
+ return OK;
+}
+
+// ":[range]cbuffer [bufnr]" command.
+// ":[range]caddbuffer [bufnr]" command.
+// ":[range]cgetbuffer [bufnr]" command.
+// ":[range]lbuffer [bufnr]" command.
+// ":[range]laddbuffer [bufnr]" command.
+// ":[range]lgetbuffer [bufnr]" command.
+void ex_cbuffer(exarg_T *eap)
+{
+ buf_T *buf = NULL;
+ char_u *au_name = NULL;
+ win_T *wp = NULL;
+ char_u *qf_title;
+ linenr_T line1;
+ linenr_T line2;
+
+ au_name = cbuffer_get_auname(eap->cmdidx);
+ if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name,
curbuf->b_fname, true, curbuf)) {
if (aborting()) {
return;
@@ -5395,134 +6257,112 @@ void ex_cbuffer(exarg_T *eap)
}
// Must come after autocommands.
- if (eap->cmdidx == CMD_lbuffer
- || eap->cmdidx == CMD_lgetbuffer
- || eap->cmdidx == CMD_laddbuffer) {
- qi = ll_get_or_alloc_list(curwin);
- wp = curwin;
+ qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp);
+
+ if (cbuffer_process_args(eap, &buf, &line1, &line2) == FAIL) {
+ return;
}
- if (*eap->arg == NUL)
- buf = curbuf;
- else if (*skipwhite(skipdigits(eap->arg)) == NUL)
- buf = buflist_findnr(atoi((char *)eap->arg));
- if (buf == NULL)
- EMSG(_(e_invarg));
- else if (buf->b_ml.ml_mfp == NULL)
- EMSG(_("E681: Buffer is not loaded"));
- else {
- if (eap->addr_count == 0) {
- eap->line1 = 1;
- eap->line2 = buf->b_ml.ml_line_count;
- }
- if (eap->line1 < 1 || eap->line1 > buf->b_ml.ml_line_count
- || eap->line2 < 1 || eap->line2 > buf->b_ml.ml_line_count) {
- EMSG(_(e_invrange));
- } else {
- char_u *qf_title = qf_cmdtitle(*eap->cmdlinep);
+ qf_title = qf_cmdtitle(*eap->cmdlinep);
- if (buf->b_sfname) {
- vim_snprintf((char *)IObuff, IOSIZE, "%s (%s)",
- (char *)qf_title, (char *)buf->b_sfname);
- qf_title = IObuff;
- }
+ if (buf->b_sfname) {
+ vim_snprintf((char *)IObuff, IOSIZE, "%s (%s)",
+ (char *)qf_title, (char *)buf->b_sfname);
+ qf_title = IObuff;
+ }
- int res = qf_init_ext(qi, qi->qf_curlist, NULL, buf, NULL, p_efm,
- (eap->cmdidx != CMD_caddbuffer
- && eap->cmdidx != CMD_laddbuffer),
- eap->line1, eap->line2, qf_title, NULL);
- if (res >= 0) {
- qf_list_changed(qi, qi->qf_curlist);
- }
- // Remember the current quickfix list identifier, so that we can
- // check for autocommands changing the current quickfix list.
- unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id;
- if (au_name != NULL) {
- const buf_T *const curbuf_old = curbuf;
- apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name,
- curbuf->b_fname, true, curbuf);
- if (curbuf != curbuf_old) {
- // Autocommands changed buffer, don't jump now, "qi" may
- // be invalid.
- res = 0;
- }
- }
- // Jump to the first error for new list and if autocmds didn't
- // free the list.
- if (res > 0 && (eap->cmdidx == CMD_cbuffer || eap->cmdidx == CMD_lbuffer)
- && qflist_valid(wp, save_qfid)) {
- // display the first error
- qf_jump_first(qi, save_qfid, eap->forceit);
- }
+ incr_quickfix_busy();
+
+ int res = qf_init_ext(qi, qi->qf_curlist, NULL, buf, NULL, p_efm,
+ (eap->cmdidx != CMD_caddbuffer
+ && eap->cmdidx != CMD_laddbuffer),
+ eap->line1, eap->line2, qf_title, NULL);
+ if (qf_stack_empty(qi)) {
+ decr_quickfix_busy();
+ return;
+ }
+ if (res >= 0) {
+ qf_list_changed(qf_get_curlist(qi));
+ }
+ // Remember the current quickfix list identifier, so that we can
+ // check for autocommands changing the current quickfix list.
+ unsigned save_qfid = qf_get_curlist(qi)->qf_id;
+ if (au_name != NULL) {
+ const buf_T *const curbuf_old = curbuf;
+ apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name,
+ curbuf->b_fname, true, curbuf);
+ if (curbuf != curbuf_old) {
+ // Autocommands changed buffer, don't jump now, "qi" may
+ // be invalid.
+ res = 0;
}
}
+ // Jump to the first error for new list and if autocmds didn't
+ // free the list.
+ if (res > 0 && (eap->cmdidx == CMD_cbuffer || eap->cmdidx == CMD_lbuffer)
+ && qflist_valid(wp, save_qfid)) {
+ // display the first error
+ qf_jump_first(qi, save_qfid, eap->forceit);
+ }
+
+ decr_quickfix_busy();
+}
+
+/// Return the autocmd name for the :cexpr Ex commands.
+static char_u * cexpr_get_auname(cmdidx_T cmdidx)
+{
+ switch (cmdidx) {
+ case CMD_cexpr: return (char_u *)"cexpr";
+ case CMD_cgetexpr: return (char_u *)"cgetexpr";
+ case CMD_caddexpr: return (char_u *)"caddexpr";
+ case CMD_lexpr: return (char_u *)"lexpr";
+ case CMD_lgetexpr: return (char_u *)"lgetexpr";
+ case CMD_laddexpr: return (char_u *)"laddexpr";
+ default: return NULL;
+ }
}
-/*
- * ":cexpr {expr}", ":cgetexpr {expr}", ":caddexpr {expr}" command.
- * ":lexpr {expr}", ":lgetexpr {expr}", ":laddexpr {expr}" command.
- */
+/// ":cexpr {expr}", ":cgetexpr {expr}", ":caddexpr {expr}" command.
+/// ":lexpr {expr}", ":lgetexpr {expr}", ":laddexpr {expr}" command.
void ex_cexpr(exarg_T *eap)
{
- qf_info_T *qi = &ql_info;
- const char *au_name = NULL;
+ char_u *au_name = NULL;
win_T *wp = NULL;
- switch (eap->cmdidx) {
- case CMD_cexpr:
- au_name = "cexpr";
- break;
- case CMD_cgetexpr:
- au_name = "cgetexpr";
- break;
- case CMD_caddexpr:
- au_name = "caddexpr";
- break;
- case CMD_lexpr:
- au_name = "lexpr";
- break;
- case CMD_lgetexpr:
- au_name = "lgetexpr";
- break;
- case CMD_laddexpr:
- au_name = "laddexpr";
- break;
- default:
- break;
- }
- if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, (char_u *)au_name,
+ au_name = cexpr_get_auname(eap->cmdidx);
+ if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name,
curbuf->b_fname, true, curbuf)) {
if (aborting()) {
return;
}
}
- if (eap->cmdidx == CMD_lexpr
- || eap->cmdidx == CMD_lgetexpr
- || eap->cmdidx == CMD_laddexpr) {
- qi = ll_get_or_alloc_list(curwin);
- wp = curwin;
- }
+ qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp);
- /* Evaluate the expression. When the result is a string or a list we can
- * use it to fill the errorlist. */
+ // Evaluate the expression. When the result is a string or a list we can
+ // use it to fill the errorlist.
typval_T tv;
if (eval0(eap->arg, &tv, NULL, true) != FAIL) {
if ((tv.v_type == VAR_STRING && tv.vval.v_string != NULL)
|| tv.v_type == VAR_LIST) {
+ incr_quickfix_busy();
int res = qf_init_ext(qi, qi->qf_curlist, NULL, NULL, &tv, p_efm,
(eap->cmdidx != CMD_caddexpr
&& eap->cmdidx != CMD_laddexpr),
(linenr_T)0, (linenr_T)0,
qf_cmdtitle(*eap->cmdlinep), NULL);
+ if (qf_stack_empty(qi)) {
+ decr_quickfix_busy();
+ goto cleanup;
+ }
if (res >= 0) {
- qf_list_changed(qi, qi->qf_curlist);
+ qf_list_changed(qf_get_curlist(qi));
}
// Remember the current quickfix list identifier, so that we can
// check for autocommands changing the current quickfix list.
- unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id;
+ unsigned save_qfid = qf_get_curlist(qi)->qf_id;
if (au_name != NULL) {
- apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name,
+ apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name,
curbuf->b_fname, true, curbuf);
}
// Jump to the first error for a new list and if autocmds didn't
@@ -5533,33 +6373,155 @@ void ex_cexpr(exarg_T *eap)
// display the first error
qf_jump_first(qi, save_qfid, eap->forceit);
}
+ decr_quickfix_busy();
} else {
EMSG(_("E777: String or List expected"));
}
+cleanup:
tv_clear(&tv);
}
}
-/*
- * ":helpgrep {pattern}"
- */
-void ex_helpgrep(exarg_T *eap)
+// Get the location list for ":lhelpgrep"
+static qf_info_T *hgr_get_ll(bool *new_ll)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
+{
+ win_T *wp;
+ qf_info_T *qi;
+
+ // If the current window is a help window, then use it
+ if (bt_help(curwin->w_buffer)) {
+ wp = curwin;
+ } else {
+ // Find an existing help window
+ wp = qf_find_help_win();
+ }
+
+ if (wp == NULL) { // Help window not found
+ qi = NULL;
+ } else {
+ qi = wp->w_llist;
+ }
+ if (qi == NULL) {
+ // Allocate a new location list for help text matches
+ qi = qf_alloc_stack(QFLT_LOCATION);
+ *new_ll = true;
+ }
+
+ return qi;
+}
+
+// Search for a pattern in a help file.
+static void hgr_search_file(
+ qf_info_T *qi,
+ char_u *fname,
+ regmatch_T *p_regmatch)
+ FUNC_ATTR_NONNULL_ARG(1, 3)
+{
+ FILE *const fd = os_fopen((char *)fname, "r");
+ if (fd == NULL) {
+ return;
+ }
+
+ long lnum = 1;
+ while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) {
+ char_u *line = IObuff;
+
+ if (vim_regexec(p_regmatch, line, (colnr_T)0)) {
+ int l = (int)STRLEN(line);
+
+ // remove trailing CR, LF, spaces, etc.
+ while (l > 0 && line[l - 1] <= ' ') {
+ line[--l] = NUL;
+ }
+
+ if (qf_add_entry(qf_get_curlist(qi),
+ NULL, // dir
+ fname,
+ NULL,
+ 0,
+ line,
+ lnum,
+ (int)(p_regmatch->startp[0] - line) + 1, // col
+ false, // vis_col
+ NULL, // search pattern
+ 0, // nr
+ 1, // type
+ true) // valid
+ == QF_FAIL) {
+ got_int = true;
+ if (line != IObuff) {
+ xfree(line);
+ }
+ break;
+ }
+ }
+ if (line != IObuff) {
+ xfree(line);
+ }
+ lnum++;
+ line_breakcheck();
+ }
+ fclose(fd);
+}
+
+// Search for a pattern in all the help files in the doc directory under
+// the given directory.
+static void hgr_search_files_in_dir(
+ qf_info_T *qi,
+ char_u *dirname,
+ regmatch_T *p_regmatch,
+ const char_u *lang)
+ FUNC_ATTR_NONNULL_ARG(1, 2, 3)
{
- regmatch_T regmatch;
- char_u *save_cpo;
- char_u *p;
int fcount;
- char_u **fnames;
- FILE *fd;
- int fi;
- long lnum;
- char_u *lang;
- qf_info_T *qi = &ql_info;
- int new_qi = FALSE;
- char_u *au_name = NULL;
+ char_u **fnames;
+
+ // Find all "*.txt" and "*.??x" files in the "doc" directory.
+ add_pathsep((char *)dirname);
+ 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++) {
+ // Skip files for a different language.
+ if (lang != NULL
+ && STRNICMP(lang, fnames[fi] + STRLEN(fnames[fi]) - 3, 2) != 0
+ && !(STRNICMP(lang, "en", 2) == 0
+ && STRNICMP("txt", fnames[fi] + STRLEN(fnames[fi]) - 3, 3)
+ == 0)) {
+ continue;
+ }
- /* Check for a specified language */
- lang = check_help_lang(eap->arg);
+ hgr_search_file(qi, fnames[fi], p_regmatch);
+ }
+ FreeWild(fcount, fnames);
+ }
+}
+
+// Search for a pattern in all the help files in the 'runtimepath'
+// and add the matches to a quickfix list.
+// 'lang' is the language specifier. If supplied, then only matches in the
+// specified language are found.
+static void hgr_search_in_rtp(qf_info_T *qi, regmatch_T *p_regmatch,
+ const char_u *lang)
+ FUNC_ATTR_NONNULL_ARG(1, 2)
+{
+ // Go through all directories in 'runtimepath'
+ char_u *p = p_rtp;
+ while (*p != NUL && !got_int) {
+ copy_option_part(&p, NameBuff, MAXPATHL, ",");
+
+ hgr_search_files_in_dir(qi, NameBuff, p_regmatch, lang);
+ }
+}
+
+// ":helpgrep {pattern}"
+void ex_helpgrep(exarg_T *eap)
+{
+ qf_info_T *qi = &ql_info;
+ bool new_qi = false;
+ char_u *au_name = NULL;
switch (eap->cmdidx) {
case CMD_helpgrep: au_name = (char_u *)"helpgrep"; break;
@@ -5573,150 +6535,67 @@ void ex_helpgrep(exarg_T *eap)
}
}
- /* Make 'cpoptions' empty, the 'l' flag should not be used here. */
- save_cpo = p_cpo;
+ // Make 'cpoptions' empty, the 'l' flag should not be used here.
+ char_u *const save_cpo = p_cpo;
p_cpo = empty_option;
- if (eap->cmdidx == CMD_lhelpgrep) {
- win_T *wp = NULL;
-
- // If the current window is a help window, then use it
- if (bt_help(curwin->w_buffer)) {
- wp = curwin;
- } else {
- // Find an existing help window
- FOR_ALL_WINDOWS_IN_TAB(wp2, curtab) {
- if (bt_help(wp2->w_buffer)) {
- wp = wp2;
- break;
- }
- }
- }
-
- if (wp == NULL) { // Help window not found
- qi = NULL;
- } else {
- qi = wp->w_llist;
- }
- if (qi == NULL) {
- /* Allocate a new location list for help text matches */
- qi = ll_new_list();
- new_qi = TRUE;
- }
+ if (is_loclist_cmd(eap->cmdidx)) {
+ qi = hgr_get_ll(&new_qi);
}
- regmatch.regprog = vim_regcomp(eap->arg, RE_MAGIC + RE_STRING);
- regmatch.rm_ic = FALSE;
+ incr_quickfix_busy();
+
+ // Check for a specified language
+ char_u *const lang = check_help_lang(eap->arg);
+ regmatch_T regmatch = {
+ .regprog = vim_regcomp(eap->arg, RE_MAGIC + RE_STRING),
+ .rm_ic = false,
+ };
if (regmatch.regprog != NULL) {
// Create a new quickfix list.
qf_new_list(qi, qf_cmdtitle(*eap->cmdlinep));
- // Go through all the directories in 'runtimepath'
- p = p_rtp;
- while (*p != NUL && !got_int) {
- copy_option_part(&p, NameBuff, MAXPATHL, ",");
-
- /* Find all "*.txt" and "*.??x" files in the "doc" directory. */
- add_pathsep((char *)NameBuff);
- STRCAT(NameBuff, "doc/*.\\(txt\\|??x\\)");
-
- // Note: We cannot just do `&NameBuff` because it is a statically sized array
- // so `NameBuff == &NameBuff` according to C semantics.
- char_u *buff_list[1] = {NameBuff};
- if (gen_expand_wildcards(1, buff_list, &fcount,
- &fnames, EW_FILE|EW_SILENT) == OK
- && fcount > 0) {
- for (fi = 0; fi < fcount && !got_int; fi++) {
- // Skip files for a different language.
- if (lang != NULL
- && STRNICMP(lang, fnames[fi] + STRLEN(fnames[fi]) - 3, 2) != 0
- && !(STRNICMP(lang, "en", 2) == 0
- && STRNICMP("txt", fnames[fi]
- + STRLEN(fnames[fi]) - 3, 3) == 0)) {
- continue;
- }
- fd = os_fopen((char *)fnames[fi], "r");
- if (fd != NULL) {
- lnum = 1;
- while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) {
- char_u *line = IObuff;
- if (vim_regexec(&regmatch, line, (colnr_T)0)) {
- int l = (int)STRLEN(line);
-
- /* remove trailing CR, LF, spaces, etc. */
- while (l > 0 && line[l - 1] <= ' ')
- line[--l] = NUL;
-
- if (qf_add_entry(qi,
- qi->qf_curlist,
- NULL, // dir
- fnames[fi],
- NULL,
- 0,
- line,
- lnum,
- (int)(regmatch.startp[0] - line)
- + 1, // col
- false, // vis_col
- NULL, // search pattern
- 0, // nr
- 1, // type
- true) // valid
- == FAIL) {
- got_int = true;
- if (line != IObuff) {
- xfree(line);
- }
- break;
- }
- }
- if (line != IObuff)
- xfree(line);
- ++lnum;
- line_breakcheck();
- }
- fclose(fd);
- }
- }
- FreeWild(fcount, fnames);
- }
- }
+ hgr_search_in_rtp(qi, &regmatch, lang);
vim_regfree(regmatch.regprog);
- qi->qf_lists[qi->qf_curlist].qf_nonevalid = FALSE;
- qi->qf_lists[qi->qf_curlist].qf_ptr =
- qi->qf_lists[qi->qf_curlist].qf_start;
- qi->qf_lists[qi->qf_curlist].qf_index = 1;
+ qf_list_T *qfl = qf_get_curlist(qi);
+ qfl->qf_nonevalid = false;
+ qfl->qf_ptr = qfl->qf_start;
+ qfl->qf_index = 1;
+ qf_list_changed(qfl);
+ qf_update_buffer(qi, NULL);
}
- if (p_cpo == empty_option)
+ if (p_cpo == empty_option) {
p_cpo = save_cpo;
- else
- /* Darn, some plugin changed the value. */
+ } else {
+ // Darn, some plugin changed the value.
free_string_option(save_cpo);
-
- qf_list_changed(qi, qi->qf_curlist);
- qf_update_buffer(qi, NULL);
+ }
if (au_name != NULL) {
apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name,
curbuf->b_fname, true, curbuf);
if (!new_qi && IS_LL_STACK(qi) && qf_find_buf(qi) == NULL) {
// autocommands made "qi" invalid
+ decr_quickfix_busy();
return;
}
}
- /* Jump to first match. */
- if (qi->qf_lists[qi->qf_curlist].qf_count > 0)
- qf_jump(qi, 0, 0, FALSE);
- else
+ // Jump to first match.
+ if (!qf_list_empty(qf_get_curlist(qi))) {
+ qf_jump(qi, 0, 0, false);
+ } else {
EMSG2(_(e_nomatch2), eap->arg);
+ }
+
+ decr_quickfix_busy();
if (eap->cmdidx == CMD_lhelpgrep) {
- /* If the help window is not opened or if it already points to the
- * correct location list, then free the new location list. */
+ // If the help window is not opened or if it already points to the
+ // correct location list, then free the new location list.
if (!bt_help(curwin->w_buffer) || curwin->w_llist == qi) {
if (new_qi) {
ll_free_all(&qi);
@@ -5727,3 +6606,4 @@ void ex_helpgrep(exarg_T *eap)
}
}
+
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
index 63640e723e..f33c61d39f 100644
--- a/src/nvim/regexp_nfa.c
+++ b/src/nvim/regexp_nfa.c
@@ -4943,7 +4943,7 @@ static int skip_to_start(int c, colnr_T *colp)
*/
static long find_match_text(colnr_T startcol, int regstart, char_u *match_text)
{
-#define PTR2LEN(x) enc_utf8 ? utf_ptr2len(x) : MB_PTR2LEN(x)
+#define PTR2LEN(x) utf_ptr2len(x)
colnr_T col = startcol;
int regstart_len = PTR2LEN(regline + startcol);
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 5bcd2c808d..1d29ae064e 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -124,7 +124,7 @@
// temporary buffer for rendering a single screenline, so it can be
-// comparared with previous contents to calulate smallest delta.
+// comparared with previous contents to calculate smallest delta.
static size_t linebuf_size = 0;
static schar_T *linebuf_char = NULL;
static sattr_T *linebuf_attr = NULL;
@@ -286,6 +286,11 @@ int update_screen(int type)
return FAIL;
}
+ // May have postponed updating diffs.
+ if (need_diff_redraw) {
+ diff_redraw(true);
+ }
+
if (must_redraw) {
if (type < must_redraw) /* use maximal type */
type = must_redraw;
@@ -577,11 +582,14 @@ void conceal_check_cursor_line(void)
/// Whether cursorline is drawn in a special way
///
/// If true, both old and new cursorline will need
-/// need to be redrawn when moving cursor within windows.
+/// to be redrawn when moving cursor within windows.
+/// TODO(bfredl): VIsual_active shouldn't be needed, but is used to fix a glitch
+/// caused by scrolling.
bool win_cursorline_standout(const win_T *wp)
FUNC_ATTR_NONNULL_ALL
{
- return wp->w_p_cul || (wp->w_p_cole > 0 && !conceal_cursor_line(wp));
+ return wp->w_p_cul
+ || (wp->w_p_cole > 0 && (VIsual_active || !conceal_cursor_line(wp)));
}
/*
@@ -871,7 +879,7 @@ static void win_update(win_T *wp)
if (wp->w_lines[0].wl_lnum != wp->w_topline)
i += diff_check_fill(wp, wp->w_lines[0].wl_lnum)
- wp->w_old_topfill;
- if (i < wp->w_grid.Rows - 2) { // less than a screen off
+ if (i != 0 && i < wp->w_grid.Rows - 2) { // less than a screen off
// Try to insert the correct number of lines.
// If not the last window, delete the lines at the bottom.
// win_ins_lines may fail when the terminal can't do it.
@@ -2403,10 +2411,10 @@ win_line (
filler_todo = filler_lines;
// Cursor line highlighting for 'cursorline' in the current window.
- if (wp->w_p_cul && lnum == wp->w_cursor.lnum) {
+ if (lnum == wp->w_cursor.lnum) {
// Do not show the cursor line when Visual mode is active, because it's
// not clear what is selected then.
- if (!(wp == curwin && VIsual_active)) {
+ if (wp->w_p_cul && !(wp == curwin && VIsual_active)) {
int cul_attr = win_hl_attr(wp, HLF_CUL);
HlAttrs ae = syn_attr2entry(cul_attr);
@@ -2991,7 +2999,7 @@ win_line (
if (shl->startcol != MAXCOL
&& v >= (long)shl->startcol
&& v < (long)shl->endcol) {
- int tmp_col = v + MB_PTR2LEN(ptr);
+ int tmp_col = v + utfc_ptr2len(ptr);
if (shl->endcol < tmp_col) {
shl->endcol = tmp_col;
@@ -3756,14 +3764,12 @@ win_line (
char_attr = hl_combine_attr(char_attr, extra_attr);
}
- /*
- * Handle the case where we are in column 0 but not on the first
- * character of the line and the user wants us to show us a
- * special character (via 'listchars' option "precedes:<char>".
- */
+ // Handle the case where we are in column 0 but not on the first
+ // character of the line and the user wants us to show us a
+ // special character (via 'listchars' option "precedes:<char>".
if (lcs_prec_todo != NUL
&& wp->w_p_list
- && (wp->w_p_wrap ? wp->w_skipcol > 0 : wp->w_leftcol > 0)
+ && (wp->w_p_wrap ? (wp->w_skipcol > 0 && row == 0) : wp->w_leftcol > 0)
&& filler_todo <= 0
&& draw_state > WL_NR
&& c != NUL) {
@@ -4007,7 +4013,7 @@ win_line (
break;
}
- ++vcol;
+ vcol += cells;
}
}
@@ -4015,10 +4021,13 @@ win_line (
if (wp->w_buffer->terminal) {
// terminal buffers may need to highlight beyond the end of the
// logical line
- while (col < grid->Columns) {
+ int n = wp->w_p_rl ? -1 : 1;
+ while (col >= 0 && col < grid->Columns) {
schar_from_ascii(linebuf_char[off], ' ');
- linebuf_attr[off++] = term_attrs[vcol++];
- col++;
+ linebuf_attr[off] = term_attrs[vcol];
+ off += n;
+ vcol += n;
+ col += n;
}
}
grid_put_linebuf(grid, row, 0, col, grid->Columns, wp->w_p_rl, wp,
@@ -4353,7 +4362,7 @@ static int grid_char_needs_redraw(ScreenGrid *grid, int off_from, int off_to,
|| (line_off2cells(linebuf_char, off_from, off_from + cols) > 1
&& schar_cmp(linebuf_char[off_from + 1],
grid->chars[off_to + 1])))
- || p_wd < 0));
+ || rdb_flags & RDB_NODELTA));
}
/// Move one buffered line to the window grid, but only the characters that
@@ -5103,6 +5112,8 @@ win_redr_custom (
win_T *ewp;
int p_crb_save;
+ ScreenGrid *grid = &default_grid;
+
/* There is a tiny chance that this gets called recursively: When
* redrawing a status line triggers redrawing the ruler or tabline.
* Avoid trouble by not allowing recursion. */
@@ -5142,10 +5153,11 @@ win_redr_custom (
}
maxwidth = wp->w_width - col;
if (!wp->w_status_height) {
+ grid = &msg_grid_adj;
row = Rows - 1;
maxwidth--; // writing in last column may cause scrolling
fillchar = ' ';
- attr = 0;
+ attr = HL_ATTR(HLF_MSG);
}
use_sandbox = was_set_insecurely((char_u *)"rulerformat", 0);
@@ -5195,13 +5207,13 @@ win_redr_custom (
/*
* Draw each snippet with the specified highlighting.
*/
- grid_puts_line_start(&default_grid, row);
+ grid_puts_line_start(grid, row);
curattr = attr;
p = buf;
for (n = 0; hltab[n].start != NULL; n++) {
int textlen = (int)(hltab[n].start - p);
- grid_puts_len(&default_grid, p, textlen, row, col, curattr);
+ grid_puts_len(grid, p, textlen, row, col, curattr);
col += vim_strnsize(p, textlen);
p = hltab[n].start;
@@ -5215,7 +5227,7 @@ win_redr_custom (
curattr = highlight_user[hltab[n].userhl - 1];
}
// Make sure to use an empty string instead of p, if p is beyond buf + len.
- grid_puts(&default_grid, p >= buf + len ? (char_u *)"" : p, row, col,
+ grid_puts(grid, p >= buf + len ? (char_u *)"" : p, row, col,
curattr);
grid_puts_line_flush(false);
@@ -7060,7 +7072,7 @@ static void win_redr_ruler(win_T *wp, int always)
} else {
row = Rows - 1;
fillchar = ' ';
- attr = 0;
+ attr = HL_ATTR(HLF_MSG);
width = Columns;
off = 0;
}
diff --git a/src/nvim/search.c b/src/nvim/search.c
index 7d1c19d68c..5e32715e49 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -516,19 +516,17 @@ void last_pat_prog(regmmatch_T *regmatch)
/// the index of the first matching
/// subpattern plus one; one if there was none.
int searchit(
- win_T *win, /* window to search in, can be NULL for a
- buffer without a window! */
+ win_T *win, // window to search in; can be NULL for a
+ // buffer without a window!
buf_T *buf,
pos_T *pos,
- pos_T *end_pos, // set to end of the match, unless NULL
+ pos_T *end_pos, // set to end of the match, unless NULL
Direction dir,
char_u *pat,
long count,
int options,
- int pat_use, // which pattern to use when "pat" is empty
- linenr_T stop_lnum, // stop after this line number when != 0
- proftime_T *tm, // timeout limit or NULL
- int *timed_out // set when timed out or NULL
+ int pat_use, // which pattern to use when "pat" is empty
+ searchit_arg_T *extra_arg // optional extra arguments, can be NULL
)
{
int found;
@@ -548,7 +546,16 @@ int searchit(
int submatch = 0;
bool first_match = true;
int save_called_emsg = called_emsg;
- int break_loop = FALSE;
+ int break_loop = false;
+ linenr_T stop_lnum = 0; // stop after this line number when != 0
+ proftime_T *tm = NULL; // timeout limit or NULL
+ int *timed_out = NULL; // set when timed out or NULL
+
+ if (extra_arg != NULL) {
+ stop_lnum = extra_arg->sa_stop_lnum;
+ tm = extra_arg->sa_tm;
+ timed_out = &extra_arg->sa_timed_out;
+ }
if (search_regcomp(pat, RE_SEARCH, pat_use,
(options & (SEARCH_HIS + SEARCH_KEEP)), &regmatch) == FAIL) {
@@ -681,7 +688,7 @@ int searchit(
}
if (matchcol == matchpos.col && ptr[matchcol] != NUL) {
- matchcol += MB_PTR2LEN(ptr + matchcol);
+ matchcol += utfc_ptr2len(ptr + matchcol);
}
if (matchcol == 0 && (options & SEARCH_START)) {
@@ -889,6 +896,9 @@ int searchit(
give_warning((char_u *)_(dir == BACKWARD
? top_bot_msg : bot_top_msg), true);
}
+ if (extra_arg != NULL) {
+ extra_arg->sa_wrapped = true;
+ }
}
if (got_int || called_emsg
|| (timed_out != NULL && *timed_out)
@@ -983,8 +993,7 @@ int do_search(
char_u *pat,
long count,
int options,
- proftime_T *tm, // timeout limit or NULL
- int *timed_out // flag set on timeout or NULL
+ searchit_arg_T *sia // optional arguments or NULL
)
{
pos_T pos; /* position of the last match */
@@ -1271,7 +1280,7 @@ int do_search(
& (SEARCH_KEEP + SEARCH_PEEK + SEARCH_HIS + SEARCH_MSG
+ SEARCH_START
+ ((pat != NULL && *pat == ';') ? 0 : SEARCH_NOOF)))),
- RE_LAST, (linenr_T)0, tm, timed_out);
+ RE_LAST, sia);
if (dircp != NULL) {
*dircp = dirc; // restore second '/' or '?' for normal_cmd()
@@ -1755,7 +1764,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel)
find_mps_values(&initc, &findc, &backwards, FALSE);
if (findc)
break;
- pos.col += MB_PTR2LEN(linep + pos.col);
+ pos.col += utfc_ptr2len(linep + pos.col);
}
if (!findc) {
/* no brace in the line, maybe use " #if" then */
@@ -2234,14 +2243,14 @@ showmatch(
for (p = curbuf->b_p_mps; *p != NUL; ++p) {
if (PTR2CHAR(p) == c && (curwin->w_p_rl ^ p_ri))
break;
- p += MB_PTR2LEN(p) + 1;
- if (PTR2CHAR(p) == c
- && !(curwin->w_p_rl ^ p_ri)
- )
+ p += utfc_ptr2len(p) + 1;
+ if (PTR2CHAR(p) == c && !(curwin->w_p_rl ^ p_ri)) {
break;
- p += MB_PTR2LEN(p);
- if (*p == NUL)
+ }
+ p += utfc_ptr2len(p);
+ if (*p == NUL) {
return;
+ }
}
if ((lpos = findmatch(NULL, NUL)) == NULL) { // no match, so beep
@@ -3768,30 +3777,31 @@ find_prev_quote(
return col_start;
}
-/*
- * Find quote under the cursor, cursor at end.
- * Returns TRUE if found, else FALSE.
- */
-int
-current_quote(
+// Find quote under the cursor, cursor at end.
+// Returns true if found, else false.
+bool current_quote(
oparg_T *oap,
long count,
- int include, /* TRUE == include quote char */
- int quotechar /* Quote character */
+ bool include, // true == include quote char
+ int quotechar // Quote character
)
+ FUNC_ATTR_NONNULL_ALL
{
char_u *line = get_cursor_line_ptr();
int col_end;
int col_start = curwin->w_cursor.col;
bool inclusive = false;
- int vis_empty = true; // Visual selection <= 1 char
- int vis_bef_curs = false; // Visual starts before cursor
- int inside_quotes = false; // Looks like "i'" done before
- int selected_quote = false; // Has quote inside selection
+ bool vis_empty = true; // Visual selection <= 1 char
+ bool vis_bef_curs = false; // Visual starts before cursor
+ bool did_exclusive_adj = false; // adjusted pos for 'selection'
+ bool inside_quotes = false; // Looks like "i'" done before
+ bool selected_quote = false; // Has quote inside selection
int i;
- int restore_vis_bef = false; // resotre VIsual on abort
+ bool restore_vis_bef = false; // resotre VIsual on abort
- // Correct cursor when 'selection' is "exclusive".
+ // When 'selection' is "exclusive" move the cursor to where it would be
+ // with 'selection' "inclusive", so that the logic is the same for both.
+ // The cursor then is moved forward after adjusting the area.
if (VIsual_active) {
// this only works within one line
if (VIsual.lnum != curwin->w_cursor.lnum) {
@@ -3799,8 +3809,17 @@ current_quote(
}
vis_bef_curs = lt(VIsual, curwin->w_cursor);
+ vis_empty = equalpos(VIsual, curwin->w_cursor);
if (*p_sel == 'e') {
- if (!vis_bef_curs) {
+ if (vis_bef_curs) {
+ dec_cursor();
+ did_exclusive_adj = true;
+ } else if (!vis_empty) {
+ dec(&VIsual);
+ did_exclusive_adj = true;
+ }
+ vis_empty = equalpos(VIsual, curwin->w_cursor);
+ if (!vis_bef_curs && !vis_empty) {
// VIsual needs to be start of Visual selection.
pos_T t = curwin->w_cursor;
@@ -3809,9 +3828,7 @@ current_quote(
vis_bef_curs = true;
restore_vis_bef = true;
}
- dec_cursor();
}
- vis_empty = equalpos(VIsual, curwin->w_cursor);
}
if (!vis_empty) {
@@ -3836,7 +3853,7 @@ current_quote(
/* Find out if we have a quote in the selection. */
while (i <= col_end)
if (line[i++] == quotechar) {
- selected_quote = TRUE;
+ selected_quote = true;
break;
}
}
@@ -3925,8 +3942,8 @@ current_quote(
}
}
- /* When "include" is TRUE, include spaces after closing quote or before
- * the starting quote. */
+ // When "include" is true, include spaces after closing quote or before
+ // the starting quote.
if (include) {
if (ascii_iswhite(line[col_end + 1]))
while (ascii_iswhite(line[col_end + 1]))
@@ -3972,9 +3989,10 @@ current_quote(
inclusive = true;
if (VIsual_active) {
if (vis_empty || vis_bef_curs) {
- /* decrement cursor when 'selection' is not exclusive */
- if (*p_sel != 'e')
+ // decrement cursor when 'selection' is not exclusive
+ if (*p_sel != 'e') {
dec_cursor();
+ }
} else {
/* Cursor is at start of Visual area. Set the end of the Visual
* area when it was just inside quotes or it didn't end at a
@@ -3998,11 +4016,13 @@ current_quote(
oap->inclusive = inclusive;
}
- return OK;
+ return true;
abort_search:
if (VIsual_active && *p_sel == 'e') {
- inc_cursor();
+ if (did_exclusive_adj) {
+ inc_cursor();
+ }
if (restore_vis_bef) {
pos_T t = curwin->w_cursor;
@@ -4028,9 +4048,6 @@ current_search(
bool old_p_ws = p_ws;
pos_T save_VIsual = VIsual;
- /* wrapping should not occur */
- p_ws = false;
-
/* Correct cursor when 'selection' is exclusive */
if (VIsual_active && *p_sel == 'e' && lt(VIsual, curwin->w_cursor))
dec_cursor();
@@ -4040,25 +4057,21 @@ current_search(
pos_T pos; // position after the pattern
int result; // result of various function calls
+ orig_pos = pos = curwin->w_cursor;
if (VIsual_active) {
- orig_pos = pos = curwin->w_cursor;
-
// Searching further will extend the match.
if (forward) {
incl(&pos);
} else {
decl(&pos);
}
- } else {
- orig_pos = pos = curwin->w_cursor;
}
// Is the pattern is zero-width?, this time, don't care about the direction
- int one_char = is_one_char(spats[last_idx].pat, true, &curwin->w_cursor,
- FORWARD);
- if (one_char == -1) {
- p_ws = old_p_ws;
- return FAIL; /* pattern not found */
+ int zero_width = is_zero_width(spats[last_idx].pat, true, &curwin->w_cursor,
+ FORWARD);
+ if (zero_width == -1) {
+ return FAIL; // pattern not found
}
/*
@@ -4070,15 +4083,22 @@ current_search(
int dir = forward ? i : !i;
int flags = 0;
- if (!dir && !one_char) {
+ if (!dir && !zero_width) {
flags = SEARCH_END;
}
end_pos = pos;
+ // wrapping should not occur in the first round
+ if (i == 0) {
+ p_ws = false;
+ }
+
result = searchit(curwin, curbuf, &pos, &end_pos,
(dir ? FORWARD : BACKWARD),
spats[last_idx].pat, i ? count : 1,
- SEARCH_KEEP | flags, RE_SEARCH, 0, NULL, NULL);
+ SEARCH_KEEP | flags, RE_SEARCH, NULL);
+
+ p_ws = old_p_ws;
// First search may fail, but then start searching from the
// beginning of the file (cursor might be on the search match)
@@ -4088,7 +4108,6 @@ current_search(
curwin->w_cursor = orig_pos;
if (VIsual_active)
VIsual = save_VIsual;
- p_ws = old_p_ws;
return FAIL;
} else if (i == 0 && !result) {
if (forward) { // try again from start of buffer
@@ -4100,26 +4119,24 @@ current_search(
ml_get(curwin->w_buffer->b_ml.ml_line_count));
}
}
- p_ws = old_p_ws;
}
pos_T start_pos = pos;
- p_ws = old_p_ws;
-
if (!VIsual_active) {
VIsual = start_pos;
}
// put cursor on last character of match
curwin->w_cursor = end_pos;
- if (lt(VIsual, end_pos)) {
+ if (lt(VIsual, end_pos) && forward) {
dec_cursor();
+ } else if (VIsual_active && lt(curwin->w_cursor, VIsual)) {
+ curwin->w_cursor = pos; // put the cursor on the start of the match
}
VIsual_active = true;
VIsual_mode = 'v';
- redraw_curbuf_later(INVERTED); // Update the inversion.
if (*p_sel == 'e') {
// Correction for exclusive selection depends on the direction.
if (forward && ltoreq(VIsual, curwin->w_cursor)) {
@@ -4141,13 +4158,13 @@ current_search(
return OK;
}
-/// Check if the pattern is one character long or zero-width.
+/// Check if the pattern is zero-width.
/// If move is true, check from the beginning of the buffer,
/// else from position "cur".
/// "direction" is FORWARD or BACKWARD.
/// Returns TRUE, FALSE or -1 for failure.
-static int is_one_char(char_u *pattern, bool move, pos_T *cur,
- Direction direction)
+static int
+is_zero_width(char_u *pattern, int move, pos_T *cur, Direction direction)
{
regmmatch_T regmatch;
int nmatched = 0;
@@ -4175,7 +4192,7 @@ static int is_one_char(char_u *pattern, bool move, pos_T *cur,
flag = SEARCH_START;
}
if (searchit(curwin, curbuf, &pos, NULL, direction, pattern, 1,
- SEARCH_KEEP + flag, RE_SEARCH, 0, NULL, NULL) != FAIL) {
+ SEARCH_KEEP + flag, RE_SEARCH, NULL) != FAIL) {
// Zero-width pattern should match somewhere, then we can check if
// start and end are in the same position.
called_emsg = false;
@@ -4184,7 +4201,7 @@ static int is_one_char(char_u *pattern, bool move, pos_T *cur,
nmatched = vim_regexec_multi(&regmatch, curwin, curbuf,
pos.lnum, regmatch.startpos[0].col,
NULL, NULL);
- if (!nmatched) {
+ if (nmatched != 0) {
break;
}
} while (direction == FORWARD
@@ -4195,10 +4212,6 @@ static int is_one_char(char_u *pattern, bool move, pos_T *cur,
result = (nmatched != 0
&& regmatch.startpos[0].lnum == regmatch.endpos[0].lnum
&& regmatch.startpos[0].col == regmatch.endpos[0].col);
- // one char width
- if (!result && inc(&pos) >= 0 && pos.col == regmatch.endpos[0].col) {
- result = true;
- }
}
}
@@ -4262,7 +4275,7 @@ static void search_stat(int dirc, pos_T *pos,
start = profile_setlimit(20L);
while (!got_int && searchit(curwin, curbuf, &lastpos, NULL,
FORWARD, NULL, 1, SEARCH_KEEP, RE_LAST,
- (linenr_T)0, NULL, NULL) != FAIL) {
+ NULL) != FAIL) {
// Stop after passing the time limit.
if (profile_passed_limit(start)) {
cnt = OUT_OF_TIME;
@@ -4458,7 +4471,8 @@ find_pattern_in_path(
if (i == max_path_depth) {
break;
}
- if (path_full_compare(new_fname, files[i].name, true) & kEqualFiles) {
+ if (path_full_compare(new_fname, files[i].name,
+ true, true) & kEqualFiles) {
if (type != CHECK_PATH
&& action == ACTION_SHOW_ALL && files[i].matched) {
msg_putchar('\n'); // cursor below last one */
@@ -4844,7 +4858,7 @@ exit_matched:
&& action == ACTION_EXPAND
&& !(compl_cont_status & CONT_SOL)
&& *startp != NUL
- && *(p = startp + MB_PTR2LEN(startp)) != NUL)
+ && *(p = startp + utfc_ptr2len(startp)) != NUL)
goto search_line;
}
line_breakcheck();
diff --git a/src/nvim/search.h b/src/nvim/search.h
index cb094aab8c..0366aee8a1 100644
--- a/src/nvim/search.h
+++ b/src/nvim/search.h
@@ -70,6 +70,15 @@ typedef struct spat {
dict_T *additional_data; ///< Additional data from ShaDa file.
} SearchPattern;
+/// Optional extra arguments for searchit().
+typedef struct {
+ linenr_T sa_stop_lnum; ///< stop after this line number when != 0
+ proftime_T *sa_tm; ///< timeout limit or NULL
+ int sa_timed_out; ///< set when timed out
+ int sa_wrapped; ///< search wrapped around
+} searchit_arg_T;
+
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "search.h.generated.h"
#endif
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index 724a0332bc..5feb7efda9 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -1910,11 +1910,11 @@ int init_syl_tab(slang_T *slang)
// Count the number of syllables in "word".
// When "word" contains spaces the syllables after the last space are counted.
// Returns zero if syllables are not defines.
-static int count_syllables(slang_T *slang, char_u *word)
+static int count_syllables(slang_T *slang, const char_u *word)
+ FUNC_ATTR_NONNULL_ALL
{
int cnt = 0;
bool skip = false;
- char_u *p;
int len;
syl_item_T *syl;
int c;
@@ -1922,7 +1922,7 @@ static int count_syllables(slang_T *slang, char_u *word)
if (slang->sl_syllable == NULL)
return 0;
- for (p = word; *p != NUL; p += len) {
+ for (const char_u *p = word; *p != NUL; p += len) {
// When running into a space reset counter.
if (*p == ' ') {
len = 1;
@@ -2031,7 +2031,8 @@ char_u *did_set_spelllang(win_T *wp)
// Check if we loaded this language before.
for (slang = first_lang; slang != NULL; slang = slang->sl_next) {
- if (path_full_compare(lang, slang->sl_fname, false) == kEqualFiles) {
+ if (path_full_compare(lang, slang->sl_fname, false, true)
+ == kEqualFiles) {
break;
}
}
@@ -2076,7 +2077,7 @@ char_u *did_set_spelllang(win_T *wp)
// Loop over the languages, there can be several files for "lang".
for (slang = first_lang; slang != NULL; slang = slang->sl_next) {
if (filename
- ? path_full_compare(lang, slang->sl_fname, false) == kEqualFiles
+ ? path_full_compare(lang, slang->sl_fname, false, true) == kEqualFiles
: STRICMP(lang, slang->sl_name) == 0) {
region_mask = REGION_ALL;
if (!filename && region != NULL) {
@@ -2129,7 +2130,7 @@ char_u *did_set_spelllang(win_T *wp)
for (c = 0; c < ga.ga_len; ++c) {
p = LANGP_ENTRY(ga, c)->lp_slang->sl_fname;
if (p != NULL
- && path_full_compare(spf_name, p, false) == kEqualFiles) {
+ && path_full_compare(spf_name, p, false, true) == kEqualFiles) {
break;
}
}
@@ -2139,7 +2140,8 @@ char_u *did_set_spelllang(win_T *wp)
// 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) == kEqualFiles) {
+ if (path_full_compare(spf_name, slang->sl_fname, false, true)
+ == kEqualFiles) {
break;
}
}
@@ -2568,36 +2570,33 @@ void init_spell_chartab(void)
/// Thus this only works properly when past the first character of the word.
///
/// @param wp Buffer used.
-static bool spell_iswordp(char_u *p, win_T *wp)
+static bool spell_iswordp(const char_u *p, const win_T *wp)
+ FUNC_ATTR_NONNULL_ALL
{
- char_u *s;
- int l;
int c;
- if (has_mbyte) {
- l = MB_PTR2LEN(p);
- s = p;
- if (l == 1) {
- // be quick for ASCII
- if (wp->w_s->b_spell_ismw[*p])
- s = p + 1; // skip a mid-word character
- } else {
- c = utf_ptr2char(p);
- if (c < 256 ? wp->w_s->b_spell_ismw[c]
- : (wp->w_s->b_spell_ismw_mb != NULL
- && vim_strchr(wp->w_s->b_spell_ismw_mb, c) != NULL)) {
- s = p + l;
- }
+ const int l = utfc_ptr2len(p);
+ const char_u *s = p;
+ if (l == 1) {
+ // be quick for ASCII
+ if (wp->w_s->b_spell_ismw[*p]) {
+ s = p + 1; // skip a mid-word character
}
-
- c = utf_ptr2char(s);
- if (c > 255) {
- return spell_mb_isword_class(mb_get_class(s), wp);
+ } else {
+ c = utf_ptr2char(p);
+ if (c < 256
+ ? wp->w_s->b_spell_ismw[c]
+ : (wp->w_s->b_spell_ismw_mb != NULL
+ && vim_strchr(wp->w_s->b_spell_ismw_mb, c) != NULL)) {
+ s = p + l;
}
- return spelltab.st_isw[c];
}
- return spelltab.st_isw[wp->w_s->b_spell_ismw[*p] ? p[1] : p[0]];
+ c = utf_ptr2char(s);
+ if (c > 255) {
+ return spell_mb_isword_class(mb_get_class(s), wp);
+ }
+ return spelltab.st_isw[c];
}
// Returns true if "p" points to a word character.
@@ -2615,7 +2614,8 @@ bool spell_iswordp_nmw(const char_u *p, win_T *wp)
// Only for characters above 255.
// Unicode subscript and superscript are not considered word characters.
// See also utf_class() in mbyte.c.
-static bool spell_mb_isword_class(int cl, win_T *wp)
+static bool spell_mb_isword_class(int cl, const win_T *wp)
+ FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
if (wp->w_s->b_cjk)
// East Asian characters are not considered word characters.
@@ -2625,9 +2625,10 @@ static bool spell_mb_isword_class(int cl, win_T *wp)
// Returns true if "p" points to a word character.
// Wide version of spell_iswordp().
-static bool spell_iswordp_w(int *p, win_T *wp)
+static bool spell_iswordp_w(const int *p, const win_T *wp)
+ FUNC_ATTR_NONNULL_ALL
{
- int *s;
+ const int *s;
if (*p < 256 ? wp->w_s->b_spell_ismw[*p]
: (wp->w_s->b_spell_ismw_mb != NULL
@@ -3031,7 +3032,7 @@ void ex_spellrepall(exarg_T *eap)
sub_nlines = 0;
curwin->w_cursor.lnum = 0;
while (!got_int) {
- if (do_search(NULL, '/', frompat, 1L, SEARCH_KEEP, NULL, NULL) == 0
+ if (do_search(NULL, '/', frompat, 1L, SEARCH_KEEP, NULL) == 0
|| u_save_cursor() == FAIL) {
break;
}
@@ -4116,7 +4117,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
&& goodword_ends) {
int l;
- l = MB_PTR2LEN(fword + sp->ts_fidx);
+ l = utfc_ptr2len(fword + sp->ts_fidx);
if (fword_ends) {
// Copy the skipped character to preword.
memmove(preword + sp->ts_prewordlen,
@@ -4262,7 +4263,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
// Correct ts_fidx for the byte length of the
// character (we didn't check that before).
sp->ts_fidx = sp->ts_fcharstart
- + MB_PTR2LEN(fword + sp->ts_fcharstart);
+ + utfc_ptr2len(fword + sp->ts_fcharstart);
// For changing a composing character adjust
// the score from SCORE_SUBST to
@@ -4361,7 +4362,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
// a bit illogical for soundfold tree but it does give better
// results.
c = utf_ptr2char(fword + sp->ts_fidx);
- stack[depth].ts_fidx += MB_PTR2LEN(fword + sp->ts_fidx);
+ stack[depth].ts_fidx += utfc_ptr2len(fword + sp->ts_fidx);
if (utf_iscomposing(c)) {
stack[depth].ts_score -= SCORE_DEL - SCORE_DELCOMP;
} else if (c == utf_ptr2char(fword + stack[depth].ts_fidx)) {
@@ -4531,9 +4532,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
case STATE_UNSWAP:
// Undo the STATE_SWAP swap: "21" -> "12".
p = fword + sp->ts_fidx;
- n = MB_PTR2LEN(p);
+ n = utfc_ptr2len(p);
c = utf_ptr2char(p + n);
- memmove(p + MB_PTR2LEN(p + n), p, n);
+ memmove(p + utfc_ptr2len(p + n), p, n);
utf_char2bytes(c, p);
FALLTHROUGH;
@@ -4587,11 +4588,11 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
case STATE_UNSWAP3:
// Undo STATE_SWAP3: "321" -> "123"
p = fword + sp->ts_fidx;
- n = MB_PTR2LEN(p);
+ n = utfc_ptr2len(p);
c2 = utf_ptr2char(p + n);
- fl = MB_PTR2LEN(p + n);
+ fl = utfc_ptr2len(p + n);
c = utf_ptr2char(p + n + fl);
- tl = MB_PTR2LEN(p + n + fl);
+ tl = utfc_ptr2len(p + n + fl);
memmove(p + fl + tl, p, n);
utf_char2bytes(c, p);
utf_char2bytes(c2, p + tl);
@@ -4635,10 +4636,10 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
case STATE_UNROT3L:
// Undo ROT3L: "231" -> "123"
p = fword + sp->ts_fidx;
- n = MB_PTR2LEN(p);
- n += MB_PTR2LEN(p + n);
+ n = utfc_ptr2len(p);
+ n += utfc_ptr2len(p + n);
c = utf_ptr2char(p + n);
- tl = MB_PTR2LEN(p + n);
+ tl = utfc_ptr2len(p + n);
memmove(p + tl, p, n);
utf_char2bytes(c, p);
@@ -4673,9 +4674,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
// Undo ROT3R: "312" -> "123"
p = fword + sp->ts_fidx;
c = utf_ptr2char(p);
- tl = MB_PTR2LEN(p);
- n = MB_PTR2LEN(p + tl);
- n += MB_PTR2LEN(p + tl + n);
+ tl = utfc_ptr2len(p);
+ n = utfc_ptr2len(p + tl);
+ n += utfc_ptr2len(p + tl + n);
memmove(p, p + tl, n);
utf_char2bytes(c, p + n);
diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c
index 01daafa09e..4fac001bc5 100644
--- a/src/nvim/spellfile.c
+++ b/src/nvim/spellfile.c
@@ -265,6 +265,8 @@
// follow; never used in prefix tree
#define BY_SPECIAL BY_FLAGS2 // highest special byte value
+#define ZERO_FLAG 65009 // used when flag is zero: "0"
+
// Flags used in .spl file for soundsalike flags.
#define SAL_F0LLOWUP 1
#define SAL_COLLAPSE 2
@@ -1787,7 +1789,7 @@ spell_reload_one (
bool didit = false;
for (slang = first_lang; slang != NULL; slang = slang->sl_next) {
- if (path_full_compare(fname, slang->sl_fname, false) == kEqualFiles) {
+ if (path_full_compare(fname, slang->sl_fname, false, true) == kEqualFiles) {
slang_clear(slang);
if (spell_load_file(fname, NULL, slang, false) == NULL)
// reloading failed, clear the language
@@ -2783,6 +2785,7 @@ static unsigned affitem2flag(int flagtype, char_u *item, char_u *fname, int lnum
}
// Get one affix name from "*pp" and advance the pointer.
+// Returns ZERO_FLAG for "0".
// Returns zero for an error, still advances the pointer then.
static unsigned get_affitem(int flagtype, char_u **pp)
{
@@ -2794,6 +2797,9 @@ static unsigned get_affitem(int flagtype, char_u **pp)
return 0;
}
res = getdigits_int(pp, true, 0);
+ if (res == 0) {
+ res = ZERO_FLAG;
+ }
} else {
res = mb_ptr2char_adv((const char_u **)pp);
if (flagtype == AFT_LONG || (flagtype == AFT_CAPLONG
@@ -2915,10 +2921,15 @@ static bool flag_in_afflist(int flagtype, char_u *afflist, unsigned flag)
int digits = getdigits_int(&p, true, 0);
assert(digits >= 0);
n = (unsigned int)digits;
- if (n == flag)
+ if (n == 0) {
+ n = ZERO_FLAG;
+ }
+ if (n == flag) {
return true;
- if (*p != NUL) // skip over comma
- ++p;
+ }
+ if (*p != NUL) { // skip over comma
+ p++;
+ }
}
break;
}
@@ -4719,7 +4730,8 @@ static void spell_make_sugfile(spellinfo_T *spin, char_u *wfname)
// of the code for the soundfolding stuff.
// It might have been done already by spell_reload_one().
for (slang = first_lang; slang != NULL; slang = slang->sl_next) {
- if (path_full_compare(wfname, slang->sl_fname, false) == kEqualFiles) {
+ if (path_full_compare(wfname, slang->sl_fname, false, true)
+ == kEqualFiles) {
break;
}
}
diff --git a/src/nvim/state.c b/src/nvim/state.c
index 81bc078a88..b195c1d96b 100644
--- a/src/nvim/state.c
+++ b/src/nvim/state.c
@@ -156,7 +156,7 @@ char *get_mode(void)
buf[0] = 'n';
if (finish_op) {
buf[1] = 'o';
- // to be able to detect force-linewise/blockwise/characterwise operations
+ // to be able to detect force-linewise/blockwise/charwise operations
buf[2] = (char)motion_force;
} else if (restart_edit == 'I' || restart_edit == 'R'
|| restart_edit == 'V') {
diff --git a/src/nvim/strings.c b/src/nvim/strings.c
index 3ba9354c67..2f5491fda5 100644
--- a/src/nvim/strings.c
+++ b/src/nvim/strings.c
@@ -953,11 +953,17 @@ int vim_vsnprintf_typval(
- mb_string2cells((char_u *)str_arg));
}
if (precision) {
- const char *p1 = str_arg;
- for (size_t i = 0; i < precision && *p1; i++) {
- p1 += mb_ptr2len((const char_u *)p1);
+ char_u *p1;
+ size_t i = 0;
+
+ for (p1 = (char_u *)str_arg; *p1;
+ p1 += mb_ptr2len(p1)) {
+ i += (size_t)utf_ptr2cells(p1);
+ if (i > precision) {
+ break;
+ }
}
- str_arg_l = precision = (size_t)(p1 - str_arg);
+ str_arg_l = precision = (size_t)(p1 - (char_u *)str_arg);
}
}
break;
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index 5a61238d8c..61ee225eba 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -116,10 +116,10 @@ static int include_link = 0; /* when 2 include "nvim/link" and "clear" */
/// following names, separated by commas (but no spaces!).
static char *(hl_name_table[]) =
{ "bold", "standout", "underline", "undercurl",
- "italic", "reverse", "inverse", "strikethrough", "NONE" };
+ "italic", "reverse", "inverse", "strikethrough", "nocombine", "NONE" };
static int hl_attr_table[] =
{ HL_BOLD, HL_STANDOUT, HL_UNDERLINE, HL_UNDERCURL, HL_ITALIC, HL_INVERSE,
- HL_INVERSE, HL_STRIKETHROUGH, 0 };
+ HL_INVERSE, HL_STRIKETHROUGH, HL_NOCOMBINE, 0 };
// The patterns that are being searched for are stored in a syn_pattern.
// A match item consists of one pattern.
@@ -2460,11 +2460,8 @@ update_si_end(
int force /* when TRUE overrule a previous end */
)
{
- lpos_T startpos;
- lpos_T endpos;
lpos_T hl_endpos;
lpos_T end_endpos;
- int end_idx;
/* return quickly for a keyword */
if (sip->si_idx < 0)
@@ -2480,9 +2477,12 @@ update_si_end(
* We need to find the end of the region. It may continue in the next
* line.
*/
- end_idx = 0;
- startpos.lnum = current_lnum;
- startpos.col = startcol;
+ int end_idx = 0;
+ lpos_T startpos = {
+ .lnum = current_lnum,
+ .col = startcol,
+ };
+ lpos_T endpos = { 0 };
find_endpos(sip->si_idx, &startpos, &endpos, &hl_endpos,
&(sip->si_flags), &end_endpos, &end_idx, sip->si_extmatch);
@@ -7316,7 +7316,7 @@ static void set_hl_attr(int idx)
sgp->sg_attr = hl_get_syn_attr(idx+1, at_en);
- // a cursor style uses this syn_id, make sure its atribute is updated.
+ // a cursor style uses this syn_id, make sure its attribute is updated.
if (cursor_mode_uses_syn_id(idx+1)) {
ui_mode_info_set();
}
@@ -7569,8 +7569,8 @@ void highlight_changed(void)
{
int id;
char_u userhl[30]; // use 30 to avoid compiler warning
- int id_SNC = -1;
int id_S = -1;
+ int id_SNC = 0;
int hlcnt;
need_highlight_changed = FALSE;
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index 91f3da1793..3629b37c32 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -51,17 +51,21 @@
* Structure to hold pointers to various items in a tag line.
*/
typedef struct tag_pointers {
- /* filled in by parse_tag_line(): */
- char_u *tagname; /* start of tag name (skip "file:") */
- char_u *tagname_end; /* char after tag name */
- char_u *fname; /* first char of file name */
- char_u *fname_end; /* char after file name */
- char_u *command; /* first char of command */
- /* filled in by parse_match(): */
- char_u *command_end; /* first char after command */
- char_u *tag_fname; /* file name of the tags file */
- char_u *tagkind; /* "kind:" value */
- char_u *tagkind_end; /* end of tagkind */
+ // filled in by parse_tag_line():
+ char_u *tagname; // start of tag name (skip "file:")
+ char_u *tagname_end; // char after tag name
+ char_u *fname; // first char of file name
+ char_u *fname_end; // char after file name
+ char_u *command; // first char of command
+ // filled in by parse_match():
+ char_u *command_end; // first char after command
+ char_u *tag_fname; // file name of the tags file. This is used
+ // when 'tr' is set.
+ char_u *tagkind; // "kind:" value
+ char_u *tagkind_end; // end of tagkind
+ char_u *user_data; // user_data string
+ char_u *user_data_end; // end of user_data
+ linenr_T tagline; // "line:" value
} tagptrs_T;
/*
@@ -102,6 +106,10 @@ static char_u *nofile_fname = NULL; /* fname for NOTAGFILE error */
static char_u *bottommsg = (char_u *)N_("E555: at bottom of tag stack");
static char_u *topmsg = (char_u *)N_("E556: at top of tag stack");
+static char_u *recurmsg
+ = (char_u *)N_("E986: cannot modify the tag stack within tagfunc");
+static char_u *tfu_inv_ret_msg
+ = (char_u *)N_("E987: invalid return value from tagfunc");
static char_u *tagmatchname = NULL; /* name of last used tag */
@@ -109,7 +117,12 @@ static char_u *tagmatchname = NULL; /* name of last used tag */
* Tag for preview window is remembered separately, to avoid messing up the
* normal tagstack.
*/
-static taggy_T ptag_entry = { NULL, { { 0, 0, 0 }, 0, 0, NULL }, 0, 0 };
+static taggy_T ptag_entry = { NULL, { { 0, 0, 0 }, 0, 0, NULL }, 0, 0, NULL };
+
+static int tfu_in_use = false; // disallow recursive call of tagfunc
+
+// Used instead of NUL to separate tag fields in the growarrays.
+#define TAG_SEP 0x02
/*
* Jump to tag; handling of tag commands and tag stack
@@ -131,13 +144,13 @@ static taggy_T ptag_entry = { NULL, { { 0, 0, 0 }, 0, 0, NULL }, 0, 0 };
*
* for cscope, returns TRUE if we jumped to tag or aborted, FALSE otherwise
*/
-int
-do_tag (
- char_u *tag, /* tag (pattern) to jump to */
+int
+do_tag(
+ char_u *tag, // tag (pattern) to jump to
int type,
int count,
- int forceit, /* :ta with ! */
- int verbose /* print "tag not found" message */
+ int forceit, // :ta with !
+ int verbose // print "tag not found" message
)
{
taggy_T *tagstack = curwin->w_tagstack;
@@ -148,28 +161,20 @@ do_tag (
int oldtagstackidx = tagstackidx;
int prevtagstackidx = tagstackidx;
int prev_num_matches;
- int new_tag = FALSE;
- int other_name;
- int i, j, k;
- int idx;
+ int new_tag = false;
+ int i;
int ic;
- char_u *p;
- char_u *name;
- int no_regexp = FALSE;
+ int no_regexp = false;
int error_cur_match = 0;
- char_u *command_end;
- int save_pos = FALSE;
+ int save_pos = false;
fmark_T saved_fmark;
- int taglen;
- int jumped_to_tag = FALSE;
- tagptrs_T tagp, tagp2;
+ int jumped_to_tag = false;
int new_num_matches;
char_u **new_matches;
- int attr;
int use_tagstack;
- int skip_msg = FALSE;
- char_u *buf_ffname = curbuf->b_ffname; /* name to use for
- priority computation */
+ int skip_msg = false;
+ char_u *buf_ffname = curbuf->b_ffname; // name for priority computation
+ int use_tfu = 1;
/* remember the matches for the last used tag */
static int num_matches = 0;
@@ -177,19 +182,25 @@ do_tag (
static char_u **matches = NULL;
static int flags;
+ if (tfu_in_use) {
+ EMSG(_(recurmsg));
+ return false;
+ }
+
#ifdef EXITFREE
if (type == DT_FREE) {
/* remove the list of matches */
FreeWild(num_matches, matches);
cs_free_tags();
num_matches = 0;
- return FALSE;
+ return false;
}
#endif
if (type == DT_HELP) {
type = DT_TAG;
- no_regexp = TRUE;
+ no_regexp = true;
+ use_tfu = 0;
}
prev_num_matches = num_matches;
@@ -205,14 +216,15 @@ do_tag (
use_tagstack = false;
new_tag = true;
if (g_do_tagpreview != 0) {
- xfree(ptag_entry.tagname);
+ tagstack_clear_entry(&ptag_entry);
ptag_entry.tagname = vim_strsave(tag);
}
} else {
- if (g_do_tagpreview != 0)
- use_tagstack = FALSE;
- else
- use_tagstack = TRUE;
+ if (g_do_tagpreview != 0) {
+ use_tagstack = false;
+ } else {
+ use_tagstack = true;
+ }
/* new pattern, add to the tag stack */
if (*tag != NUL
@@ -228,7 +240,7 @@ do_tag (
cur_match = ptag_entry.cur_match;
cur_fnum = ptag_entry.cur_fnum;
} else {
- xfree(ptag_entry.tagname);
+ tagstack_clear_entry(&ptag_entry);
ptag_entry.tagname = vim_strsave(tag);
}
} else {
@@ -236,16 +248,18 @@ do_tag (
* If the last used entry is not at the top, delete all tag
* stack entries above it.
*/
- while (tagstackidx < tagstacklen)
- xfree(tagstack[--tagstacklen].tagname);
+ while (tagstackidx < tagstacklen) {
+ tagstack_clear_entry(&tagstack[--tagstacklen]);
+ }
/* if the tagstack is full: remove oldest entry */
if (++tagstacklen > TAGSTACKSIZE) {
tagstacklen = TAGSTACKSIZE;
- xfree(tagstack[0].tagname);
- for (i = 1; i < tagstacklen; ++i)
+ tagstack_clear_entry(&tagstack[0]);
+ for (i = 1; i < tagstacklen; i++) {
tagstack[i - 1] = tagstack[i];
- --tagstackidx;
+ }
+ tagstackidx--;
}
// put the tag name in the tag stack
@@ -253,15 +267,15 @@ do_tag (
curwin->w_tagstacklen = tagstacklen;
- save_pos = TRUE; /* save the cursor position below */
+ save_pos = true; // save the cursor position below
}
- new_tag = TRUE;
+ new_tag = true;
} else {
if (
- g_do_tagpreview != 0 ? ptag_entry.tagname == NULL :
- tagstacklen == 0) {
- /* empty stack */
+ g_do_tagpreview != 0 ? ptag_entry.tagname == NULL :
+ tagstacklen == 0) {
+ // empty stack
EMSG(_(e_tagstack));
goto end_do_tag;
}
@@ -271,7 +285,7 @@ do_tag (
if ((tagstackidx -= count) < 0) {
EMSG(_(bottommsg));
if (tagstackidx + count == 0) {
- /* We did [num]^T from the bottom of the stack */
+ // We did [num]^T from the bottom of the stack
tagstackidx = 0;
goto end_do_tag;
}
@@ -279,7 +293,7 @@ do_tag (
* way to the bottom now.
*/
tagstackidx = 0;
- } else if (tagstackidx >= tagstacklen) { /* count == 0? */
+ } else if (tagstackidx >= tagstacklen) { // count == 0?
EMSG(_(topmsg));
goto end_do_tag;
}
@@ -293,8 +307,8 @@ do_tag (
* file was changed) keep original position in tag stack.
*/
if (buflist_getfile(saved_fmark.fnum, saved_fmark.mark.lnum,
- GETF_SETMARK, forceit) == FAIL) {
- tagstackidx = oldtagstackidx; /* back to old posn */
+ GETF_SETMARK, forceit) == FAIL) {
+ tagstackidx = oldtagstackidx; // back to old posn
goto end_do_tag;
}
/* A BufReadPost autocommand may jump to the '" mark, but
@@ -305,12 +319,12 @@ do_tag (
curwin->w_cursor.lnum = saved_fmark.mark.lnum;
}
curwin->w_cursor.col = saved_fmark.mark.col;
- curwin->w_set_curswant = TRUE;
+ curwin->w_set_curswant = true;
check_cursor();
if ((fdo_flags & FDO_TAG) && old_KeyTyped)
foldOpenCursor();
- /* remove the old list of matches */
+ // remove the old list of matches
FreeWild(num_matches, matches);
cs_free_tags();
num_matches = 0;
@@ -325,8 +339,8 @@ do_tag (
cur_match = ptag_entry.cur_match;
cur_fnum = ptag_entry.cur_fnum;
} else {
- /* ":tag" (no argument): go to newer pattern */
- save_pos = TRUE; /* save the cursor position below */
+ // ":tag" (no argument): go to newer pattern
+ save_pos = true; // save the cursor position below
if ((tagstackidx += count - 1) >= tagstacklen) {
/*
* Beyond the last one, just give an error message and
@@ -335,8 +349,8 @@ do_tag (
*/
tagstackidx = tagstacklen - 1;
EMSG(_(topmsg));
- save_pos = FALSE;
- } else if (tagstackidx < 0) { /* must have been count == 0 */
+ save_pos = false;
+ } else if (tagstackidx < 0) { // must have been count == 0
EMSG(_(bottommsg));
tagstackidx = 0;
goto end_do_tag;
@@ -344,9 +358,9 @@ do_tag (
cur_match = tagstack[tagstackidx].cur_match;
cur_fnum = tagstack[tagstackidx].cur_fnum;
}
- new_tag = TRUE;
- } else { /* go to other matching tag */
- /* Save index for when selection is cancelled. */
+ new_tag = true;
+ } else { // go to other matching tag
+ // Save index for when selection is cancelled.
prevtagstackidx = tagstackidx;
if (g_do_tagpreview != 0) {
@@ -371,7 +385,7 @@ do_tag (
cur_match = MAXCOL - 1;
else if (cur_match < 0) {
EMSG(_("E425: Cannot go before first matching tag"));
- skip_msg = TRUE;
+ skip_msg = true;
cur_match = 0;
cur_fnum = curbuf->b_fnum;
}
@@ -418,15 +432,17 @@ do_tag (
* Repeat searching for tags, when a file has not been found.
*/
for (;; ) {
- /*
- * When desired match not found yet, try to find it (and others).
- */
- if (use_tagstack)
+ int other_name;
+ char_u *name;
+
+ // When desired match not found yet, try to find it (and others).
+ if (use_tagstack) {
name = tagstack[tagstackidx].tagname;
- else if (g_do_tagpreview != 0)
+ } else if (g_do_tagpreview != 0) {
name = ptag_entry.tagname;
- else
+ } else {
name = tag;
+ }
other_name = (tagmatchname == NULL || STRCMP(tagmatchname, name) != 0);
if (new_tag
|| (cur_match >= num_matches && max_num_matches != MAXCOL)
@@ -446,38 +462,49 @@ do_tag (
max_num_matches = cur_match + 1;
}
- /* when the argument starts with '/', use it as a regexp */
+ // when the argument starts with '/', use it as a regexp
if (!no_regexp && *name == '/') {
flags = TAG_REGEXP;
++name;
} else
flags = TAG_NOIC;
- if (type == DT_CSCOPE)
+ if (type == DT_CSCOPE) {
flags = TAG_CSCOPE;
- if (verbose)
+ }
+ if (verbose) {
flags |= TAG_VERBOSE;
+ }
+ if (!use_tfu) {
+ flags |= TAG_NO_TAGFUNC;
+ }
+
if (find_tags(name, &new_num_matches, &new_matches, flags,
- max_num_matches, buf_ffname) == OK
- && new_num_matches < max_num_matches)
- max_num_matches = MAXCOL; /* If less than max_num_matches
- found: all matches found. */
+ max_num_matches, buf_ffname) == OK
+ && new_num_matches < max_num_matches) {
+ max_num_matches = MAXCOL; // If less than max_num_matches
+ // found: all matches found.
+ }
/* If there already were some matches for the same name, move them
* to the start. Avoids that the order changes when using
* ":tnext" and jumping to another file. */
if (!new_tag && !other_name) {
- /* Find the position of each old match in the new list. Need
- * to use parse_match() to find the tag line. */
- idx = 0;
- for (j = 0; j < num_matches; ++j) {
+ int j, k;
+ int idx = 0;
+ tagptrs_T tagp, tagp2;
+
+ // Find the position of each old match in the new list. Need
+ // to use parse_match() to find the tag line.
+ for (j = 0; j < num_matches; j++) {
parse_match(matches[j], &tagp);
for (i = idx; i < new_num_matches; ++i) {
parse_match(new_matches[i], &tagp2);
if (STRCMP(tagp.tagname, tagp2.tagname) == 0) {
- p = new_matches[i];
- for (k = i; k > idx; --k)
+ char_u *p = new_matches[i];
+ for (k = i; k > idx; k--) {
new_matches[k] = new_matches[k - 1];
+ }
new_matches[idx++] = p;
break;
}
@@ -504,304 +531,27 @@ do_tag (
// jump to count'th matching tag.
cur_match = count > 0 ? count - 1 : 0;
} else if (type == DT_SELECT || (type == DT_JUMP && num_matches > 1)) {
- // List all the matching tags.
- // Assume that the first match indicates how long the tags can
- // be, and align the file names to that.
- parse_match(matches[0], &tagp);
- taglen = (int)(tagp.tagname_end - tagp.tagname + 2);
- if (taglen < 18)
- taglen = 18;
- if (taglen > Columns - 25)
- taglen = MAXCOL;
- if (msg_col == 0)
- msg_didout = FALSE; /* overwrite previous message */
- msg_start();
- MSG_PUTS_ATTR(_(" # pri kind tag"), HL_ATTR(HLF_T));
- msg_clr_eos();
- taglen_advance(taglen);
- MSG_PUTS_ATTR(_("file\n"), HL_ATTR(HLF_T));
-
- for (i = 0; i < num_matches && !got_int; i++) {
- parse_match(matches[i], &tagp);
- if (!new_tag && ((g_do_tagpreview != 0 && i == ptag_entry.cur_match)
- || (use_tagstack
- && i == tagstack[tagstackidx].cur_match))) {
- *IObuff = '>';
- } else {
- *IObuff = ' ';
- }
- vim_snprintf((char *)IObuff + 1, IOSIZE - 1, "%2d %s ", i + 1,
- mt_names[matches[i][0] & MT_MASK]);
- msg_puts((const char *)IObuff);
- if (tagp.tagkind != NULL) {
- msg_outtrans_len(tagp.tagkind,
- (int)(tagp.tagkind_end - tagp.tagkind));
- }
- msg_advance(13);
- msg_outtrans_len_attr(tagp.tagname,
- (int)(tagp.tagname_end - tagp.tagname),
- HL_ATTR(HLF_T));
- msg_putchar(' ');
- taglen_advance(taglen);
-
- /* Find out the actual file name. If it is long, truncate
- * it and put "..." in the middle */
- p = tag_full_fname(&tagp);
- msg_puts_long_attr(p, HL_ATTR(HLF_D));
- xfree(p);
-
- if (msg_col > 0)
- msg_putchar('\n');
- if (got_int)
- break;
- msg_advance(15);
-
- /* print any extra fields */
- command_end = tagp.command_end;
- if (command_end != NULL) {
- p = command_end + 3;
- while (*p && *p != '\r' && *p != '\n') {
- while (*p == TAB)
- ++p;
-
- /* skip "file:" without a value (static tag) */
- if (STRNCMP(p, "file:", 5) == 0
- && ascii_isspace(p[5])) {
- p += 5;
- continue;
- }
- /* skip "kind:<kind>" and "<kind>" */
- if (p == tagp.tagkind
- || (p + 5 == tagp.tagkind
- && STRNCMP(p, "kind:", 5) == 0)) {
- p = tagp.tagkind_end;
- continue;
- }
- // print all other extra fields
- attr = HL_ATTR(HLF_CM);
- while (*p && *p != '\r' && *p != '\n') {
- if (msg_col + ptr2cells(p) >= Columns) {
- msg_putchar('\n');
- if (got_int)
- break;
- msg_advance(15);
- }
- p = msg_outtrans_one(p, attr);
- if (*p == TAB) {
- msg_puts_attr(" ", attr);
- break;
- }
- if (*p == ':')
- attr = 0;
- }
- }
- if (msg_col > 15) {
- msg_putchar('\n');
- if (got_int)
- break;
- msg_advance(15);
- }
- } else {
- for (p = tagp.command;
- *p && *p != '\r' && *p != '\n'; ++p)
- ;
- command_end = p;
- }
-
- /*
- * Put the info (in several lines) at column 15.
- * Don't display "/^" and "?^".
- */
- p = tagp.command;
- if (*p == '/' || *p == '?') {
- ++p;
- if (*p == '^')
- ++p;
- }
- /* Remove leading whitespace from pattern */
- while (p != command_end && ascii_isspace(*p))
- ++p;
-
- while (p != command_end) {
- if (msg_col + (*p == TAB ? 1 : ptr2cells(p)) > Columns)
- msg_putchar('\n');
- if (got_int)
- break;
- msg_advance(15);
-
- // Skip backslash used for escaping a command char or a backslash.
- if (*p == '\\' && (*(p + 1) == *tagp.command
- || *(p + 1) == '\\')) {
- ++p;
- }
-
- if (*p == TAB) {
- msg_putchar(' ');
- ++p;
- } else
- p = msg_outtrans_one(p, 0);
-
- /* don't display the "$/;\"" and "$?;\"" */
- if (p == command_end - 2 && *p == '$'
- && *(p + 1) == *tagp.command)
- break;
- /* don't display matching '/' or '?' */
- if (p == command_end - 1 && *p == *tagp.command
- && (*p == '/' || *p == '?'))
- break;
- }
- if (msg_col)
- msg_putchar('\n');
- os_breakcheck();
- }
- if (got_int) {
- got_int = false; // only stop the listing
- }
+ print_tag_list(new_tag, use_tagstack, num_matches, matches);
ask_for_selection = true;
} else if (type == DT_LTAG) {
- list_T *list;
- char_u tag_name[128 + 1];
- char_u *fname;
- char_u *cmd;
-
- /*
- * Add the matching tags to the location list for the current
- * window.
- */
-
- fname = xmalloc(MAXPATHL + 1);
- cmd = xmalloc(CMDBUFFSIZE + 1);
- list = tv_list_alloc(num_matches);
-
- for (i = 0; i < num_matches; ++i) {
- int len, cmd_len;
- long lnum;
- dict_T *dict;
-
- parse_match(matches[i], &tagp);
-
- /* Save the tag name */
- len = (int)(tagp.tagname_end - tagp.tagname);
- if (len > 128)
- len = 128;
- STRLCPY(tag_name, tagp.tagname, len + 1);
-
- /* Save the tag file name */
- p = tag_full_fname(&tagp);
- STRLCPY(fname, p, MAXPATHL + 1);
- xfree(p);
-
- /*
- * Get the line number or the search pattern used to locate
- * the tag.
- */
- lnum = 0;
- if (isdigit(*tagp.command))
- /* Line number is used to locate the tag */
- lnum = atol((char *)tagp.command);
- else {
- char_u *cmd_start, *cmd_end;
-
- /* Search pattern is used to locate the tag */
-
- /* Locate the end of the command */
- cmd_start = tagp.command;
- cmd_end = tagp.command_end;
- if (cmd_end == NULL) {
- for (p = tagp.command;
- *p && *p != '\r' && *p != '\n'; ++p)
- ;
- cmd_end = p;
- }
-
- /*
- * Now, cmd_end points to the character after the
- * command. Adjust it to point to the last
- * character of the command.
- */
- cmd_end--;
-
- /*
- * Skip the '/' and '?' characters at the
- * beginning and end of the search pattern.
- */
- if (*cmd_start == '/' || *cmd_start == '?')
- cmd_start++;
-
- if (*cmd_end == '/' || *cmd_end == '?')
- cmd_end--;
-
- len = 0;
- cmd[0] = NUL;
-
- /*
- * If "^" is present in the tag search pattern, then
- * copy it first.
- */
- if (*cmd_start == '^') {
- STRCPY(cmd, "^");
- cmd_start++;
- len++;
- }
-
- /*
- * Precede the tag pattern with \V to make it very
- * nomagic.
- */
- STRCAT(cmd, "\\V");
- len += 2;
-
- cmd_len = (int)(cmd_end - cmd_start + 1);
- if (cmd_len > (CMDBUFFSIZE - 5))
- cmd_len = CMDBUFFSIZE - 5;
- STRNCAT(cmd, cmd_start, cmd_len);
- len += cmd_len;
-
- if (cmd[len - 1] == '$') {
- /*
- * Replace '$' at the end of the search pattern
- * with '\$'
- */
- cmd[len - 1] = '\\';
- cmd[len] = '$';
- len++;
- }
-
- cmd[len] = NUL;
- }
-
- dict = tv_dict_alloc();
- tv_list_append_dict(list, dict);
-
- tv_dict_add_str(dict, S_LEN("text"), (const char *)tag_name);
- tv_dict_add_str(dict, S_LEN("filename"), (const char *)fname);
- tv_dict_add_nr(dict, S_LEN("lnum"), lnum);
- if (lnum == 0) {
- tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cmd);
- }
+ if (add_llist_tags(tag, num_matches, matches) == FAIL) {
+ goto end_do_tag;
}
- vim_snprintf((char *)IObuff, IOSIZE, "ltag %s", tag);
- set_errorlist(curwin, list, ' ', IObuff, NULL);
-
- tv_list_free(list);
- xfree(fname);
- xfree(cmd);
-
- cur_match = 0; /* Jump to the first tag */
+ cur_match = 0; // Jump to the first tag
}
if (ask_for_selection) {
// Ask to select a tag from the list.
i = prompt_for_number(NULL);
if (i <= 0 || i > num_matches || got_int) {
- /* no valid choice: don't change anything */
+ // no valid choice: don't change anything
if (use_tagstack) {
tagstack[tagstackidx].fmark = saved_fmark;
tagstackidx = prevtagstackidx;
}
cs_free_tags();
- jumped_to_tag = TRUE;
+ jumped_to_tag = true;
break;
}
cur_match = i - 1;
@@ -817,14 +567,25 @@ do_tag (
EMSG(_("E427: There is only one matching tag"));
else
EMSG(_("E428: Cannot go beyond last matching tag"));
- skip_msg = TRUE;
+ skip_msg = true;
}
cur_match = num_matches - 1;
}
if (use_tagstack) {
+ tagptrs_T tagp2;
+
tagstack[tagstackidx].cur_match = cur_match;
tagstack[tagstackidx].cur_fnum = cur_fnum;
- ++tagstackidx;
+
+ // store user-provided data originating from tagfunc
+ if (use_tfu && parse_match(matches[cur_match], &tagp2) == OK
+ && tagp2.user_data) {
+ XFREE_CLEAR(tagstack[tagstackidx].user_data);
+ tagstack[tagstackidx].user_data = vim_strnsave(
+ tagp2.user_data, tagp2.user_data_end - tagp2.user_data);
+ }
+
+ tagstackidx++;
} else if (g_do_tagpreview != 0) {
ptag_entry.cur_match = cur_match;
ptag_entry.cur_fnum = cur_fnum;
@@ -843,13 +604,14 @@ do_tag (
&& type != DT_CSCOPE
&& (num_matches > 1 || ic)
&& !skip_msg) {
- /* Give an indication of the number of matching tags */
- sprintf((char *)IObuff, _("tag %d of %d%s"),
- cur_match + 1,
- num_matches,
- max_num_matches != MAXCOL ? _(" or more") : "");
- if (ic)
+ // Give an indication of the number of matching tags
+ snprintf((char *)IObuff, sizeof(IObuff), _("tag %d of %d%s"),
+ cur_match + 1,
+ num_matches,
+ max_num_matches != MAXCOL ? _(" or more") : "");
+ if (ic) {
STRCAT(IObuff, _(" Using tag with different case!"));
+ }
if ((num_matches > prev_num_matches || new_tag)
&& num_matches > 1) {
if (ic) {
@@ -867,7 +629,7 @@ do_tag (
}
}
- /* Let the SwapExists event know what tag we are jumping to. */
+ // Let the SwapExists event know what tag we are jumping to.
vim_snprintf((char *)IObuff, IOSIZE, ":ta %s\r", name);
set_vim_var_string(VV_SWAPCOMMAND, (char *) IObuff, -1);
@@ -879,7 +641,7 @@ do_tag (
set_vim_var_string(VV_SWAPCOMMAND, NULL, -1);
if (i == NOTAGFILE) {
- /* File not found: try again with another matching tag */
+ // File not found: try again with another matching tag
if ((type == DT_PREV && cur_match > 0)
|| ((type == DT_TAG || type == DT_NEXT
|| type == DT_FIRST)
@@ -902,22 +664,352 @@ do_tag (
* tagstackidx is still valid. */
if (use_tagstack && tagstackidx > curwin->w_tagstacklen)
tagstackidx = curwin->w_tagstackidx;
- jumped_to_tag = TRUE;
+ jumped_to_tag = true;
}
}
break;
}
end_do_tag:
- /* Only store the new index when using the tagstack and it's valid. */
- if (use_tagstack && tagstackidx <= curwin->w_tagstacklen)
+ // Only store the new index when using the tagstack and it's valid.
+ if (use_tagstack && tagstackidx <= curwin->w_tagstacklen) {
curwin->w_tagstackidx = tagstackidx;
+ }
postponed_split = 0; // don't split next time
g_do_tagpreview = 0; // don't do tag preview next time
return jumped_to_tag;
}
+//
+// List all the matching tags.
+//
+static void
+print_tag_list(
+ int new_tag,
+ int use_tagstack,
+ int num_matches,
+ char_u **matches)
+{
+ taggy_T *tagstack = curwin->w_tagstack;
+ int tagstackidx = curwin->w_tagstackidx;
+ int i;
+ char_u *p;
+ char_u *command_end;
+ tagptrs_T tagp;
+ int taglen;
+ int attr;
+
+ // Assume that the first match indicates how long the tags can
+ // be, and align the file names to that.
+ parse_match(matches[0], &tagp);
+ taglen = (int)(tagp.tagname_end - tagp.tagname + 2);
+ if (taglen < 18) {
+ taglen = 18;
+ }
+ if (taglen > Columns - 25) {
+ taglen = MAXCOL;
+ }
+ if (msg_col == 0) {
+ msg_didout = false; // overwrite previous message
+ }
+ msg_start();
+ msg_puts_attr(_(" # pri kind tag"), HL_ATTR(HLF_T));
+ msg_clr_eos();
+ taglen_advance(taglen);
+ msg_puts_attr(_("file\n"), HL_ATTR(HLF_T));
+
+ for (i = 0; i < num_matches && !got_int; i++) {
+ parse_match(matches[i], &tagp);
+ if (!new_tag && (
+ (g_do_tagpreview != 0
+ && i == ptag_entry.cur_match)
+ || (use_tagstack
+ && i == tagstack[tagstackidx].cur_match))) {
+ *IObuff = '>';
+ } else {
+ *IObuff = ' ';
+ }
+ vim_snprintf((char *)IObuff + 1, IOSIZE - 1,
+ "%2d %s ", i + 1,
+ mt_names[matches[i][0] & MT_MASK]);
+ msg_puts((char *)IObuff);
+ if (tagp.tagkind != NULL) {
+ msg_outtrans_len(tagp.tagkind,
+ (int)(tagp.tagkind_end - tagp.tagkind));
+ }
+ msg_advance(13);
+ msg_outtrans_len_attr(tagp.tagname,
+ (int)(tagp.tagname_end - tagp.tagname),
+ HL_ATTR(HLF_T));
+ msg_putchar(' ');
+ taglen_advance(taglen);
+
+ // Find out the actual file name. If it is long, truncate
+ // it and put "..." in the middle
+ p = tag_full_fname(&tagp);
+ if (p != NULL) {
+ msg_outtrans_attr(p, HL_ATTR(HLF_D));
+ XFREE_CLEAR(p);
+ }
+ if (msg_col > 0) {
+ msg_putchar('\n');
+ }
+ if (got_int) {
+ break;
+ }
+ msg_advance(15);
+
+ // print any extra fields
+ command_end = tagp.command_end;
+ if (command_end != NULL) {
+ p = command_end + 3;
+ while (*p && *p != '\r' && *p != '\n') {
+ while (*p == TAB) {
+ p++;
+ }
+
+ // skip "file:" without a value (static tag)
+ if (STRNCMP(p, "file:", 5) == 0 && ascii_isspace(p[5])) {
+ p += 5;
+ continue;
+ }
+ // skip "kind:<kind>" and "<kind>"
+ if (p == tagp.tagkind
+ || (p + 5 == tagp.tagkind
+ && STRNCMP(p, "kind:", 5) == 0)) {
+ p = tagp.tagkind_end;
+ continue;
+ }
+ // print all other extra fields
+ attr = HL_ATTR(HLF_CM);
+ while (*p && *p != '\r' && *p != '\n') {
+ if (msg_col + ptr2cells(p) >= Columns) {
+ msg_putchar('\n');
+ if (got_int) {
+ break;
+ }
+ msg_advance(15);
+ }
+ p = msg_outtrans_one(p, attr);
+ if (*p == TAB) {
+ msg_puts_attr(" ", attr);
+ break;
+ }
+ if (*p == ':') {
+ attr = 0;
+ }
+ }
+ }
+ if (msg_col > 15) {
+ msg_putchar('\n');
+ if (got_int) {
+ break;
+ }
+ msg_advance(15);
+ }
+ } else {
+ for (p = tagp.command;
+ *p && *p != '\r' && *p != '\n';
+ p++) {
+ }
+ command_end = p;
+ }
+
+ // Put the info (in several lines) at column 15.
+ // Don't display "/^" and "?^".
+ p = tagp.command;
+ if (*p == '/' || *p == '?') {
+ p++;
+ if (*p == '^') {
+ p++;
+ }
+ }
+ // Remove leading whitespace from pattern
+ while (p != command_end && ascii_isspace(*p)) {
+ p++;
+ }
+
+ while (p != command_end) {
+ if (msg_col + (*p == TAB ? 1 : ptr2cells(p)) > Columns) {
+ msg_putchar('\n');
+ }
+ if (got_int) {
+ break;
+ }
+ msg_advance(15);
+
+ // skip backslash used for escaping a command char or
+ // a backslash
+ if (*p == '\\' && (*(p + 1) == *tagp.command
+ || *(p + 1) == '\\')) {
+ p++;
+ }
+
+ if (*p == TAB) {
+ msg_putchar(' ');
+ p++;
+ } else {
+ p = msg_outtrans_one(p, 0);
+ }
+
+ // don't display the "$/;\"" and "$?;\""
+ if (p == command_end - 2 && *p == '$'
+ && *(p + 1) == *tagp.command) {
+ break;
+ }
+ // don't display matching '/' or '?'
+ if (p == command_end - 1 && *p == *tagp.command
+ && (*p == '/' || *p == '?')) {
+ break;
+ }
+ }
+ if (msg_col) {
+ msg_putchar('\n');
+ }
+ os_breakcheck();
+ }
+ if (got_int) {
+ got_int = false; // only stop the listing
+ }
+}
+
+//
+// Add the matching tags to the location list for the current
+// window.
+//
+static int
+add_llist_tags(
+ char_u *tag,
+ int num_matches,
+ char_u **matches)
+{
+ list_T *list;
+ char_u tag_name[128 + 1];
+ char_u *fname;
+ char_u *cmd;
+ int i;
+ char_u *p;
+ tagptrs_T tagp;
+
+ fname = xmalloc(MAXPATHL + 1);
+ cmd = xmalloc(CMDBUFFSIZE + 1);
+ list = tv_list_alloc(0);
+
+ for (i = 0; i < num_matches; i++) {
+ int len, cmd_len;
+ long lnum;
+ dict_T *dict;
+
+ parse_match(matches[i], &tagp);
+
+ // Save the tag name
+ len = (int)(tagp.tagname_end - tagp.tagname);
+ if (len > 128) {
+ len = 128;
+ }
+ xstrlcpy((char *)tag_name, (const char *)tagp.tagname, len);
+ tag_name[len] = NUL;
+
+ // Save the tag file name
+ p = tag_full_fname(&tagp);
+ if (p == NULL) {
+ continue;
+ }
+ xstrlcpy((char *)fname, (const char *)p, MAXPATHL);
+ XFREE_CLEAR(p);
+
+ // Get the line number or the search pattern used to locate
+ // the tag.
+ lnum = 0;
+ if (isdigit(*tagp.command)) {
+ // Line number is used to locate the tag
+ lnum = atol((char *)tagp.command);
+ } else {
+ char_u *cmd_start, *cmd_end;
+
+ // Search pattern is used to locate the tag
+
+ // Locate the end of the command
+ cmd_start = tagp.command;
+ cmd_end = tagp.command_end;
+ if (cmd_end == NULL) {
+ for (p = tagp.command;
+ *p && *p != '\r' && *p != '\n'; p++) {
+ }
+ cmd_end = p;
+ }
+
+ // Now, cmd_end points to the character after the
+ // command. Adjust it to point to the last
+ // character of the command.
+ cmd_end--;
+
+ // Skip the '/' and '?' characters at the
+ // beginning and end of the search pattern.
+ if (*cmd_start == '/' || *cmd_start == '?') {
+ cmd_start++;
+ }
+
+ if (*cmd_end == '/' || *cmd_end == '?') {
+ cmd_end--;
+ }
+
+ len = 0;
+ cmd[0] = NUL;
+
+ // If "^" is present in the tag search pattern, then
+ // copy it first.
+ if (*cmd_start == '^') {
+ STRCPY(cmd, "^");
+ cmd_start++;
+ len++;
+ }
+
+ // Precede the tag pattern with \V to make it very
+ // nomagic.
+ STRCAT(cmd, "\\V");
+ len += 2;
+
+ cmd_len = (int)(cmd_end - cmd_start + 1);
+ if (cmd_len > (CMDBUFFSIZE - 5)) {
+ cmd_len = CMDBUFFSIZE - 5;
+ }
+ xstrlcat((char *)cmd, (char *)cmd_start, cmd_len);
+ len += cmd_len;
+
+ if (cmd[len - 1] == '$') {
+ // Replace '$' at the end of the search pattern
+ // with '\$'
+ cmd[len - 1] = '\\';
+ cmd[len] = '$';
+ len++;
+ }
+
+ cmd[len] = NUL;
+ }
+
+ dict = tv_dict_alloc();
+ tv_list_append_dict(list, dict);
+
+ tv_dict_add_str(dict, S_LEN("text"), (const char *)tag_name);
+ tv_dict_add_str(dict, S_LEN("filename"), (const char *)fname);
+ tv_dict_add_nr(dict, S_LEN("lnum"), lnum);
+ if (lnum == 0) {
+ tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cmd);
+ }
+ }
+
+ vim_snprintf((char *)IObuff, IOSIZE, "ltag %s", tag);
+ set_errorlist(curwin, list, ' ', IObuff, NULL);
+
+ tv_list_free(list);
+ XFREE_CLEAR(fname);
+ XFREE_CLEAR(cmd);
+
+ return OK;
+}
+
/*
* Free cached tags.
*/
@@ -1029,6 +1121,210 @@ static void prepare_pats(pat_T *pats, int has_re)
pats->regmatch.regprog = NULL;
}
+//
+// Call the user-defined function to generate a list of tags used by
+// find_tags().
+//
+// Return OK if at least 1 tag has been successfully found,
+// NOTDONE if the function returns v:null, and FAIL otherwise.
+//
+static int find_tagfunc_tags(
+ char_u *pat, // pattern supplied to the user-defined function
+ garray_T *ga, // the tags will be placed here
+ int *match_count, // here the number of tags found will be placed
+ int flags, // flags from find_tags (TAG_*)
+ char_u *buf_ffname) // name of buffer for priority
+{
+ pos_T save_pos;
+ list_T *taglist;
+ int ntags = 0;
+ int result = FAIL;
+ typval_T args[4];
+ typval_T rettv;
+ char_u flagString[3];
+ dict_T *d;
+ taggy_T *tag = &curwin->w_tagstack[curwin->w_tagstackidx];
+
+ if (*curbuf->b_p_tfu == NUL) {
+ return FAIL;
+ }
+
+ args[0].v_type = VAR_STRING;
+ args[0].vval.v_string = pat;
+ args[1].v_type = VAR_STRING;
+ args[1].vval.v_string = flagString;
+
+ // create 'info' dict argument
+ d = tv_dict_alloc();
+ if (tag->user_data != NULL) {
+ tv_dict_add_str(d, S_LEN("user_data"), (const char *)tag->user_data);
+ }
+ if (buf_ffname != NULL) {
+ tv_dict_add_str(d, S_LEN("buf_ffname"), (const char *)buf_ffname);
+ }
+
+ d->dv_refcount++;
+ args[2].v_type = VAR_DICT;
+ args[2].vval.v_dict = d;
+
+ args[3].v_type = VAR_UNKNOWN;
+
+ vim_snprintf((char *)flagString, sizeof(flagString),
+ "%s%s",
+ g_tag_at_cursor ? "c": "",
+ flags & TAG_INS_COMP ? "i": "");
+
+ save_pos = curwin->w_cursor;
+ result = call_vim_function(curbuf->b_p_tfu, 3, args, &rettv);
+ curwin->w_cursor = save_pos; // restore the cursor position
+ d->dv_refcount--;
+
+ if (result == FAIL) {
+ return FAIL;
+ }
+ if (rettv.v_type == VAR_SPECIAL && rettv.vval.v_number == VV_NULL) {
+ tv_clear(&rettv);
+ return NOTDONE;
+ }
+ if (rettv.v_type != VAR_LIST || !rettv.vval.v_list) {
+ tv_clear(&rettv);
+ EMSG(_(tfu_inv_ret_msg));
+ return FAIL;
+ }
+ taglist = rettv.vval.v_list;
+
+ TV_LIST_ITER_CONST(taglist, li, {
+ char_u *res_name;
+ char_u *res_fname;
+ char_u *res_cmd;
+ char_u *res_kind;
+ int has_extra = 0;
+ int name_only = flags & TAG_NAMES;
+
+ if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT) {
+ EMSG(_(tfu_inv_ret_msg));
+ break;
+ }
+
+ size_t len = 2;
+ res_name = NULL;
+ res_fname = NULL;
+ res_cmd = NULL;
+ res_kind = NULL;
+
+ TV_DICT_ITER(TV_LIST_ITEM_TV(li)->vval.v_dict, di, {
+ const char_u *dict_key = di->di_key;
+ typval_T *tv = &di->di_tv;
+
+ if (tv->v_type != VAR_STRING || tv->vval.v_string == NULL) {
+ continue;
+ }
+
+ len += STRLEN(tv->vval.v_string) + 1; // Space for "\tVALUE"
+ if (!STRCMP(dict_key, "name")) {
+ res_name = tv->vval.v_string;
+ continue;
+ }
+ if (!STRCMP(dict_key, "filename")) {
+ res_fname = tv->vval.v_string;
+ continue;
+ }
+ if (!STRCMP(dict_key, "cmd")) {
+ res_cmd = tv->vval.v_string;
+ continue;
+ }
+ has_extra = 1;
+ if (!STRCMP(dict_key, "kind")) {
+ res_kind = tv->vval.v_string;
+ continue;
+ }
+ // Other elements will be stored as "\tKEY:VALUE"
+ // Allocate space for the key and the colon
+ len += STRLEN(dict_key) + 1;
+ });
+
+ if (has_extra) {
+ len += 2; // need space for ;"
+ }
+
+ if (!res_name || !res_fname || !res_cmd) {
+ EMSG(_(tfu_inv_ret_msg));
+ break;
+ }
+
+ char_u *const mfp = name_only ? vim_strsave(res_name) : xmalloc(len + 2);
+
+ if (!name_only) {
+ char_u *p = mfp;
+
+ *p++ = MT_GL_OTH + 1; // mtt
+ *p++ = TAG_SEP; // no tag file name
+
+ STRCPY(p, res_name);
+ p += STRLEN(p);
+
+ *p++ = TAB;
+ STRCPY(p, res_fname);
+ p += STRLEN(p);
+
+ *p++ = TAB;
+ STRCPY(p, res_cmd);
+ p += STRLEN(p);
+
+ if (has_extra) {
+ STRCPY(p, ";\"");
+ p += STRLEN(p);
+
+ if (res_kind) {
+ *p++ = TAB;
+ STRCPY(p, res_kind);
+ p += STRLEN(p);
+ }
+
+ TV_DICT_ITER(TV_LIST_ITEM_TV(li)->vval.v_dict, di, {
+ const char_u *dict_key = di->di_key;
+ typval_T *tv = &di->di_tv;
+ if (tv->v_type != VAR_STRING || tv->vval.v_string == NULL) {
+ continue;
+ }
+
+ if (!STRCMP(dict_key, "name")) {
+ continue;
+ }
+ if (!STRCMP(dict_key, "filename")) {
+ continue;
+ }
+ if (!STRCMP(dict_key, "cmd")) {
+ continue;
+ }
+ if (!STRCMP(dict_key, "kind")) {
+ continue;
+ }
+
+ *p++ = TAB;
+ STRCPY(p, dict_key);
+ p += STRLEN(p);
+ STRCPY(p, ":");
+ p += STRLEN(p);
+ STRCPY(p, tv->vval.v_string);
+ p += STRLEN(p);
+ });
+ }
+ }
+
+ // Add all matches because tagfunc should do filtering.
+ ga_grow(ga, 1);
+ ((char_u **)(ga->ga_data))[ga->ga_len++] = mfp;
+ ntags++;
+ result = OK;
+ });
+
+ tv_clear(&rettv);
+
+ *match_count = ntags;
+ return result;
+}
+
/*
* find_tags() - search for tags in tags files
*
@@ -1054,12 +1350,13 @@ static void prepare_pats(pat_T *pats, int has_re)
* TAG_NOIC don't always ignore case
* TAG_KEEP_LANG keep language
* TAG_CSCOPE use cscope results for tags
+ * TAG_NO_TAGFUNC do not call the 'tagfunc' function
*/
-int
-find_tags (
- char_u *pat, /* pattern to search for */
- int *num_matches, /* return: number of matches found */
- char_u ***matchesp, /* return: array of matches found */
+int
+find_tags(
+ char_u *pat, // pattern to search for
+ int *num_matches, // return: number of matches found
+ char_u ***matchesp, // return: array of matches found
int flags,
int mincount, /* MAXCOL: find all matches
other: minimal number of matches */
@@ -1094,7 +1391,6 @@ find_tags (
int low_char; // first char at low_offset
int high_char; // first char at high_offset
} search_info;
- off_T filesize;
int tagcmp;
off_T offset;
int round;
@@ -1144,6 +1440,7 @@ find_tags (
int get_it_again = FALSE;
int use_cscope = (flags & TAG_CSCOPE);
int verbose = (flags & TAG_VERBOSE);
+ int use_tfu = ((flags & TAG_NO_TAGFUNC) == 0);
int save_p_ic = p_ic;
// Change the value of 'ignorecase' according to 'tagcase' for the
@@ -1221,6 +1518,16 @@ find_tags (
// uninitialised.
memset(&search_info, 0, 1); // -V512
+ if (*curbuf->b_p_tfu != NUL && use_tfu && !tfu_in_use) {
+ tfu_in_use = true;
+ retval = find_tagfunc_tags(pat, &ga_match[0], &match_count,
+ flags, buf_ffname);
+ tfu_in_use = false;
+ if (retval != NOTDONE) {
+ goto findtag_end;
+ }
+ }
+
/*
* When finding a specified number of matches, first try with matching
* case, so binary search can be used, and try ignore-case matches in a
@@ -1352,12 +1659,9 @@ find_tags (
break; /* End the binary search without a match. */
else
search_info.curr_offset = offset;
- }
- /*
- * Skipping back (after a match during binary search).
- */
- else if (state == TS_SKIP_BACK) {
- search_info.curr_offset -= LSIZE * 2;
+ } else if (state == TS_SKIP_BACK) {
+ // Skipping back (after a match during binary search).
+ search_info.curr_offset -= lbuf_size * 2;
if (search_info.curr_offset < 0) {
search_info.curr_offset = 0;
rewind(fp);
@@ -1373,7 +1677,7 @@ find_tags (
/* Adjust the search file offset to the correct position */
search_info.curr_offset_used = search_info.curr_offset;
vim_fseek(fp, search_info.curr_offset, SEEK_SET);
- eof = vim_fgets(lbuf, LSIZE, fp);
+ eof = vim_fgets(lbuf, lbuf_size, fp);
if (!eof && search_info.curr_offset != 0) {
/* The explicit cast is to work around a bug in gcc 3.4.2
* (repeated below). */
@@ -1383,12 +1687,12 @@ find_tags (
vim_fseek(fp, search_info.low_offset, SEEK_SET);
search_info.curr_offset = search_info.low_offset;
}
- eof = vim_fgets(lbuf, LSIZE, fp);
+ eof = vim_fgets(lbuf, lbuf_size, fp);
}
/* skip empty and blank lines */
while (!eof && vim_isblankline(lbuf)) {
search_info.curr_offset = vim_ftell(fp);
- eof = vim_fgets(lbuf, LSIZE, fp);
+ eof = vim_fgets(lbuf, lbuf_size, fp);
}
if (eof) {
/* Hit end of file. Skip backwards. */
@@ -1404,10 +1708,9 @@ find_tags (
else {
/* skip empty and blank lines */
do {
- if (use_cscope)
- eof = cs_fgets(lbuf, LSIZE);
- else
- eof = vim_fgets(lbuf, LSIZE, fp);
+ eof = use_cscope
+ ? cs_fgets(lbuf, lbuf_size)
+ : vim_fgets(lbuf, lbuf_size, fp);
} while (!eof && vim_isblankline(lbuf));
if (eof) {
@@ -1503,19 +1806,21 @@ line_read_in:
state = TS_LINEAR;
}
- /*
- * When starting a binary search, get the size of the file and
- * compute the first offset.
- */
+ // When starting a binary search, get the size of the file and
+ // compute the first offset.
if (state == TS_BINARY) {
- // Get the tag file size.
- if ((filesize = vim_lseek(fileno(fp), (off_T)0L, SEEK_END)) <= 0) {
+ if (vim_fseek(fp, 0, SEEK_END) != 0) {
+ // can't seek, don't use binary search
state = TS_LINEAR;
} else {
- vim_lseek(fileno(fp), (off_T)0L, SEEK_SET);
-
- /* Calculate the first read offset in the file. Start
- * the search in the middle of the file. */
+ // Get the tag file size.
+ // Don't use lseek(), it doesn't work
+ // properly on MacOS Catalina.
+ const off_T filesize = vim_ftell(fp);
+ vim_fseek(fp, 0, SEEK_SET);
+
+ // Calculate the first read offset in the file. Start
+ // the search in the middle of the file.
search_info.low_offset = 0;
search_info.low_char = 0;
search_info.high_offset = filesize;
@@ -1530,19 +1835,14 @@ parse_line:
// When the line is too long the NUL will not be in the
// last-but-one byte (see vim_fgets()).
// Has been reported for Mozilla JS with extremely long names.
- // In that case we can't parse it and we ignore the line.
- if (lbuf[LSIZE - 2] != NUL && !use_cscope) {
- if (p_verbose >= 5) {
- verbose_enter();
- MSG(_("Ignoring long line in tags file"));
- verbose_leave();
- }
- if (state != TS_LINEAR) {
- // Avoid getting stuck.
- linear = true;
- state = TS_LINEAR;
- vim_fseek(fp, search_info.low_offset, SEEK_SET);
- }
+ // In that case we need to increase lbuf_size.
+ if (lbuf[lbuf_size - 2] != NUL && !use_cscope) {
+ lbuf_size *= 2;
+ xfree(lbuf);
+ lbuf = xmalloc(lbuf_size);
+ // this will try the same thing again, make sure the offset is
+ // different
+ search_info.curr_offset = 0;
continue;
}
@@ -1802,7 +2102,6 @@ parse_line:
}
}
} else {
-#define TAG_SEP 0x02
size_t tag_fname_len = STRLEN(tag_fname);
// Save the tag in a buffer.
// Use 0x02 to separate fields (Can't use NUL, because the
@@ -1986,9 +2285,7 @@ void free_tag_stuff(void)
do_tag(NULL, DT_FREE, 0, 0, 0);
tag_freematch();
- if (ptag_entry.tagname) {
- XFREE_CLEAR(ptag_entry.tagname);
- }
+ tagstack_clear_entry(&ptag_entry);
}
#endif
@@ -1999,11 +2296,11 @@ void free_tag_stuff(void)
*
* Return FAIL if no more tag file names, OK otherwise.
*/
-int
-get_tagfname (
- tagname_T *tnp, /* holds status info */
- int first, /* TRUE when first file name is wanted */
- char_u *buf /* pointer to buffer of MAXPATHL chars */
+int
+get_tagfname(
+ tagname_T *tnp, // holds status info
+ int first, // TRUE when first file name is wanted
+ char_u *buf // pointer to buffer of MAXPATHL chars
)
{
char_u *fname = NULL;
@@ -2128,9 +2425,9 @@ void tagname_free(tagname_T *tnp)
*
* Return FAIL if there is a format error in this line, OK otherwise.
*/
-static int
-parse_tag_line (
- char_u *lbuf, /* line to be parsed */
+static int
+parse_tag_line(
+ char_u *lbuf, // line to be parsed
tagptrs_T *tagp
)
{
@@ -2211,10 +2508,10 @@ static size_t matching_line_len(const char_u *const lbuf)
*
* Return OK or FAIL.
*/
-static int
-parse_match (
- char_u *lbuf, /* input: matching line */
- tagptrs_T *tagp /* output: pointers into the line */
+static int
+parse_match(
+ char_u *lbuf, // input: matching line
+ tagptrs_T *tagp // output: pointers into the line
)
{
int retval;
@@ -2229,6 +2526,8 @@ parse_match (
tagp);
tagp->tagkind = NULL;
+ tagp->user_data = NULL;
+ tagp->tagline = 0;
tagp->command_end = NULL;
if (retval == OK) {
@@ -2246,13 +2545,19 @@ parse_match (
while (ASCII_ISALPHA(*p)) {
if (STRNCMP(p, "kind:", 5) == 0) {
tagp->tagkind = p + 5;
+ } else if (STRNCMP(p, "user_data:", 10) == 0) {
+ tagp->user_data = p + 10;
+ } else if (STRNCMP(p, "line:", 5) == 0) {
+ tagp->tagline = atoi((char *)p + 5);
+ }
+ if (tagp->tagkind != NULL && tagp->user_data != NULL) {
break;
}
+
pc = vim_strchr(p, ':');
pt = vim_strchr(p, '\t');
if (pc == NULL || (pt != NULL && pc > pt)) {
tagp->tagkind = p;
- break;
}
if (pt == NULL)
break;
@@ -2266,6 +2571,12 @@ parse_match (
;
tagp->tagkind_end = p;
}
+ if (tagp->user_data != NULL) {
+ for (p = tagp->user_data;
+ *p && *p != '\t' && *p != '\r' && *p != '\n'; p++) {
+ }
+ tagp->user_data_end = p;
+ }
}
return retval;
}
@@ -2334,6 +2645,9 @@ static int jumpto_tag(
str = tagp.command;
for (pbuf_end = pbuf; *str && *str != '\n' && *str != '\r'; ) {
*pbuf_end++ = *str++;
+ if (pbuf_end - pbuf + 1 >= LSIZE) {
+ break;
+ }
}
*pbuf_end = NUL;
@@ -2485,9 +2799,15 @@ static int jumpto_tag(
p_ic = FALSE; /* don't ignore case now */
p_scs = FALSE;
save_lnum = curwin->w_cursor.lnum;
- curwin->w_cursor.lnum = 0; /* start search before first line */
+ 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;
+ }
if (do_search(NULL, pbuf[0], pbuf + 1, (long)1,
- search_options, NULL, NULL)) {
+ search_options, NULL)) {
retval = OK;
} else {
int found = 1;
@@ -2498,20 +2818,18 @@ static int jumpto_tag(
*/
p_ic = TRUE;
if (!do_search(NULL, pbuf[0], pbuf + 1, (long)1,
- search_options, NULL, NULL)) {
+ search_options, NULL)) {
// Failed to find pattern, take a guess: "^func ("
found = 2;
(void)test_for_static(&tagp);
cc = *tagp.tagname_end;
*tagp.tagname_end = NUL;
snprintf((char *)pbuf, LSIZE, "^%s\\s\\*(", tagp.tagname);
- if (!do_search(NULL, '/', pbuf, (long)1,
- search_options, NULL, NULL)) {
+ if (!do_search(NULL, '/', pbuf, (long)1, search_options, NULL)) {
// Guess again: "^char * \<func ("
snprintf((char *)pbuf, LSIZE, "^\\[#a-zA-Z_]\\.\\*\\<%s\\s\\*(",
tagp.tagname);
- if (!do_search(NULL, '/', pbuf, (long)1,
- search_options, NULL, NULL)) {
+ if (!do_search(NULL, '/', pbuf, (long)1, search_options, NULL)) {
found = 0;
}
}
@@ -2666,7 +2984,8 @@ static int test_for_current(char_u *fname, char_u *fname_end, char_u *tag_fname,
*fname_end = NUL;
}
fullname = expand_tag_fname(fname, tag_fname, true);
- retval = (path_full_compare(fullname, buf_ffname, true) & kEqualFiles);
+ retval = (path_full_compare(fullname, buf_ffname, true, true)
+ & kEqualFiles);
xfree(fullname);
*fname_end = c;
}
@@ -2715,6 +3034,15 @@ static int find_extra(char_u **pp)
return FAIL;
}
+//
+// Free a single entry in a tag stack
+//
+static void tagstack_clear_entry(taggy_T *item)
+{
+ XFREE_CLEAR(item->tagname);
+ XFREE_CLEAR(item->user_data);
+}
+
int
expand_tags (
int tagnames, /* expand tag names */
@@ -2734,14 +3062,16 @@ expand_tags (
tagnmflag = TAG_NAMES;
else
tagnmflag = 0;
- if (pat[0] == '/')
+ if (pat[0] == '/') {
ret = find_tags(pat + 1, num_file, file,
- TAG_REGEXP | tagnmflag | TAG_VERBOSE,
- TAG_MANY, curbuf->b_ffname);
- else
+ TAG_REGEXP | tagnmflag | TAG_VERBOSE | TAG_NO_TAGFUNC,
+ TAG_MANY, curbuf->b_ffname);
+ } else {
ret = find_tags(pat, num_file, file,
- TAG_REGEXP | tagnmflag | TAG_VERBOSE | TAG_NOIC,
- TAG_MANY, curbuf->b_ffname);
+ TAG_REGEXP | tagnmflag | TAG_VERBOSE
+ | TAG_NO_TAGFUNC | TAG_NOIC,
+ TAG_MANY, curbuf->b_ffname);
+ }
if (ret == OK && !tagnames) {
/* Reorganize the tags for display and matching as strings of:
* "<tagname>\0<kind>\0<filename>\0"
@@ -2767,8 +3097,8 @@ expand_tags (
* Add a tag field to the dictionary "dict".
* Return OK or FAIL.
*/
-static int
-add_tag_field (
+static int
+add_tag_field(
dict_T *dict,
const char *field_name,
const char_u *start, // start of the value
@@ -2903,6 +3233,9 @@ static void get_tag_details(taggy_T *tag, dict_T *retdict)
tv_dict_add_str(retdict, S_LEN("tagname"), (const char *)tag->tagname);
tv_dict_add_nr(retdict, S_LEN("matchnr"), tag->cur_match + 1);
tv_dict_add_nr(retdict, S_LEN("bufnr"), tag->cur_fnum);
+ if (tag->user_data) {
+ tv_dict_add_str(retdict, S_LEN("user_data"), (const char *)tag->user_data);
+ }
pos = tv_list_alloc(4);
tv_dict_add_list(retdict, S_LEN("from"), pos);
@@ -2941,7 +3274,7 @@ static void tagstack_clear(win_T *wp)
{
// Free the current tag stack
for (int i = 0; i < wp->w_tagstacklen; i++) {
- xfree(wp->w_tagstack[i].tagname);
+ tagstack_clear_entry(&wp->w_tagstack[i]);
}
wp->w_tagstacklen = 0;
wp->w_tagstackidx = 0;
@@ -2952,7 +3285,7 @@ static void tagstack_clear(win_T *wp)
static void tagstack_shift(win_T *wp)
{
taggy_T *tagstack = wp->w_tagstack;
- xfree(tagstack[0].tagname);
+ tagstack_clear_entry(&tagstack[0]);
for (int i = 1; i < wp->w_tagstacklen; i++) {
tagstack[i - 1] = tagstack[i];
}
@@ -2966,7 +3299,8 @@ static void tagstack_push_item(
int cur_fnum,
int cur_match,
pos_T mark,
- int fnum)
+ int fnum,
+ char_u *user_data)
{
taggy_T *tagstack = wp->w_tagstack;
int idx = wp->w_tagstacklen; // top of the stack
@@ -2986,6 +3320,7 @@ static void tagstack_push_item(
}
tagstack[idx].fmark.mark = mark;
tagstack[idx].fmark.fnum = fnum;
+ tagstack[idx].user_data = user_data;
}
// Add a list of items to the tag stack in the specified window
@@ -3021,10 +3356,13 @@ static void tagstack_push_items(win_T *wp, list_T *l)
if (mark.col > 0) {
mark.col--;
}
- tagstack_push_item(wp, tagname,
- (int)tv_dict_get_number(itemdict, "bufnr"),
- (int)tv_dict_get_number(itemdict, "matchnr") - 1,
- mark, fnum);
+ tagstack_push_item(
+ wp,
+ tagname,
+ (int)tv_dict_get_number(itemdict, "bufnr"),
+ (int)tv_dict_get_number(itemdict, "matchnr") - 1,
+ mark, fnum,
+ (char_u *)tv_dict_get_string(itemdict, "user_data", true));
}
}
@@ -3048,6 +3386,12 @@ int set_tagstack(win_T *wp, dict_T *d, int action)
dictitem_T *di;
list_T *l;
+ // not allowed to alter the tag stack entries from inside tagfunc
+ if (tfu_in_use) {
+ EMSG(_(recurmsg));
+ return FAIL;
+ }
+
if ((di = tv_dict_find(d, "items", -1)) != NULL) {
if (di->di_tv.v_type != VAR_LIST) {
return FAIL;
diff --git a/src/nvim/tag.h b/src/nvim/tag.h
index a8fddd05da..9f671043b3 100644
--- a/src/nvim/tag.h
+++ b/src/nvim/tag.h
@@ -20,20 +20,21 @@
#define DT_LTAG 11 /* tag using location list */
#define DT_FREE 99 /* free cached matches */
-/*
- * flags for find_tags().
- */
-#define TAG_HELP 1 /* only search for help tags */
-#define TAG_NAMES 2 /* only return name of tag */
-#define TAG_REGEXP 4 /* use tag pattern as regexp */
-#define TAG_NOIC 8 /* don't always ignore case */
-#define TAG_CSCOPE 16 /* cscope tag */
-#define TAG_VERBOSE 32 /* message verbosity */
-#define TAG_INS_COMP 64 /* Currently doing insert completion */
-#define TAG_KEEP_LANG 128 /* keep current language */
+//
+// flags for find_tags().
+//
+#define TAG_HELP 1 // only search for help tags
+#define TAG_NAMES 2 // only return name of tag
+#define TAG_REGEXP 4 // use tag pattern as regexp
+#define TAG_NOIC 8 // don't always ignore case
+#define TAG_CSCOPE 16 // cscope tag
+#define TAG_VERBOSE 32 // message verbosity
+#define TAG_INS_COMP 64 // Currently doing insert completion
+#define TAG_KEEP_LANG 128 // keep current language
+#define TAG_NO_TAGFUNC 256 // do not use 'tagfunc'
-#define TAG_MANY 300 /* When finding many tags (for completion),
- find up to this many tags */
+#define TAG_MANY 300 // When finding many tags (for completion),
+ // find up to this many tags
/*
* Structure used for get_tagfname().
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index 8fcc8bf0a5..c5e756905a 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -138,6 +138,8 @@ struct terminal {
int pressed_button; // which mouse button is pressed
bool pending_resize; // pending width/height
+ bool color_set[16];
+
size_t refcount; // reference count
};
@@ -220,8 +222,6 @@ Terminal *terminal_open(TerminalOptions opts)
rv->sb_size = (size_t)curbuf->b_p_scbk;
rv->sb_buffer = xmalloc(sizeof(ScrollbackLine *) * rv->sb_size);
- vterm_state_set_bold_highbright(state, true);
-
// Configure the color palette. Try to get the color from:
//
// - b:terminal_color_{NUM}
@@ -243,6 +243,7 @@ Terminal *terminal_open(TerminalOptions opts)
(uint8_t)((color_val >> 8) & 0xFF),
(uint8_t)((color_val >> 0) & 0xFF));
vterm_state_set_palette_color(state, i, &color);
+ rv->color_set[i] = true;
}
}
}
@@ -600,16 +601,22 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr,
int vt_fg = fg_default ? -1 : get_rgb(state, cell.fg);
int vt_bg = bg_default ? -1 : get_rgb(state, cell.bg);
- int vt_fg_idx = ((!fg_default && VTERM_COLOR_IS_INDEXED(&cell.fg))
- ? cell.fg.indexed.idx + 1 : 0);
- int vt_bg_idx = ((!bg_default && VTERM_COLOR_IS_INDEXED(&cell.bg))
- ? cell.bg.indexed.idx + 1 : 0);
+ bool fg_indexed = VTERM_COLOR_IS_INDEXED(&cell.fg);
+ bool bg_indexed = VTERM_COLOR_IS_INDEXED(&cell.bg);
+
+ int vt_fg_idx = ((!fg_default && fg_indexed) ? cell.fg.indexed.idx + 1 : 0);
+ int vt_bg_idx = ((!bg_default && bg_indexed) ? cell.bg.indexed.idx + 1 : 0);
+
+ bool fg_set = vt_fg_idx && vt_fg_idx <= 16 && term->color_set[vt_fg_idx-1];
+ bool bg_set = vt_bg_idx && vt_bg_idx <= 16 && term->color_set[vt_bg_idx-1];
int hl_attrs = (cell.attrs.bold ? HL_BOLD : 0)
| (cell.attrs.italic ? HL_ITALIC : 0)
| (cell.attrs.reverse ? HL_INVERSE : 0)
| (cell.attrs.underline ? HL_UNDERLINE : 0)
- | (cell.attrs.strike ? HL_STRIKETHROUGH: 0);
+ | (cell.attrs.strike ? HL_STRIKETHROUGH: 0)
+ | ((fg_indexed && !fg_set) ? HL_FG_INDEXED : 0)
+ | ((bg_indexed && !bg_set) ? HL_BG_INDEXED : 0);
int attr_id = 0;
diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile
index d1a449c7cc..b470dbf8f6 100644
--- a/src/nvim/testdir/Makefile
+++ b/src/nvim/testdir/Makefile
@@ -11,11 +11,10 @@ ROOT := ../../..
export SHELL := sh
export NVIM_PRG := $(NVIM_PRG)
-export TMPDIR := $(abspath ../../../Xtest-tmpdir)
+export TMPDIR := $(abspath Xtest-tmpdir)
SCRIPTS_DEFAULT = \
test42.out \
- test48.out \
test64.out \
ifneq ($(OS),Windows_NT)
@@ -34,21 +33,23 @@ SCRIPTS ?= $(SCRIPTS_DEFAULT)
# Tests using runtest.vim.
NEW_TESTS_ALOT := test_alot_utf8 test_alot
-NEW_TESTS_IN_ALOT := $(shell sed '/^source/ s/^source //;s/\.vim$$//' test_alot*.vim)
+NEW_TESTS_IN_ALOT := $(shell sed -n '/^source/ s/^source //; s/\.vim$$//p' $(addsuffix .vim,$(NEW_TESTS_ALOT)))
+NEW_TESTS_IN_ALOT_LATIN := $(shell sed -n '/^source/ s/^source //; s/\.vim$$//p' test_alot_latin.vim)
# Ignored tests.
# test_alot_latin: Nvim does not allow setting encoding.
# test_autochdir: ported to Lua, but kept for easier merging.
# test_eval_func: used as include in old-style test (test_eval.in).
# test_listlbr: Nvim does not allow setting encoding.
# test_largefile: uses too much resources to run on CI.
-NEW_TESTS_IGNORE := $(NEW_TESTS_IN_ALOT) $(NEW_TESTS_ALOT) \
- test_alot_latin \
+NEW_TESTS_IGNORE := \
+ test_alot_latin $(NEW_TESTS_IN_ALOT_LATIN) \
test_autochdir \
test_eval_func \
test_listlbr \
test_largefile \
-NEW_TESTS ?= $(addsuffix .res,$(sort $(filter-out $(NEW_TESTS_IGNORE),$(basename $(notdir $(wildcard test_*.vim))))) $(NEW_TESTS_ALOT))
+NEW_TESTS := $(sort $(basename $(notdir $(wildcard test_*.vim))))
+NEW_TESTS_RES := $(addsuffix .res,$(filter-out $(NEW_TESTS_ALOT) $(NEW_TESTS_IN_ALOT) $(NEW_TESTS_IGNORE),$(NEW_TESTS)) $(NEW_TESTS_ALOT))
ifdef VALGRIND_GDB
@@ -112,6 +113,16 @@ fixff:
-$(NVIM_PRG) $(NO_INITS) -u unix.vim "+argdo set ff=dos|upd" +q \
dotest.in
+# Execute an individual new style test, e.g.:
+# make test_largefile
+$(NEW_TESTS):
+ rm -f $@.res test.log messages
+ @MAKEFLAGS=--no-print-directory $(MAKE) -f Makefile $@.res
+ @cat messages
+ @if test -f test.log; then \
+ exit 1; \
+ fi
+
RM_ON_RUN := test.out X* viminfo
RM_ON_START := test.ok
RUN_VIM := $(TOOL) $(NVIM_PRG) -u unix.vim -U NONE -i viminfo --headless --noplugin -s dotest.in
@@ -172,7 +183,7 @@ newtests: newtestssilent
cat messages && cat test.log; \
fi"
-newtestssilent: $(NEW_TESTS)
+newtestssilent: $(NEW_TESTS_RES)
%.res: %.vim .gdbinit
@echo "[OLDTEST] Running" $*
diff --git a/src/nvim/testdir/runnvim.sh b/src/nvim/testdir/runnvim.sh
index 2dcd9150be..72f9254635 100755
--- a/src/nvim/testdir/runnvim.sh
+++ b/src/nvim/testdir/runnvim.sh
@@ -82,6 +82,11 @@ main() {(
fi
if test "$FAILED" = 1 ; then
echo "Test $test_name failed, see output above and summary for more details" >> test.log
+ # When Neovim crashed/aborted it might not have created messages.
+ # test.log itself is used as an indicator to exit non-zero in the Makefile.
+ if ! test -f message; then
+ cp -a test.log messages
+ fi
fi
)}
diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim
index 593ce6fcdc..2d4134a644 100644
--- a/src/nvim/testdir/runtest.vim
+++ b/src/nvim/testdir/runtest.vim
@@ -282,18 +282,23 @@ endif
" Names of flaky tests.
let s:flaky_tests = [
+ \ 'Test_autocmd_SafeState()',
\ 'Test_cursorhold_insert()',
\ 'Test_exit_callback_interval()',
\ 'Test_map_timeout_with_timer_interrupt()',
\ 'Test_oneshot()',
\ 'Test_out_cb()',
\ 'Test_paused()',
+ \ 'Test_popup_and_window_resize()',
\ 'Test_quoteplus()',
\ 'Test_quotestar()',
\ 'Test_reltime()',
\ 'Test_repeat_many()',
\ 'Test_repeat_three()',
+ \ 'Test_state()',
\ 'Test_stop_all_in_callback()',
+ \ 'Test_term_mouse_double_click_to_create_tab',
+ \ 'Test_term_mouse_multiple_clicks_to_visually_select()',
\ 'Test_terminal_composing_unicode()',
\ 'Test_terminal_redir_file()',
\ 'Test_terminal_tmap()',
diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim
index ea28f328ae..d032c9a739 100644
--- a/src/nvim/testdir/setup.vim
+++ b/src/nvim/testdir/setup.vim
@@ -19,6 +19,7 @@ set sidescroll=0
set tags=./tags,tags
set undodir^=.
set wildoptions=
+set startofline
" Prevent Nvim log from writing to stderr.
let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log'
diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim
index df512e2e3f..3875ffc056 100644
--- a/src/nvim/testdir/shared.vim
+++ b/src/nvim/testdir/shared.vim
@@ -69,7 +69,8 @@ endfunc
" Read the port number from the Xportnr file.
func GetPort()
let l = []
- for i in range(200)
+ " with 200 it sometimes failed
+ for i in range(400)
try
let l = readfile("Xportnr")
catch
@@ -135,39 +136,80 @@ endfunc
" Wait for up to five seconds for "expr" to become true. "expr" can be a
" stringified expression to evaluate, or a funcref without arguments.
+" Using a lambda works best. Example:
+" call WaitFor({-> status == "ok"})
"
" A second argument can be used to specify a different timeout in msec.
"
-" Return time slept in milliseconds. With the +reltime feature this can be
-" more than the actual waiting time. Without +reltime it can also be less.
+" When successful the time slept is returned.
+" When running into the timeout an exception is thrown, thus the function does
+" not return.
func WaitFor(expr, ...)
let timeout = get(a:000, 0, 5000)
+ let slept = s:WaitForCommon(a:expr, v:null, timeout)
+ if slept < 0
+ throw 'WaitFor() timed out after ' . timeout . ' msec'
+ endif
+ return slept
+endfunc
+
+" Wait for up to five seconds for "assert" to return zero. "assert" must be a
+" (lambda) function containing one assert function. Example:
+" call WaitForAssert({-> assert_equal("dead", job_status(job)})
+"
+" A second argument can be used to specify a different timeout in msec.
+"
+" Return zero for success, one for failure (like the assert function).
+func WaitForAssert(assert, ...)
+ let timeout = get(a:000, 0, 5000)
+ if s:WaitForCommon(v:null, a:assert, timeout) < 0
+ return 1
+ endif
+ return 0
+endfunc
+
+" Common implementation of WaitFor() and WaitForAssert().
+" Either "expr" or "assert" is not v:null
+" Return the waiting time for success, -1 for failure.
+func s:WaitForCommon(expr, assert, timeout)
" using reltime() is more accurate, but not always available
+ let slept = 0
if has('reltime')
let start = reltime()
- else
- let slept = 0
endif
- if type(a:expr) == v:t_func
- let Test = a:expr
- else
- let Test = {-> eval(a:expr) }
- endif
- for i in range(timeout / 10)
- if Test()
- if has('reltime')
- return float2nr(reltimefloat(reltime(start)) * 1000)
- endif
+
+ while 1
+ if type(a:expr) == v:t_func
+ let success = a:expr()
+ elseif type(a:assert) == v:t_func
+ let success = a:assert() == 0
+ else
+ let success = eval(a:expr)
+ endif
+ if success
return slept
endif
- if !has('reltime')
- let slept += 10
+
+ if slept >= a:timeout
+ break
+ endif
+ if type(a:assert) == v:t_func
+ " Remove the error added by the assert function.
+ call remove(v:errors, -1)
endif
+
sleep 10m
- endfor
- throw 'WaitFor() timed out after ' . timeout . ' msec'
+ if has('reltime')
+ let slept = float2nr(reltimefloat(reltime(start)) * 1000)
+ else
+ let slept += 10
+ endif
+ endwhile
+
+ return -1 " timed out
endfunc
+
" Wait for up to a given milliseconds.
" With the +timers feature this waits for key-input by getchar(), Resume()
" feeds key-input and resumes process. Return time waited in milliseconds.
@@ -211,6 +253,8 @@ func GetVimProg()
endif
endfunc
+let g:valgrind_cnt = 1
+
" Get the command to run Vim, with -u NONE and --headless arguments.
" If there is an argument use it instead of "NONE".
func GetVimCommand(...)
@@ -226,14 +270,25 @@ func GetVimCommand(...)
endif
let cmd .= ' --headless -i NONE'
let cmd = substitute(cmd, 'VIMRUNTIME=.*VIMRUNTIME;', '', '')
+
+ " If using valgrind, make sure every run uses a different log file.
+ if cmd =~ 'valgrind.*--log-file='
+ let cmd = substitute(cmd, '--log-file=\(^\s*\)', '--log-file=\1.' . g:valgrind_cnt, '')
+ let g:valgrind_cnt += 1
+ endif
+
return cmd
endfunc
-" Get the command to run Vim, with --clean.
+" Get the command to run Vim, with --clean instead of "-u NONE".
func GetVimCommandClean()
let cmd = GetVimCommand()
let cmd = substitute(cmd, '-u NONE', '--clean', '')
let cmd = substitute(cmd, '--headless', '', '')
+
+ " Optionally run Vim under valgrind
+ " let cmd = 'valgrind --tool=memcheck --leak-check=yes --num-callers=25 --log-file=valgrind ' . cmd
+
return cmd
endfunc
@@ -249,9 +304,6 @@ endfunc
func RunVimPiped(before, after, arguments, pipecmd)
let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log'
let cmd = GetVimCommand()
- if cmd == ''
- return 0
- endif
let args = ''
if len(a:before) > 0
call writefile(a:before, 'Xbefore.vim')
@@ -262,6 +314,9 @@ func RunVimPiped(before, after, arguments, pipecmd)
let args .= ' -S Xafter.vim'
endif
+ " Optionally run Vim under valgrind
+ " let cmd = 'valgrind --tool=memcheck --leak-check=yes --num-callers=25 --log-file=valgrind ' . cmd
+
exe "silent !" . a:pipecmd . cmd . args . ' ' . a:arguments
if len(a:before) > 0
diff --git a/src/nvim/testdir/test42.in b/src/nvim/testdir/test42.in
index baa6e67d26..d9057e72fb 100644
--- a/src/nvim/testdir/test42.in
+++ b/src/nvim/testdir/test42.in
Binary files differ
diff --git a/src/nvim/testdir/test48.in b/src/nvim/testdir/test48.in
deleted file mode 100644
index 1df5a3c46a..0000000000
--- a/src/nvim/testdir/test48.in
+++ /dev/null
@@ -1,82 +0,0 @@
-This is a test of 'virtualedit'.
-
-STARTTEST
-:set noswf
-:set ve=all
-j-dgg
-:"
-:" Insert "keyword keyw", ESC, C CTRL-N, shows "keyword ykeyword".
-:" Repeating CTRL-N fixes it. (Mary Ellen Foster)
-2/w
-C
-:"
-:" Using "C" then then <CR> moves the last remaining character to the next
-:" line. (Mary Ellen Foster)
-j^/are
-C are belong to vim
-:"
-:" When past the end of a line that ends in a single character "b" skips
-:" that word.
-^$15lbC7
-:"
-:" Make sure 'i' works
-$4li<-- should be 3 ' '
-:"
-:" Make sure 'C' works
-$4lC<-- should be 3 ' '
-:"
-:" Make sure 'a' works
-$4la<-- should be 4 ' '
-:"
-:" Make sure 'A' works
-$4lA<-- should be 0 ' '
-:"
-:" Make sure 'D' works
-$4lDi<-- 'D' should be intact
-:"
-:" Test for yank bug reported by Mark Waggoner.
-:set ve=block
-^2w3jyGp
-:"
-:" Test "r" beyond the end of the line
-:set ve=all
-/^"r"
-$5lrxa<-- should be 'x'
-:"
-:" Test "r" on a tab
-:" Note that for this test, 'ts' must be 8 (the default).
-^5lrxA<-- should be ' x '
-:"
-:" Test to make sure 'x' can delete control characters
-:set display=uhex
-^xxxxxxi[This line should contain only the text between the brackets.]
-:set display=
-:"
-:" Test for ^Y/^E due to bad w_virtcol value, reported by
-:" Roy <royl@netropolis.net>.
-^O3li4li4li <-- should show the name of a noted text editor
-^o4li4li4li <-- and its version number-dd
-:"
-:" Test for yanking and pasting using the small delete register
-gg/^foo
-dewve"-p
-:wq! test.out
-ENDTEST
-foo, bar
-keyword keyw
-all your base are belong to us
-1 2 3 4 5 6
-'i'
-'C'
-'a'
-'A'
-'D'
-this is a test
-this is a test
-this is a test
-"r"
-"r"
-ab sd
-abcv6efi.him0kl
-
-
diff --git a/src/nvim/testdir/test48.ok b/src/nvim/testdir/test48.ok
deleted file mode 100644
index 14cd9b12ec..0000000000
--- a/src/nvim/testdir/test48.ok
+++ /dev/null
@@ -1,23 +0,0 @@
-, foo
-keyword keyword
-all your base
-are belong to vim
-1 2 3 4 5 7
-'i' <-- should be 3 ' '
-'C' <-- should be 3 ' '
-'a' <-- should be 4 ' '
-'A'<-- should be 0 ' '
-'D' <-- 'D' should be intact
-this is a test
-this is a test
-this is a test
-"r" x<-- should be 'x'
-"r" x <-- should be ' x '
-[This line should contain only the text between the brackets.]
- v i m <-- should show the name of a noted text editor
- 6 . 0 <-- and its version number
-
-a
-a
-a
-
diff --git a/src/nvim/testdir/test49.vim b/src/nvim/testdir/test49.vim
index 837e55ebca..fc79f57d2e 100644
--- a/src/nvim/testdir/test49.vim
+++ b/src/nvim/testdir/test49.vim
@@ -178,7 +178,7 @@ endif
" next Xpath value. No new Xnext value is prepared. The argument
" should be 2^(n-1) for the nth Xloop command inside the loop.
" If the loop has only one Xloop command, the argument can be
-" ommitted (default: 1).
+" omitted (default: 1).
"
" - Use XloopNEXT before ":continue" and ":endwhile". This computes a new
" Xnext value for the next execution of the loop by multiplying the old
diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim
index 6bf2e8329c..5668f45dea 100644
--- a/src/nvim/testdir/test_alot.vim
+++ b/src/nvim/testdir/test_alot.vim
@@ -2,6 +2,7 @@
" This makes testing go faster, since Vim doesn't need to restart.
source test_assign.vim
+source test_backup.vim
source test_behave.vim
source test_cd.vim
source test_changedtick.vim
@@ -27,7 +28,6 @@ source test_jumps.vim
source test_fileformat.vim
source test_filetype.vim
source test_lambda.vim
-source test_mapping.vim
source test_menu.vim
source test_messages.vim
source test_modeline.vim
@@ -45,6 +45,7 @@ source test_syn_attr.vim
source test_tabline.vim
source test_tabpage.vim
source test_tagcase.vim
+source test_tagfunc.vim
source test_tagjump.vim
source test_taglist.vim
source test_true_false.vim
diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim
index a4c8ce7e43..4cc90eca7a 100644
--- a/src/nvim/testdir/test_assert.vim
+++ b/src/nvim/testdir/test_assert.vim
@@ -1,5 +1,40 @@
" Test that the methods used for testing work.
+func Test_assert_equalfile()
+ call assert_equal(1, assert_equalfile('abcabc', 'xyzxyz'))
+ call assert_match("E485: Can't read file abcabc", v:errors[0])
+ call remove(v:errors, 0)
+
+ let goodtext = ["one", "two", "three"]
+ call writefile(goodtext, 'Xone')
+ call assert_equal(1, assert_equalfile('Xone', 'xyzxyz'))
+ call assert_match("E485: Can't read file xyzxyz", v:errors[0])
+ call remove(v:errors, 0)
+
+ call writefile(goodtext, 'Xtwo')
+ call assert_equal(0, assert_equalfile('Xone', 'Xtwo'))
+
+ call writefile([goodtext[0]], 'Xone')
+ call assert_equal(1, assert_equalfile('Xone', 'Xtwo'))
+ call assert_match("first file is shorter", v:errors[0])
+ call remove(v:errors, 0)
+
+ call writefile(goodtext, 'Xone')
+ call writefile([goodtext[0]], 'Xtwo')
+ call assert_equal(1, assert_equalfile('Xone', 'Xtwo'))
+ call assert_match("second file is shorter", v:errors[0])
+ call remove(v:errors, 0)
+
+ call writefile(['1234X89'], 'Xone')
+ call writefile(['1234Y89'], 'Xtwo')
+ call assert_equal(1, assert_equalfile('Xone', 'Xtwo'))
+ call assert_match("difference at byte 4", v:errors[0])
+ call remove(v:errors, 0)
+
+ call delete('Xone')
+ call delete('Xtwo')
+endfunc
+
func Test_assert_fails_in_try_block()
try
call assert_equal(0, assert_fails('throw "error"'))
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index 5848940a2b..275b053bc9 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -425,18 +425,20 @@ func Test_autocmd_bufwipe_in_SessLoadPost()
set noswapfile
mksession!
- let content = ['set nocp noswapfile',
- \ 'let v:swapchoice="e"',
- \ 'augroup test_autocmd_sessionload',
- \ 'autocmd!',
- \ 'autocmd SessionLoadPost * exe bufnr("Xsomething") . "bw!"',
- \ 'augroup END',
- \ '',
- \ 'func WriteErrors()',
- \ ' call writefile([execute("messages")], "Xerrors")',
- \ 'endfunc',
- \ 'au VimLeave * call WriteErrors()',
- \ ]
+ let content =<< trim [CODE]
+ set nocp noswapfile
+ let v:swapchoice="e"
+ augroup test_autocmd_sessionload
+ autocmd!
+ autocmd SessionLoadPost * exe bufnr("Xsomething") . "bw!"
+ augroup END
+
+ func WriteErrors()
+ call writefile([execute("messages")], "Xerrors")
+ endfunc
+ au VimLeave * call WriteErrors()
+ [CODE]
+
call writefile(content, 'Xvimrc')
call system(v:progpath. ' --headless -i NONE -u Xvimrc --noplugins -S Session.vim -c cq')
let errors = join(readfile('Xerrors'))
@@ -454,27 +456,29 @@ func Test_autocmd_bufwipe_in_SessLoadPost2()
set noswapfile
mksession!
- let content = ['set nocp noswapfile',
- \ 'function! DeleteInactiveBufs()',
- \ ' tabfirst',
- \ ' let tabblist = []',
- \ ' for i in range(1, tabpagenr(''$''))',
- \ ' call extend(tabblist, tabpagebuflist(i))',
- \ ' endfor',
- \ ' for b in range(1, bufnr(''$''))',
- \ ' if bufexists(b) && buflisted(b) && (index(tabblist, b) == -1 || bufname(b) =~# ''^$'')',
- \ ' exec ''bwipeout '' . b',
- \ ' endif',
- \ ' endfor',
- \ ' echomsg "SessionLoadPost DONE"',
- \ 'endfunction',
- \ 'au SessionLoadPost * call DeleteInactiveBufs()',
- \ '',
- \ 'func WriteErrors()',
- \ ' call writefile([execute("messages")], "Xerrors")',
- \ 'endfunc',
- \ 'au VimLeave * call WriteErrors()',
- \ ]
+ let content =<< trim [CODE]
+ set nocp noswapfile
+ function! DeleteInactiveBufs()
+ tabfirst
+ let tabblist = []
+ for i in range(1, tabpagenr(''$''))
+ call extend(tabblist, tabpagebuflist(i))
+ endfor
+ for b in range(1, bufnr(''$''))
+ if bufexists(b) && buflisted(b) && (index(tabblist, b) == -1 || bufname(b) =~# ''^$'')
+ exec ''bwipeout '' . b
+ endif
+ endfor
+ echomsg "SessionLoadPost DONE"
+ endfunction
+ au SessionLoadPost * call DeleteInactiveBufs()
+
+ func WriteErrors()
+ call writefile([execute("messages")], "Xerrors")
+ endfunc
+ au VimLeave * call WriteErrors()
+ [CODE]
+
call writefile(content, 'Xvimrc')
call system(v:progpath. ' --headless -i NONE -u Xvimrc --noplugins -S Session.vim -c cq')
let errors = join(readfile('Xerrors'))
@@ -936,21 +940,23 @@ func Test_bufunload_all()
call writefile(['Test file Xxx1'], 'Xxx1')"
call writefile(['Test file Xxx2'], 'Xxx2')"
- let content = [
- \ "func UnloadAllBufs()",
- \ " let i = 1",
- \ " while i <= bufnr('$')",
- \ " if i != bufnr('%') && bufloaded(i)",
- \ " exe i . 'bunload'",
- \ " endif",
- \ " let i += 1",
- \ " endwhile",
- \ "endfunc",
- \ "au BufUnload * call UnloadAllBufs()",
- \ "au VimLeave * call writefile(['Test Finished'], 'Xout')",
- \ "edit Xxx1",
- \ "split Xxx2",
- \ "q"]
+ let content =<< trim [CODE]
+ func UnloadAllBufs()
+ let i = 1
+ while i <= bufnr('$')
+ if i != bufnr('%') && bufloaded(i)
+ exe i . 'bunload'
+ endif
+ let i += 1
+ endwhile
+ endfunc
+ au BufUnload * call UnloadAllBufs()
+ au VimLeave * call writefile(['Test Finished'], 'Xout')
+ edit Xxx1
+ split Xxx2
+ q
+ [CODE]
+
call writefile(content, 'Xtest')
call delete('Xout')
@@ -1344,11 +1350,11 @@ func Test_Changed_FirstTime()
let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile'], {'term_rows': 3})
call assert_equal('running', term_getstatus(buf))
" Wait for the ruler (in the status line) to be shown.
- call WaitFor({-> term_getline(buf, 3) =~# '\<All$'})
+ call WaitForAssert({-> assert_match('\<All$', term_getline(buf, 3))})
" It's only adding autocmd, so that no event occurs.
call term_sendkeys(buf, ":au! TextChanged <buffer> call writefile(['No'], 'Xchanged.txt')\<cr>")
call term_sendkeys(buf, "\<C-\\>\<C-N>:qa!\<cr>")
- call WaitFor({-> term_getstatus(buf) == 'finished'})
+ call WaitForAssert({-> assert_equal('finished', term_getstatus(buf))})
call assert_equal([''], readfile('Xchanged.txt'))
" clean up
diff --git a/src/nvim/testdir/test_backup.vim b/src/nvim/testdir/test_backup.vim
new file mode 100644
index 0000000000..fa10430613
--- /dev/null
+++ b/src/nvim/testdir/test_backup.vim
@@ -0,0 +1,58 @@
+" Tests for the backup function
+
+func Test_backup()
+ set backup backupdir=.
+ new
+ call setline(1, ['line1', 'line2'])
+ :f Xbackup.txt
+ :w! Xbackup.txt
+ " backup file is only created after
+ " writing a second time (before overwriting)
+ :w! Xbackup.txt
+ let l = readfile('Xbackup.txt~')
+ call assert_equal(['line1', 'line2'], l)
+ bw!
+ set backup&vim backupdir&vim
+ call delete('Xbackup.txt')
+ call delete('Xbackup.txt~')
+endfunc
+
+func Test_backup2()
+ set backup backupdir=.//
+ new
+ call setline(1, ['line1', 'line2', 'line3'])
+ :f Xbackup.txt
+ :w! Xbackup.txt
+ " backup file is only created after
+ " writing a second time (before overwriting)
+ :w! Xbackup.txt
+ sp *Xbackup.txt~
+ call assert_equal(['line1', 'line2', 'line3'], getline(1,'$'))
+ let f=expand('%')
+ call assert_match('src%nvim%testdir%Xbackup.txt\~', f)
+ bw!
+ bw!
+ call delete('Xbackup.txt')
+ call delete(f)
+ set backup&vim backupdir&vim
+endfunc
+
+func Test_backup2_backupcopy()
+ set backup backupdir=.// backupcopy=yes
+ new
+ call setline(1, ['line1', 'line2', 'line3'])
+ :f Xbackup.txt
+ :w! Xbackup.txt
+ " backup file is only created after
+ " writing a second time (before overwriting)
+ :w! Xbackup.txt
+ sp *Xbackup.txt~
+ call assert_equal(['line1', 'line2', 'line3'], getline(1,'$'))
+ let f=expand('%')
+ call assert_match('src%nvim%testdir%Xbackup.txt\~', f)
+ bw!
+ bw!
+ call delete('Xbackup.txt')
+ call delete(f)
+ set backup&vim backupdir&vim backupcopy&vim
+endfunc
diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim
index 7c2c5e341c..d9795d9335 100644
--- a/src/nvim/testdir/test_cindent.vim
+++ b/src/nvim/testdir/test_cindent.vim
@@ -18,25 +18,25 @@ endfunc
func Test_cino_extern_c()
" Test for cino-E
- let without_ind = [
- \ '#ifdef __cplusplus',
- \ 'extern "C" {',
- \ '#endif',
- \ 'int func_a(void);',
- \ '#ifdef __cplusplus',
- \ '}',
- \ '#endif'
- \ ]
+ let without_ind =<< trim [CODE]
+ #ifdef __cplusplus
+ extern "C" {
+ #endif
+ int func_a(void);
+ #ifdef __cplusplus
+ }
+ #endif
+ [CODE]
- let with_ind = [
- \ '#ifdef __cplusplus',
- \ 'extern "C" {',
- \ '#endif',
- \ "\tint func_a(void);",
- \ '#ifdef __cplusplus',
- \ '}',
- \ '#endif'
- \ ]
+ let with_ind =<< trim [CODE]
+ #ifdef __cplusplus
+ extern "C" {
+ #endif
+ int func_a(void);
+ #ifdef __cplusplus
+ }
+ #endif
+ [CODE]
new
setlocal cindent cinoptions=E0
call setline(1, without_ind)
@@ -89,16 +89,32 @@ func Test_cindent_expr()
return v:lnum == 1 ? shiftwidth() : 0
endfunc
setl expandtab sw=8 indentkeys+=; indentexpr=MyIndentFunction()
- call setline(1, ['var_a = something()', 'b = something()'])
+ let testinput =<< trim [CODE]
+ var_a = something()
+ b = something()
+ [CODE]
+ call setline(1, testinput)
call cursor(1, 1)
call feedkeys("^\<c-v>j$A;\<esc>", 'tnix')
- call assert_equal([' var_a = something();', 'b = something();'], getline(1, '$'))
+ let expected =<< [CODE]
+ var_a = something();
+b = something();
+[CODE]
+ call assert_equal(expected, getline(1, '$'))
%d
- call setline(1, [' var_a = something()', ' b = something()'])
+ let testinput =<< [CODE]
+ var_a = something()
+ b = something()
+[CODE]
+ call setline(1, testinput)
call cursor(1, 1)
call feedkeys("^\<c-v>j$A;\<esc>", 'tnix')
- call assert_equal([' var_a = something();', ' b = something()'], getline(1, '$'))
+ let expected =<< [CODE]
+ var_a = something();
+ b = something()
+[CODE]
+ call assert_equal(expected, getline(1, '$'))
bw!
endfunc
diff --git a/src/nvim/testdir/test_clientserver.vim b/src/nvim/testdir/test_clientserver.vim
index 813cb338a5..3377f86126 100644
--- a/src/nvim/testdir/test_clientserver.vim
+++ b/src/nvim/testdir/test_clientserver.vim
@@ -28,12 +28,11 @@ func Test_client_server()
let name = 'XVIMTEST'
let cmd .= ' --servername ' . name
let job = job_start(cmd, {'stoponexit': 'kill', 'out_io': 'null'})
- call WaitFor({-> job_status(job) == "run"})
+ call WaitForAssert({-> assert_equal("run", job_status(job))})
" Takes a short while for the server to be active.
" When using valgrind it takes much longer.
- call WaitFor('serverlist() =~ "' . name . '"')
- call assert_match(name, serverlist())
+ call WaitForAssert({-> assert_match(name, serverlist())})
call remote_foreground(name)
@@ -54,12 +53,10 @@ func Test_client_server()
endif
" Wait for the server to be up and answering requests.
sleep 100m
- call WaitFor('remote_expr("' . name . '", "v:version", "", 1) != ""')
- call assert_true(remote_expr(name, "v:version", "", 1) != "")
+ call WaitForAssert({-> assert_true(remote_expr(name, "v:version", "", 1) != "")})
call remote_send(name, ":let testvar = 'maybe'\<CR>")
- call WaitFor('remote_expr("' . name . '", "testvar", "", 1) == "maybe"')
- call assert_equal('maybe', remote_expr(name, "testvar", "", 2))
+ call WaitForAssert({-> assert_equal('maybe', remote_expr(name, "testvar", "", 2))})
endif
call assert_fails('call remote_send("XXX", ":let testvar = ''yes''\<CR>")', 'E241')
@@ -94,7 +91,7 @@ func Test_client_server()
call remote_send(name, ":qa!\<CR>")
try
- call WaitFor({-> job_status(job) == "dead"})
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
finally
if job_status(job) != 'dead'
call assert_report('Server did not exit')
diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim
index e6aafd964b..9c3c33a943 100644
--- a/src/nvim/testdir/test_cmdline.vim
+++ b/src/nvim/testdir/test_cmdline.vim
@@ -1,6 +1,5 @@
" Tests for editing the command line.
-
func Test_complete_tab()
call writefile(['testfile'], 'Xtestfile')
call feedkeys(":e Xtestf\t\r", "tx")
@@ -79,26 +78,45 @@ func Test_map_completion()
call feedkeys(":map <silent> <sp\<Tab>\<Home>\"\<CR>", 'xt')
call assert_equal('"map <silent> <special>', getreg(':'))
+ map <Middle>x middle
+
map ,f commaf
map ,g commaf
+ map <Left> left
+ map <A-Left>x shiftleft
call feedkeys(":map ,\<Tab>\<Home>\"\<CR>", 'xt')
call assert_equal('"map ,f', getreg(':'))
call feedkeys(":map ,\<Tab>\<Tab>\<Home>\"\<CR>", 'xt')
call assert_equal('"map ,g', getreg(':'))
+ call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"map <Left>', getreg(':'))
+ call feedkeys(":map <A-Left>\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal("\"map <A-Left>\<Tab>", getreg(':'))
unmap ,f
unmap ,g
+ unmap <Left>
+ unmap <A-Left>x
set cpo-=< cpo-=B cpo-=k
map <Left> left
call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt')
call assert_equal('"map <Left>', getreg(':'))
+ call feedkeys(":map <M\<Tab>\<Home>\"\<CR>", 'xt')
+ " call assert_equal("\"map <M\<Tab>", getreg(':'))
unmap <Left>
" set cpo+=<
map <Left> left
+ exe "set t_k6=\<Esc>[17~"
+ call feedkeys(":map \<Esc>[17~x f6x\<CR>", 'xt')
call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt')
call assert_equal('"map <Left>', getreg(':'))
+ if !has('gui_running')
+ call feedkeys(":map \<Esc>[17~\<Tab>\<Home>\"\<CR>", 'xt')
+ " call assert_equal("\"map <F6>x", getreg(':'))
+ endif
unmap <Left>
+ call feedkeys(":unmap \<Esc>[17~x\<CR>", 'xt')
set cpo-=<
set cpo+=B
@@ -114,6 +132,9 @@ func Test_map_completion()
call assert_equal('"map <Left>', getreg(':'))
unmap <Left>
" set cpo-=k
+
+ unmap <Middle>x
+ set cpo&vim
endfunc
func Test_match_completion()
@@ -160,6 +181,7 @@ func Test_expr_completion()
endif
for cmd in [
\ 'let a = ',
+ \ 'const a = ',
\ 'if',
\ 'elseif',
\ 'while',
@@ -302,7 +324,7 @@ func Test_getcompletion()
call assert_equal([], l)
let l = getcompletion('.', 'shellcmd')
- call assert_equal(['./', '../'], l[0:1])
+ call assert_equal(['./', '../'], filter(l, 'v:val =~ "\\./"'))
call assert_equal(-1, match(l[2:], '^\.\.\?/$'))
let root = has('win32') ? 'C:\\' : '/'
let l = getcompletion(root, 'shellcmd')
@@ -376,6 +398,29 @@ func Test_getcompletion()
call assert_fails('call getcompletion("", "burp")', 'E475:')
endfunc
+func Test_shellcmd_completion()
+ let save_path = $PATH
+
+ call mkdir('Xpathdir/Xpathsubdir', 'p')
+ call writefile([''], 'Xpathdir/Xfile.exe')
+ call setfperm('Xpathdir/Xfile.exe', 'rwx------')
+
+ " Set PATH to example directory without trailing slash.
+ let $PATH = getcwd() . '/Xpathdir'
+
+ " Test for the ":!<TAB>" case. Previously, this would include subdirs of
+ " dirs in the PATH, even though they won't be executed. We check that only
+ " subdirs of the PWD and executables from the PATH are included in the
+ " suggestions.
+ let actual = getcompletion('X', 'shellcmd')
+ let expected = map(filter(glob('*', 0, 1), 'isdirectory(v:val) && v:val[0] == "X"'), 'v:val . "/"')
+ call insert(expected, 'Xfile.exe')
+ call assert_equal(expected, actual)
+
+ call delete('Xpathdir', 'rf')
+ let $PATH = save_path
+endfunc
+
func Test_expand_star_star()
call mkdir('a/b', 'p')
call writefile(['asdfasdf'], 'a/b/fileXname')
@@ -477,6 +522,51 @@ func Test_cmdline_complete_user_cmd()
delcommand Foo
endfunc
+func Test_cmdline_complete_user_names()
+ if has('unix') && executable('whoami')
+ let whoami = systemlist('whoami')[0]
+ let first_letter = whoami[0]
+ if len(first_letter) > 0
+ " Trying completion of :e ~x where x is the first letter of
+ " the user name should complete to at least the user name.
+ call feedkeys(':e ~' . first_letter . "\<c-a>\<c-B>\"\<cr>", 'tx')
+ call assert_match('^"e \~.*\<' . whoami . '\>', @:)
+ endif
+ endif
+ if has('win32')
+ " Just in case: check that the system has an Administrator account.
+ let names = system('net user')
+ if names =~ 'Administrator'
+ " Trying completion of :e ~A should complete to Administrator.
+ " There could be other names starting with "A" before Administrator.
+ call feedkeys(':e ~A' . "\<c-a>\<c-B>\"\<cr>", 'tx')
+ call assert_match('^"e \~.*Administrator', @:)
+ endif
+ endif
+endfunc
+
+funct Test_cmdline_complete_languages()
+ let lang = substitute(execute('language messages'), '.*"\(.*\)"$', '\1', '')
+
+ call feedkeys(":language \<c-a>\<c-b>\"\<cr>", 'tx')
+ call assert_match('^"language .*\<ctype\>.*\<messages\>.*\<time\>', @:)
+
+ if has('unix')
+ " TODO: these tests don't work on Windows. lang appears to be 'C'
+ " but C does not appear in the completion. Why?
+ call assert_match('^"language .*\<' . lang . '\>', @:)
+
+ call feedkeys(":language messages \<c-a>\<c-b>\"\<cr>", 'tx')
+ call assert_match('^"language .*\<' . lang . '\>', @:)
+
+ call feedkeys(":language ctype \<c-a>\<c-b>\"\<cr>", 'tx')
+ call assert_match('^"language .*\<' . lang . '\>', @:)
+
+ call feedkeys(":language time \<c-a>\<c-b>\"\<cr>", 'tx')
+ call assert_match('^"language .*\<' . lang . '\>', @:)
+ endif
+endfunc
+
func Test_cmdline_write_alternatefile()
new
call setline('.', ['one', 'two'])
@@ -529,6 +619,8 @@ func Check_cmdline(cmdtype)
return ''
endfunc
+set cpo&
+
func Test_getcmdtype()
call feedkeys(":MyCmd a\<C-R>=Check_cmdline(':')\<CR>\<Esc>", "xt")
@@ -569,6 +661,37 @@ func Test_getcmdwintype()
call assert_equal('', getcmdwintype())
endfunc
+func Test_getcmdwin_autocmd()
+ let s:seq = []
+ augroup CmdWin
+ au WinEnter * call add(s:seq, 'WinEnter ' .. win_getid())
+ au WinLeave * call add(s:seq, 'WinLeave ' .. win_getid())
+ au BufEnter * call add(s:seq, 'BufEnter ' .. bufnr())
+ au BufLeave * call add(s:seq, 'BufLeave ' .. bufnr())
+ au CmdWinEnter * call add(s:seq, 'CmdWinEnter ' .. win_getid())
+ au CmdWinLeave * call add(s:seq, 'CmdWinLeave ' .. win_getid())
+
+ let org_winid = win_getid()
+ let org_bufnr = bufnr()
+ call feedkeys("q::let a = getcmdwintype()\<CR>:let s:cmd_winid = win_getid()\<CR>:let s:cmd_bufnr = bufnr()\<CR>:q\<CR>", 'x!')
+ call assert_equal(':', a)
+ call assert_equal([
+ \ 'WinLeave ' .. org_winid,
+ \ 'WinEnter ' .. s:cmd_winid,
+ \ 'BufLeave ' .. org_bufnr,
+ \ 'BufEnter ' .. s:cmd_bufnr,
+ \ 'CmdWinEnter ' .. s:cmd_winid,
+ \ 'CmdWinLeave ' .. s:cmd_winid,
+ \ 'BufLeave ' .. s:cmd_bufnr,
+ \ 'WinLeave ' .. s:cmd_winid,
+ \ 'WinEnter ' .. org_winid,
+ \ 'BufEnter ' .. org_bufnr,
+ \ ], s:seq)
+
+ au!
+ augroup END
+endfunc
+
func Test_verbosefile()
set verbosefile=Xlog
echomsg 'foo'
@@ -628,4 +751,7 @@ func Test_cmdline_overstrike()
let &encoding = encoding_save
endfunc
-set cpo&
+func Test_cmdwin_feedkeys()
+ " This should not generate E488
+ call feedkeys("q:\<CR>", 'x')
+endfunc
diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim
index 46c14d8bc3..40d3cdbdae 100644
--- a/src/nvim/testdir/test_compiler.vim
+++ b/src/nvim/testdir/test_compiler.vim
@@ -10,6 +10,10 @@ func Test_compiler()
unlet $LANG
endif
+ " %:S does not work properly with 'shellslash' set
+ let save_shellslash = &shellslash
+ set noshellslash
+
e Xfoo.pl
compiler perl
call assert_equal('perl', b:current_compiler)
@@ -24,9 +28,11 @@ func Test_compiler()
w!
call feedkeys(":make\<CR>\<CR>", 'tx')
let a=execute('clist')
- call assert_match("\n 1 Xfoo.pl:3: Global symbol \"\$foo\" "
- \ . "requires explicit package name", a)
+ call assert_match('\n \d\+ Xfoo.pl:3: Global symbol "$foo" '
+ \ . 'requires explicit package name', a)
+
+ let &shellslash = save_shellslash
call delete('Xfoo.pl')
bw!
endfunc
diff --git a/src/nvim/testdir/test_const.vim b/src/nvim/testdir/test_const.vim
index 06062c5e58..eaf200e9bb 100644
--- a/src/nvim/testdir/test_const.vim
+++ b/src/nvim/testdir/test_const.vim
@@ -176,6 +176,26 @@ func Test_cannot_modify_existing_variable()
call assert_fails('const [i2, f2, s2] = [1, 1.1, "vim"]', 'E995:')
endfunc
+func Test_const_with_condition()
+ const x = 0
+ if 0 | const x = 1 | endif
+ call assert_equal(0, x)
+endfunc
+
+func Test_lockvar()
+ let x = 'hello'
+ lockvar x
+ call assert_fails('let x = "there"', 'E741')
+ if 0 | unlockvar x | endif
+ call assert_fails('let x = "there"', 'E741')
+ unlockvar x
+ let x = 'there'
+
+ if 0 | lockvar x | endif
+ let x = 'again'
+endfunc
+
+
func Test_const_with_index_access()
let l = [1, 2, 3]
call assert_fails('const l[0] = 4', 'E996:')
diff --git a/src/nvim/testdir/test_debugger.vim b/src/nvim/testdir/test_debugger.vim
index 3ef460b4fe..130bcf8910 100644
--- a/src/nvim/testdir/test_debugger.vim
+++ b/src/nvim/testdir/test_debugger.vim
@@ -26,27 +26,29 @@ func Test_Debugger()
endif
" Create a Vim script with some functions
- call writefile([
- \ 'func Foo()',
- \ ' let var1 = 1',
- \ ' let var2 = Bar(var1) + 9',
- \ ' return var2',
- \ 'endfunc',
- \ 'func Bar(var)',
- \ ' let var1 = 2 + a:var',
- \ ' let var2 = Bazz(var1) + 4',
- \ ' return var2',
- \ 'endfunc',
- \ 'func Bazz(var)',
- \ ' try',
- \ ' let var1 = 3 + a:var',
- \ ' let var3 = "another var"',
- \ ' let var3 = "value2"',
- \ ' catch',
- \ ' let var4 = "exception"',
- \ ' endtry',
- \ ' return var1',
- \ 'endfunc'], 'Xtest.vim')
+ let lines =<< trim END
+ func Foo()
+ let var1 = 1
+ let var2 = Bar(var1) + 9
+ return var2
+ endfunc
+ func Bar(var)
+ let var1 = 2 + a:var
+ let var2 = Bazz(var1) + 4
+ return var2
+ endfunc
+ func Bazz(var)
+ try
+ let var1 = 3 + a:var
+ let var3 = "another var"
+ let var3 = "value2"
+ catch
+ let var4 = "exception"
+ endtry
+ return var1
+ endfunc
+ END
+ call writefile(lines, 'Xtest.vim')
" Start Vim in a terminal
let buf = RunVimInTerminal('-S Xtest.vim', {})
@@ -294,11 +296,13 @@ func Test_Debugger()
" Tests for :breakadd file and :breakadd here
" Breakpoints should be set before sourcing the file
- call writefile([
- \ 'let var1 = 10',
- \ 'let var2 = 20',
- \ 'let var3 = 30',
- \ 'let var4 = 40'], 'Xtest.vim')
+ let lines =<< trim END
+ let var1 = 10
+ let var2 = 20
+ let var3 = 30
+ let var4 = 40
+ END
+ call writefile(lines, 'Xtest.vim')
" Start Vim in a terminal
let buf = RunVimInTerminal('Xtest.vim', {})
diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim
index 57b19aa817..21e0271bda 100644
--- a/src/nvim/testdir/test_diffmode.vim
+++ b/src/nvim/testdir/test_diffmode.vim
@@ -773,3 +773,28 @@ func Test_diff_of_diff()
call StopVimInTerminal(buf)
call delete('Xtest_diff_diff')
endfunc
+
+func CloseoffSetup()
+ enew
+ call setline(1, ['one', 'two', 'three'])
+ diffthis
+ new
+ call setline(1, ['one', 'tow', 'three'])
+ diffthis
+ call assert_equal(1, &diff)
+ only!
+endfunc
+
+func Test_diff_closeoff()
+ " "closeoff" included by default: last diff win gets 'diff' reset'
+ call CloseoffSetup()
+ call assert_equal(0, &diff)
+ enew!
+
+ " "closeoff" excluded: last diff win keeps 'diff' set'
+ set diffopt-=closeoff
+ call CloseoffSetup()
+ call assert_equal(1, &diff)
+ diffoff!
+ enew!
+endfunc
diff --git a/src/nvim/testdir/test_digraph.vim b/src/nvim/testdir/test_digraph.vim
index 62a5da33df..5da05e85b5 100644
--- a/src/nvim/testdir/test_digraph.vim
+++ b/src/nvim/testdir/test_digraph.vim
@@ -465,4 +465,17 @@ func Test_show_digraph()
bwipe!
endfunc
+func Test_show_digraph_cp1251()
+ throw 'skipped: Nvim supports ''utf8'' encoding only'
+ if !has('multi_byte')
+ return
+ endif
+ new
+ set encoding=cp1251
+ call Put_Dig("='")
+ call assert_equal("\n<\xfa> <|z> <M-z> 250, Hex fa, Oct 372, Digr ='", execute('ascii'))
+ set encoding=utf-8
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_display.vim b/src/nvim/testdir/test_display.vim
index 5feb59eef1..1c2f5a05ff 100644
--- a/src/nvim/testdir/test_display.vim
+++ b/src/nvim/testdir/test_display.vim
@@ -69,3 +69,59 @@ func! Test_display_foldtext_mbyte()
set foldtext& fillchars& foldmethod& fdc&
bw!
endfunc
+
+func Test_display_listchars_precedes()
+ set fillchars+=vert:\|
+ call NewWindow(10, 10)
+ " Need a physical line that wraps over the complete
+ " window size
+ call append(0, repeat('aaa aaa aa ', 10))
+ call append(1, repeat(['bbb bbb bbb bbb'], 2))
+ " remove blank trailing line
+ $d
+ set list nowrap
+ call cursor(1, 1)
+ " move to end of line and scroll 2 characters back
+ norm! $2zh
+ let lines=ScreenLines([1,4], winwidth(0)+1)
+ let expect = [
+ \ " aaa aa $ |",
+ \ "$ |",
+ \ "$ |",
+ \ "~ |",
+ \ ]
+ call assert_equal(expect, lines)
+ set list listchars+=precedes:< nowrap
+ call cursor(1, 1)
+ " move to end of line and scroll 2 characters back
+ norm! $2zh
+ let lines = ScreenLines([1,4], winwidth(0)+1)
+ let expect = [
+ \ "<aaa aa $ |",
+ \ "< |",
+ \ "< |",
+ \ "~ |",
+ \ ]
+ call assert_equal(expect, lines)
+ set wrap
+ call cursor(1, 1)
+ " the complete line should be displayed in the window
+ norm! $
+
+ let lines = ScreenLines([1,10], winwidth(0)+1)
+ let expect = [
+ \ "<aaa aaa a|",
+ \ "a aaa aaa |",
+ \ "aa aaa aaa|",
+ \ " aa aaa aa|",
+ \ "a aa aaa a|",
+ \ "aa aa aaa |",
+ \ "aaa aa aaa|",
+ \ " aaa aa aa|",
+ \ "a aaa aa a|",
+ \ "aa aaa aa |",
+ \ ]
+ call assert_equal(expect, lines)
+ set list& listchars& wrap&
+ bw!
+endfunc
diff --git a/src/nvim/testdir/test_exit.vim b/src/nvim/testdir/test_exit.vim
index 8f02fd29e3..99a401d4a4 100644
--- a/src/nvim/testdir/test_exit.vim
+++ b/src/nvim/testdir/test_exit.vim
@@ -3,52 +3,78 @@
source shared.vim
func Test_exiting()
- let after = [
- \ 'au QuitPre * call writefile(["QuitPre"], "Xtestout")',
- \ 'au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")',
- \ 'quit',
- \ ]
+ let after =<< trim [CODE]
+ au QuitPre * call writefile(["QuitPre"], "Xtestout")
+ au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")
+ quit
+ [CODE]
+
if RunVim([], after, '')
call assert_equal(['QuitPre', 'ExitPre'], readfile('Xtestout'))
endif
call delete('Xtestout')
- let after = [
- \ 'au QuitPre * call writefile(["QuitPre"], "Xtestout")',
- \ 'au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")',
- \ 'help',
- \ 'wincmd w',
- \ 'quit',
- \ ]
+ let after =<< trim [CODE]
+ au QuitPre * call writefile(["QuitPre"], "Xtestout")
+ au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")
+ help
+ wincmd w
+ quit
+ [CODE]
+
if RunVim([], after, '')
call assert_equal(['QuitPre', 'ExitPre'], readfile('Xtestout'))
endif
call delete('Xtestout')
- let after = [
- \ 'au QuitPre * call writefile(["QuitPre"], "Xtestout")',
- \ 'au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")',
- \ 'split',
- \ 'new',
- \ 'qall',
- \ ]
+ let after =<< trim [CODE]
+ au QuitPre * call writefile(["QuitPre"], "Xtestout")
+ au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")
+ split
+ new
+ qall
+ [CODE]
+
if RunVim([], after, '')
call assert_equal(['QuitPre', 'ExitPre'], readfile('Xtestout'))
endif
call delete('Xtestout')
- let after = [
- \ 'au QuitPre * call writefile(["QuitPre"], "Xtestout", "a")',
- \ 'au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")',
- \ 'augroup nasty',
- \ ' au ExitPre * split',
- \ 'augroup END',
- \ 'quit',
- \ 'augroup nasty',
- \ ' au! ExitPre',
- \ 'augroup END',
- \ 'quit',
- \ ]
+ " ExitPre autocommand splits the window, so that it's no longer the last one.
+ let after =<< trim [CODE]
+ au QuitPre * call writefile(["QuitPre"], "Xtestout", "a")
+ au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")
+ augroup nasty
+ au ExitPre * split
+ augroup END
+ quit
+ augroup nasty
+ au! ExitPre
+ augroup END
+ quit
+ [CODE]
+
+ if RunVim([], after, '')
+ call assert_equal(['QuitPre', 'ExitPre', 'QuitPre', 'ExitPre'],
+ \ readfile('Xtestout'))
+ endif
+ call delete('Xtestout')
+
+ " ExitPre autocommand splits and closes the window, so that there is still
+ " one window but it's a different one.
+ let after =<< trim [CODE]
+ au QuitPre * call writefile(["QuitPre"], "Xtestout", "a")
+ au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")
+ augroup nasty
+ au ExitPre * split | only
+ augroup END
+ quit
+ augroup nasty
+ au! ExitPre
+ augroup END
+ quit
+ [CODE]
+
if RunVim([], after, '')
call assert_equal(['QuitPre', 'ExitPre', 'QuitPre', 'ExitPre'],
\ readfile('Xtestout'))
diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim
index 4f99625e73..dd546dbf71 100644
--- a/src/nvim/testdir/test_expr.vim
+++ b/src/nvim/testdir/test_expr.vim
@@ -279,6 +279,9 @@ function Test_printf_misc()
call assert_equal('abc ', printf('%-4s', 'abc'))
call assert_equal('abc ', printf('%-4S', 'abc'))
+ call assert_equal('ðŸ', printf('%.2S', 'ðŸðŸ'))
+ call assert_equal('', printf('%.1S', 'ðŸðŸ'))
+
call assert_equal('1%', printf('%d%%', 1))
endfunc
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index 7512d599b8..e085f58e56 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -122,6 +122,7 @@ let s:filename_checks = {
\ 'cvs': ['cvs123'],
\ 'cvsrc': ['.cvsrc'],
\ 'cynpp': ['file.cyn'],
+ \ 'dart': ['file.dart', 'file.drt'],
\ 'datascript': ['file.ds'],
\ 'dcd': ['file.dcd'],
\ 'debcontrol': ['/debian/control'],
@@ -201,6 +202,7 @@ let s:filename_checks = {
\ 'hex': ['file.hex', 'file.h32'],
\ 'hgcommit': ['hg-editor-file.txt'],
\ 'hog': ['file.hog', 'snort.conf', 'vision.conf'],
+ \ 'hollywood': ['file.hws'],
\ 'hostconf': ['/etc/host.conf'],
\ 'hostsaccess': ['/etc/hosts.allow', '/etc/hosts.deny'],
\ 'template': ['file.tmpl'],
@@ -221,7 +223,7 @@ let s:filename_checks = {
\ 'jam': ['file.jpl', 'file.jpr'],
\ 'java': ['file.java', 'file.jav'],
\ 'javacc': ['file.jj', 'file.jjt'],
- \ 'javascript': ['file.js', 'file.javascript', 'file.es', 'file.mjs'],
+ \ 'javascript': ['file.js', 'file.javascript', 'file.es', 'file.mjs', 'file.cjs'],
\ 'javascriptreact': ['file.jsx'],
\ 'jess': ['file.clp'],
\ 'jgraph': ['file.jgr'],
@@ -273,6 +275,7 @@ let s:filename_checks = {
\ 'mason': ['file.mason', 'file.mhtml', 'file.comp'],
\ 'master': ['file.mas', 'file.master'],
\ 'mel': ['file.mel'],
+ \ 'meson': ['meson.build', 'meson_options.txt'],
\ 'messages': ['/log/auth', '/log/cron', '/log/daemon', '/log/debug', '/log/kern', '/log/lpr', '/log/mail', '/log/messages', '/log/news/news', '/log/syslog', '/log/user',
\ '/log/auth.log', '/log/cron.log', '/log/daemon.log', '/log/debug.log', '/log/kern.log', '/log/lpr.log', '/log/mail.log', '/log/messages.log', '/log/news/news.log', '/log/syslog.log', '/log/user.log',
\ '/log/auth.err', '/log/cron.err', '/log/daemon.err', '/log/debug.err', '/log/kern.err', '/log/lpr.err', '/log/mail.err', '/log/messages.err', '/log/news/news.err', '/log/syslog.err', '/log/user.err',
diff --git a/src/nvim/testdir/test_fnamemodify.vim b/src/nvim/testdir/test_fnamemodify.vim
new file mode 100644
index 0000000000..116d23ba88
--- /dev/null
+++ b/src/nvim/testdir/test_fnamemodify.vim
@@ -0,0 +1,75 @@
+" Test filename modifiers.
+
+func Test_fnamemodify()
+ let save_home = $HOME
+ let save_shell = &shell
+ let $HOME = fnamemodify('.', ':p:h:h')
+ set shell=sh
+
+ call assert_equal('/', fnamemodify('.', ':p')[-1:])
+ call assert_equal('r', fnamemodify('.', ':p:h')[-1:])
+ call assert_equal('t', fnamemodify('test.out', ':p')[-1:])
+ call assert_equal('test.out', fnamemodify('test.out', ':.'))
+ call assert_equal('a', fnamemodify('../testdir/a', ':.'))
+ call assert_equal('~/testdir/test.out', fnamemodify('test.out', ':~'))
+ call assert_equal('~/testdir/a', fnamemodify('../testdir/a', ':~'))
+ call assert_equal('a', fnamemodify('../testdir/a', ':t'))
+ call assert_equal('', fnamemodify('.', ':p:t'))
+ call assert_equal('test.out', fnamemodify('test.out', ':p:t'))
+ call assert_equal('out', fnamemodify('test.out', ':p:e'))
+ call assert_equal('out', fnamemodify('test.out', ':p:t:e'))
+ call assert_equal('abc.fb2.tar', fnamemodify('abc.fb2.tar.gz', ':r'))
+ call assert_equal('abc.fb2', fnamemodify('abc.fb2.tar.gz', ':r:r'))
+ call assert_equal('abc', fnamemodify('abc.fb2.tar.gz', ':r:r:r'))
+ call assert_equal('testdir/abc.fb2', substitute(fnamemodify('abc.fb2.tar.gz', ':p:r:r'), '.*\(testdir/.*\)', '\1', ''))
+ call assert_equal('gz', fnamemodify('abc.fb2.tar.gz', ':e'))
+ call assert_equal('tar.gz', fnamemodify('abc.fb2.tar.gz', ':e:e'))
+ call assert_equal('fb2.tar.gz', fnamemodify('abc.fb2.tar.gz', ':e:e:e'))
+ call assert_equal('fb2.tar.gz', fnamemodify('abc.fb2.tar.gz', ':e:e:e:e'))
+ call assert_equal('tar', fnamemodify('abc.fb2.tar.gz', ':e:e:r'))
+
+ call assert_equal('''abc def''', fnamemodify('abc def', ':S'))
+ call assert_equal('''abc" "def''', fnamemodify('abc" "def', ':S'))
+ call assert_equal('''abc"%"def''', fnamemodify('abc"%"def', ':S'))
+ call assert_equal('''abc''\'''' ''\''''def''', fnamemodify('abc'' ''def', ':S'))
+ call assert_equal('''abc''\''''%''\''''def''', fnamemodify('abc''%''def', ':S'))
+ sp test_alot.vim
+ call assert_equal(expand('%:r:S'), shellescape(expand('%:r')))
+ call assert_equal('test_alot,''test_alot'',test_alot.vim', join([expand('%:r'), expand('%:r:S'), expand('%')], ','))
+ quit
+
+ call assert_equal("'abc\ndef'", fnamemodify("abc\ndef", ':S'))
+ set shell=tcsh
+ call assert_equal("'abc\\\ndef'", fnamemodify("abc\ndef", ':S'))
+
+ let $HOME = save_home
+ let &shell = save_shell
+endfunc
+
+func Test_fnamemodify_er()
+ call assert_equal("with", fnamemodify("path/to/file.with.extensions", ':e:e:r:r'))
+
+ call assert_equal('c', fnamemodify('a.c', ':e'))
+ call assert_equal('c', fnamemodify('a.c', ':e:e'))
+ call assert_equal('c', fnamemodify('a.c', ':e:e:r'))
+ call assert_equal('c', fnamemodify('a.c', ':e:e:r:r'))
+
+ call assert_equal('rb', fnamemodify('a.spec.rb', ':e:r'))
+ call assert_equal('rb', fnamemodify('a.spec.rb', ':e:r'))
+ call assert_equal('spec.rb', fnamemodify('a.spec.rb', ':e:e'))
+ call assert_equal('spec', fnamemodify('a.spec.rb', ':e:e:r'))
+ call assert_equal('spec', fnamemodify('a.spec.rb', ':e:e:r:r'))
+ call assert_equal('spec', fnamemodify('a.b.spec.rb', ':e:e:r'))
+ call assert_equal('b.spec', fnamemodify('a.b.spec.rb', ':e:e:e:r'))
+ call assert_equal('b', fnamemodify('a.b.spec.rb', ':e:e:e:r:r'))
+
+ call assert_equal('spec', fnamemodify('a.b.spec.rb', ':r:e'))
+ call assert_equal('b', fnamemodify('a.b.spec.rb', ':r:r:e'))
+
+ call assert_equal('c', fnamemodify('a.b.c.d.e', ':r:r:e'))
+ call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e'))
+
+ " :e never includes the whole filename, so "a.b":e:e:e --> "b"
+ call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e'))
+ call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e:e'))
+endfunc
diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim
index 3cb42579be..324f3f8cf2 100644
--- a/src/nvim/testdir/test_fold.vim
+++ b/src/nvim/testdir/test_fold.vim
@@ -520,17 +520,18 @@ func Test_fold_create_marker_in_C()
set fdm=marker fdl=9
set filetype=c
- let content = [
- \ '/*',
- \ ' * comment',
- \ ' * ',
- \ ' *',
- \ ' */',
- \ 'int f(int* p) {',
- \ ' *p = 3;',
- \ ' return 0;',
- \ '}'
- \]
+ let content =<< trim [CODE]
+ /*
+ * comment
+ *
+ *
+ */
+ int f(int* p) {
+ *p = 3;
+ return 0;
+ }
+ [CODE]
+
for c in range(len(content) - 1)
bw!
call append(0, content)
@@ -756,3 +757,15 @@ func Test_fold_delete_with_marker()
bwipe!
bwipe!
endfunc
+
+func Test_fold_delete_with_marker_and_whichwrap()
+ new
+ let content1 = ['']
+ let content2 = ['folded line 1 "{{{1', ' test', ' test2', ' test3', '', 'folded line 2 "{{{1', ' test', ' test2', ' test3']
+ call setline(1, content1 + content2)
+ set fdm=marker ww+=l
+ normal! x
+ call assert_equal(content2, getline(1, '$'))
+ set fdm& ww&
+ bwipe!
+endfunc
diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
index a36c51f56f..7822507f86 100644
--- a/src/nvim/testdir/test_functions.vim
+++ b/src/nvim/testdir/test_functions.vim
@@ -1132,6 +1132,13 @@ func Test_reg_executing_and_recording()
" :normal command saves and restores reg_executing
let s:reg_stat = ''
+ let @q = ":call TestFunc()\<CR>:call s:save_reg_stat()\<CR>"
+ func TestFunc() abort
+ normal! ia
+ endfunc
+ call feedkeys("@q", 'xt')
+ call assert_equal(':q', s:reg_stat)
+ delfunc TestFunc
" getchar() command saves and restores reg_executing
map W :call TestFunc()<CR>
diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim
index accd21e9a3..d301874891 100644
--- a/src/nvim/testdir/test_gf.vim
+++ b/src/nvim/testdir/test_gf.vim
@@ -99,3 +99,28 @@ func Test_gf()
call delete('Xtest1')
call delete('Xtestgf')
endfunc
+
+func Test_gf_visual()
+ call writefile([], "Xtest_gf_visual")
+ new
+ call setline(1, 'XXXtest_gf_visualXXX')
+ set hidden
+
+ " Visually select Xtest_gf_visual and use gf to go to that file
+ norm! ttvtXgf
+ call assert_equal('Xtest_gf_visual', bufname('%'))
+
+ bwipe!
+ call delete('Xtest_gf_visual')
+ set hidden&
+endfunc
+
+func Test_gf_error()
+ new
+ call assert_fails('normal gf', 'E446:')
+ call assert_fails('normal gF', 'E446:')
+ call setline(1, '/doesnotexist')
+ call assert_fails('normal gf', 'E447:')
+ call assert_fails('normal gF', 'E447:')
+ bwipe!
+endfunc
diff --git a/src/nvim/testdir/test_gn.vim b/src/nvim/testdir/test_gn.vim
index 5e74289b00..d41675be0c 100644
--- a/src/nvim/testdir/test_gn.vim
+++ b/src/nvim/testdir/test_gn.vim
@@ -129,6 +129,33 @@ func Test_gn_command()
call assert_equal([' nnoremap', '', 'match'], getline(1,'$'))
sil! %d_
+ " make sure it works correctly for one-char wide search items
+ call setline('.', ['abcdefghi'])
+ let @/ = 'a'
+ exe "norm! 0fhvhhgNgU"
+ call assert_equal(['ABCDEFGHi'], getline(1,'$'))
+ call setline('.', ['abcdefghi'])
+ let @/ = 'b'
+ " this gn wraps around the end of the file
+ exe "norm! 0fhvhhgngU"
+ call assert_equal(['aBCDEFGHi'], getline(1,'$'))
+ sil! %d _
+ call setline('.', ['abcdefghi'])
+ let @/ = 'f'
+ exe "norm! 0vllgngU"
+ call assert_equal(['ABCDEFghi'], getline(1,'$'))
+ sil! %d _
+ call setline('.', ['12345678'])
+ let @/ = '5'
+ norm! gg0f7vhhhhgnd
+ call assert_equal(['12348'], getline(1,'$'))
+ sil! %d _
+ call setline('.', ['12345678'])
+ let @/ = '5'
+ norm! gg0f2vf7gNd
+ call assert_equal(['1678'], getline(1,'$'))
+ sil! %d _
+
set wrapscan&vim
set belloff&vim
endfu
diff --git a/src/nvim/testdir/test_goto.vim b/src/nvim/testdir/test_goto.vim
index c0235b1707..19513b315a 100644
--- a/src/nvim/testdir/test_goto.vim
+++ b/src/nvim/testdir/test_goto.vim
@@ -15,262 +15,283 @@ func XTest_goto_decl(cmd, lines, line, col)
endfunc
func Test_gD()
- let lines = [
- \ 'int x;',
- \ '',
- \ 'int func(void)',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int x;
+
+ int func(void)
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gD', lines, 1, 5)
endfunc
func Test_gD_too()
- let lines = [
- \ 'Filename x;',
- \ '',
- \ 'int Filename',
- \ 'int func() {',
- \ ' Filename x;',
- \ ' return x;',
- \ ]
+ let lines =<< trim [CODE]
+ Filename x;
+
+ int Filename
+ int func() {
+ Filename x;
+ return x;
+ [CODE]
+
call XTest_goto_decl('gD', lines, 1, 10)
endfunc
func Test_gD_comment()
- let lines = [
- \ '/* int x; */',
- \ 'int x;',
- \ '',
- \ 'int func(void)',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ /* int x; */
+ int x;
+
+ int func(void)
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gD', lines, 2, 5)
endfunc
func Test_gD_inline_comment()
- let lines = [
- \ 'int y /* , x */;',
- \ 'int x;',
- \ '',
- \ 'int func(void)',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int y /* , x */;
+ int x;
+
+ int func(void)
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gD', lines, 2, 5)
endfunc
func Test_gD_string()
- let lines = [
- \ 'char *s[] = "x";',
- \ 'int x = 1;',
- \ '',
- \ 'int func(void)',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ char *s[] = "x";
+ int x = 1;
+
+ int func(void)
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gD', lines, 2, 5)
endfunc
func Test_gD_string_same_line()
- let lines = [
- \ 'char *s[] = "x", int x = 1;',
- \ '',
- \ 'int func(void)',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ char *s[] = "x", int x = 1;
+
+ int func(void)
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gD', lines, 1, 22)
endfunc
func Test_gD_char()
- let lines = [
- \ "char c = 'x';",
- \ 'int x = 1;',
- \ '',
- \ 'int func(void)',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ char c = 'x';
+ int x = 1;
+
+ int func(void)
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gD', lines, 2, 5)
endfunc
func Test_gd()
- let lines = [
- \ 'int x;',
- \ '',
- \ 'int func(int x)',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int x;
+
+ int func(int x)
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 3, 14)
endfunc
func Test_gd_not_local()
- let lines = [
- \ 'int func1(void)',
- \ '{',
- \ ' return x;',
- \ '}',
- \ '',
- \ 'int func2(int x)',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int func1(void)
+ {
+ return x;
+ }
+
+ int func2(int x)
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 3, 10)
endfunc
func Test_gd_kr_style()
- let lines = [
- \ 'int func(x)',
- \ ' int x;',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int func(x)
+ int x;
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 2, 7)
endfunc
func Test_gd_missing_braces()
- let lines = [
- \ 'def func1(a)',
- \ ' a + 1',
- \ 'end',
- \ '',
- \ 'a = 1',
- \ '',
- \ 'def func2()',
- \ ' return a',
- \ 'end',
- \ ]
+ let lines =<< trim [CODE]
+ def func1(a)
+ a + 1
+ end
+
+ a = 1
+
+ def func2()
+ return a
+ end
+ [CODE]
+
call XTest_goto_decl('gd', lines, 1, 11)
endfunc
func Test_gd_comment()
- let lines = [
- \ 'int func(void)',
- \ '{',
- \ ' /* int x; */',
- \ ' int x;',
- \ ' return x;',
- \ '}',
- \]
+ let lines =<< trim [CODE]
+ int func(void)
+ {
+ /* int x; */
+ int x;
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 4, 7)
endfunc
func Test_gd_comment_in_string()
- let lines = [
- \ 'int func(void)',
- \ '{',
- \ ' char *s ="//"; int x;',
- \ ' int x;',
- \ ' return x;',
- \ '}',
- \]
+ let lines =<< trim [CODE]
+ int func(void)
+ {
+ char *s ="//"; int x;
+ int x;
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 3, 22)
endfunc
func Test_gd_string_in_comment()
set comments=
- let lines = [
- \ 'int func(void)',
- \ '{',
- \ ' /* " */ int x;',
- \ ' int x;',
- \ ' return x;',
- \ '}',
- \]
+ let lines =<< trim [CODE]
+ int func(void)
+ {
+ /* " */ int x;
+ int x;
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 3, 15)
set comments&
endfunc
func Test_gd_inline_comment()
- let lines = [
- \ 'int func(/* x is an int */ int x)',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int func(/* x is an int */ int x)
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 1, 32)
endfunc
func Test_gd_inline_comment_only()
- let lines = [
- \ 'int func(void) /* one lonely x */',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int func(void) /* one lonely x */
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 3, 10)
endfunc
func Test_gd_inline_comment_body()
- let lines = [
- \ 'int func(void)',
- \ '{',
- \ ' int y /* , x */;',
- \ '',
- \ ' for (/* int x = 0 */; y < 2; y++);',
- \ '',
- \ ' int x = 0;',
- \ '',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int func(void)
+ {
+ int y /* , x */;
+
+ for (/* int x = 0 */; y < 2; y++);
+
+ int x = 0;
+
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 7, 7)
endfunc
func Test_gd_trailing_multiline_comment()
- let lines = [
- \ 'int func(int x) /* x is an int */',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int func(int x) /* x is an int */
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 1, 14)
endfunc
func Test_gd_trailing_comment()
- let lines = [
- \ 'int func(int x) // x is an int',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int func(int x) // x is an int
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 1, 14)
endfunc
func Test_gd_string()
- let lines = [
- \ 'int func(void)',
- \ '{',
- \ ' char *s = "x";',
- \ ' int x = 1;',
- \ '',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int func(void)
+ {
+ char *s = "x";
+ int x = 1;
+
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 4, 7)
endfunc
func Test_gd_string_only()
- let lines = [
- \ 'int func(void)',
- \ '{',
- \ ' char *s = "x";',
- \ '',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int func(void)
+ {
+ char *s = "x";
+
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 5, 10)
endfunc
@@ -289,24 +310,25 @@ func Test_cursorline_keep_col()
endfunc
func Test_gd_local_block()
- let lines = [
- \ ' int main()',
- \ '{',
- \ ' char *a = "NOT NULL";',
- \ ' if(a)',
- \ ' {',
- \ ' char *b = a;',
- \ ' printf("%s\n", b);',
- \ ' }',
- \ ' else',
- \ ' {',
- \ ' char *b = "NULL";',
- \ ' return b;',
- \ ' }',
- \ '',
- \ ' return 0;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int main()
+ {
+ char *a = "NOT NULL";
+ if(a)
+ {
+ char *b = a;
+ printf("%s\n", b);
+ }
+ else
+ {
+ char *b = "NULL";
+ return b;
+ }
+
+ return 0;
+ }
+ [CODE]
+
call XTest_goto_decl('1gd', lines, 11, 11)
endfunc
diff --git a/src/nvim/testdir/test_help.vim b/src/nvim/testdir/test_help.vim
index ed3181564c..01fb9917e9 100644
--- a/src/nvim/testdir/test_help.vim
+++ b/src/nvim/testdir/test_help.vim
@@ -21,6 +21,12 @@ func Test_help_errors()
bwipe!
endfunc
+func Test_help_expr()
+ help expr-!~?
+ call assert_equal('eval.txt', expand('%:t'))
+ close
+endfunc
+
func Test_help_keyword()
new
set keywordprg=:help
diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim
index 7f52481ba8..52ec281d82 100644
--- a/src/nvim/testdir/test_ins_complete.vim
+++ b/src/nvim/testdir/test_ins_complete.vim
@@ -285,3 +285,21 @@ func Test_compl_feedkeys()
bwipe!
set completeopt&
endfunc
+
+func Test_compl_in_cmdwin()
+ set wildmenu wildchar=<Tab>
+ com! -nargs=1 -complete=command GetInput let input = <q-args>
+ com! -buffer TestCommand echo 'TestCommand'
+
+ let input = ''
+ call feedkeys("q:iGetInput T\<C-x>\<C-v>\<CR>", 'tx!')
+ call assert_equal('TestCommand', input)
+
+ let input = ''
+ call feedkeys("q::GetInput T\<Tab>\<CR>:q\<CR>", 'tx!')
+ call assert_equal('T', input)
+
+ delcom TestCommand
+ delcom GetInput
+ set wildmenu& wildchar&
+endfunc
diff --git a/src/nvim/testdir/test_join.vim b/src/nvim/testdir/test_join.vim
index 1c97414164..ecb55c9af6 100644
--- a/src/nvim/testdir/test_join.vim
+++ b/src/nvim/testdir/test_join.vim
@@ -9,6 +9,27 @@ func Test_join_with_count()
call setline(1, ['one', 'two', 'three', 'four'])
normal 10J
call assert_equal('one two three four', getline(1))
+
+ call setline(1, ['one', '', 'two'])
+ normal J
+ call assert_equal('one', getline(1))
+
+ call setline(1, ['one', ' ', 'two'])
+ normal J
+ call assert_equal('one', getline(1))
+
+ call setline(1, ['one', '', '', 'two'])
+ normal JJ
+ call assert_equal('one', getline(1))
+
+ call setline(1, ['one', ' ', ' ', 'two'])
+ normal JJ
+ call assert_equal('one', getline(1))
+
+ call setline(1, ['one', '', '', 'two'])
+ normal 2J
+ call assert_equal('one', getline(1))
+
quit!
endfunc
diff --git a/src/nvim/testdir/test_let.vim b/src/nvim/testdir/test_let.vim
index 8a6f1bc320..0b9331ee38 100644
--- a/src/nvim/testdir/test_let.vim
+++ b/src/nvim/testdir/test_let.vim
@@ -24,6 +24,10 @@ func Test_let()
let out = execute('let a {0 == 1 ? "a" : "b"}')
let s = "\na #1\nb #2"
call assert_equal(s, out)
+
+ let x = 0
+ if 0 | let x = 1 | endif
+ call assert_equal(0, x)
endfunc
func s:set_arg1(a) abort
@@ -140,3 +144,161 @@ func Test_let_varg_fail()
call assert_fails('call s:set_varg7(1)', 'E742:')
call s:set_varg8([0])
endfunction
+
+func Test_let_utf8_environment()
+ let $a = 'ĀĒĪŌŪã‚ã„ã†ãˆãŠ'
+ call assert_equal('ĀĒĪŌŪã‚ã„ã†ãˆãŠ', $a)
+endfunc
+
+func Test_let_heredoc_fails()
+ call assert_fails('let v =<< marker', 'E991:')
+
+ let text =<< trim END
+ func WrongSyntax()
+ let v =<< that there
+ endfunc
+ END
+ call writefile(text, 'XheredocFail')
+ call assert_fails('source XheredocFail', 'E126:')
+ call delete('XheredocFail')
+
+ let text =<< trim CodeEnd
+ func MissingEnd()
+ let v =<< END
+ endfunc
+ CodeEnd
+ call writefile(text, 'XheredocWrong')
+ call assert_fails('source XheredocWrong', 'E126:')
+ call delete('XheredocWrong')
+
+ let text =<< trim TEXTend
+ let v =<< " comment
+ TEXTend
+ call writefile(text, 'XheredocNoMarker')
+ call assert_fails('source XheredocNoMarker', 'E172:')
+ call delete('XheredocNoMarker')
+
+ let text =<< trim TEXTend
+ let v =<< text
+ TEXTend
+ call writefile(text, 'XheredocBadMarker')
+ call assert_fails('source XheredocBadMarker', 'E221:')
+ call delete('XheredocBadMarker')
+endfunc
+
+func Test_let_heredoc_trim_no_indent_marker()
+ let text =<< trim END
+ Text
+ with
+ indent
+END
+ call assert_equal(['Text', 'with', 'indent'], text)
+endfunc
+
+" Test for the setting a variable using the heredoc syntax
+func Test_let_heredoc()
+ let var1 =<< END
+Some sample text
+ Text with indent
+ !@#$%^&*()-+_={}|[]\~`:";'<>?,./
+END
+
+ call assert_equal(["Some sample text", "\tText with indent", " !@#$%^&*()-+_={}|[]\\~`:\";'<>?,./"], var1)
+
+ let var2 =<< XXX
+Editor
+XXX
+ call assert_equal(['Editor'], var2)
+
+ let var3 =<<END
+END
+ call assert_equal([], var3)
+
+ let var3 =<<END
+vim
+
+end
+ END
+END
+END
+ call assert_equal(['vim', '', 'end', ' END', 'END '], var3)
+
+ let var1 =<< trim END
+ Line1
+ Line2
+ Line3
+ END
+ END
+ call assert_equal(['Line1', ' Line2', "\tLine3", ' END'], var1)
+
+ let var1 =<< trim !!!
+ Line1
+ line2
+ Line3
+ !!!
+ !!!
+ call assert_equal(['Line1', ' line2', "\tLine3", '!!!',], var1)
+
+ let var1 =<< trim XX
+ Line1
+ XX
+ call assert_equal(['Line1'], var1)
+
+ " ignore "endfunc"
+ let var1 =<< END
+something
+endfunc
+END
+ call assert_equal(['something', 'endfunc'], var1)
+
+ " ignore "endfunc" with trim
+ let var1 =<< trim END
+ something
+ endfunc
+ END
+ call assert_equal(['something', 'endfunc'], var1)
+
+ " ignore "python << xx"
+ let var1 =<<END
+something
+python << xx
+END
+ call assert_equal(['something', 'python << xx'], var1)
+
+ " ignore "python << xx" with trim
+ let var1 =<< trim END
+ something
+ python << xx
+ END
+ call assert_equal(['something', 'python << xx'], var1)
+
+ " ignore "append"
+ let var1 =<< E
+something
+app
+E
+ call assert_equal(['something', 'app'], var1)
+
+ " ignore "append" with trim
+ let var1 =<< trim END
+ something
+ app
+ END
+ call assert_equal(['something', 'app'], var1)
+
+ let check = []
+ if 0
+ let check =<< trim END
+ from heredoc
+ END
+ endif
+ call assert_equal([], check)
+
+ " unpack assignment
+ let [a, b, c] =<< END
+ x
+ \y
+ z
+END
+ call assert_equal([' x', ' \y', ' z'], [a, b, c])
+endfunc
diff --git a/src/nvim/testdir/test_mksession_utf8.vim b/src/nvim/testdir/test_mksession_utf8.vim
index 67af3a9ca2..722fd28beb 100644
--- a/src/nvim/testdir/test_mksession_utf8.vim
+++ b/src/nvim/testdir/test_mksession_utf8.vim
@@ -65,34 +65,35 @@ func Test_mksession_utf8()
call wincol()
mksession! test_mks.out
let li = filter(readfile('test_mks.out'), 'v:val =~# "\\(^ *normal! 0\\|^ *exe ''normal!\\)"')
- let expected = [
- \ 'normal! 016|',
- \ 'normal! 016|',
- \ 'normal! 016|',
- \ 'normal! 08|',
- \ 'normal! 08|',
- \ 'normal! 016|',
- \ 'normal! 016|',
- \ 'normal! 016|',
- \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'",
- \ " normal! 016|",
- \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'",
- \ " normal! 016|",
- \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'",
- \ " normal! 016|",
- \ " exe 'normal! ' . s:c . '|zs' . 8 . '|'",
- \ " normal! 08|",
- \ " exe 'normal! ' . s:c . '|zs' . 8 . '|'",
- \ " normal! 08|",
- \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'",
- \ " normal! 016|",
- \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'",
- \ " normal! 016|",
- \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'",
- \ " normal! 016|",
- \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'",
- \ " normal! 016|"
- \ ]
+ let expected =<< trim [DATA]
+ normal! 016|
+ normal! 016|
+ normal! 016|
+ normal! 08|
+ normal! 08|
+ normal! 016|
+ normal! 016|
+ normal! 016|
+ exe 'normal! ' . s:c . '|zs' . 16 . '|'
+ normal! 016|
+ exe 'normal! ' . s:c . '|zs' . 16 . '|'
+ normal! 016|
+ exe 'normal! ' . s:c . '|zs' . 16 . '|'
+ normal! 016|
+ exe 'normal! ' . s:c . '|zs' . 8 . '|'
+ normal! 08|
+ exe 'normal! ' . s:c . '|zs' . 8 . '|'
+ normal! 08|
+ exe 'normal! ' . s:c . '|zs' . 16 . '|'
+ normal! 016|
+ exe 'normal! ' . s:c . '|zs' . 16 . '|'
+ normal! 016|
+ exe 'normal! ' . s:c . '|zs' . 16 . '|'
+ normal! 016|
+ exe 'normal! ' . s:c . '|zs' . 16 . '|'
+ normal! 016|
+ [DATA]
+
call assert_equal(expected, li)
tabclose!
diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim
index 532beb9c39..ad6d325510 100644
--- a/src/nvim/testdir/test_normal.vim
+++ b/src/nvim/testdir/test_normal.vim
@@ -2,12 +2,12 @@
source shared.vim
-func! Setup_NewWindow()
+func Setup_NewWindow()
10new
call setline(1, range(1,100))
endfunc
-func! MyFormatExpr()
+func MyFormatExpr()
" Adds '->$' at lines having numbers followed by trailing whitespace
for ln in range(v:lnum, v:lnum+v:count-1)
let line = getline(ln)
@@ -17,7 +17,7 @@ func! MyFormatExpr()
endfor
endfunc
-func! CountSpaces(type, ...)
+func CountSpaces(type, ...)
" for testing operatorfunc
" will count the number of spaces
" and return the result in g:a
@@ -37,7 +37,7 @@ func! CountSpaces(type, ...)
let @@ = reg_save
endfunc
-func! OpfuncDummy(type, ...)
+func OpfuncDummy(type, ...)
" for testing operatorfunc
let g:opt=&linebreak
@@ -81,7 +81,7 @@ fun! Test_normal00_optrans()
bw!
endfunc
-func! Test_normal01_keymodel()
+func Test_normal01_keymodel()
call Setup_NewWindow()
" Test 1: depending on 'keymodel' <s-down> does something different
50
@@ -115,7 +115,7 @@ func! Test_normal01_keymodel()
bw!
endfunc
-func! Test_normal02_selectmode()
+func Test_normal02_selectmode()
" some basic select mode tests
call Setup_NewWindow()
50
@@ -129,7 +129,7 @@ func! Test_normal02_selectmode()
bw!
endfunc
-func! Test_normal02_selectmode2()
+func Test_normal02_selectmode2()
" some basic select mode tests
call Setup_NewWindow()
50
@@ -139,7 +139,7 @@ func! Test_normal02_selectmode2()
bw!
endfunc
-func! Test_normal03_join()
+func Test_normal03_join()
" basic join test
call Setup_NewWindow()
50
@@ -159,7 +159,7 @@ func! Test_normal03_join()
bw!
endfunc
-func! Test_normal04_filter()
+func Test_normal04_filter()
" basic filter test
" only test on non windows platform
if has('win32')
@@ -185,7 +185,7 @@ func! Test_normal04_filter()
bw!
endfunc
-func! Test_normal05_formatexpr()
+func Test_normal05_formatexpr()
" basic formatexpr test
call Setup_NewWindow()
%d_
@@ -222,7 +222,7 @@ func Test_normal05_formatexpr_setopt()
set formatexpr=
endfunc
-func! Test_normal06_formatprg()
+func Test_normal06_formatprg()
" basic test for formatprg
" only test on non windows platform
if has('win32')
@@ -256,7 +256,7 @@ func! Test_normal06_formatprg()
call delete('Xsed_format.sh')
endfunc
-func! Test_normal07_internalfmt()
+func Test_normal07_internalfmt()
" basic test for internal formmatter to textwidth of 12
let list=range(1,11)
call map(list, 'v:val." "')
@@ -270,7 +270,7 @@ func! Test_normal07_internalfmt()
bw!
endfunc
-func! Test_normal08_fold()
+func Test_normal08_fold()
" basic tests for foldopen/folddelete
if !has("folding")
return
@@ -329,7 +329,7 @@ func! Test_normal08_fold()
bw!
endfunc
-func! Test_normal09_operatorfunc()
+func Test_normal09_operatorfunc()
" Test operatorfunc
call Setup_NewWindow()
" Add some spaces for counting
@@ -359,7 +359,7 @@ func! Test_normal09_operatorfunc()
bw!
endfunc
-func! Test_normal09a_operatorfunc()
+func Test_normal09a_operatorfunc()
" Test operatorfunc
call Setup_NewWindow()
" Add some spaces for counting
@@ -385,7 +385,7 @@ func! Test_normal09a_operatorfunc()
unlet! g:opt
endfunc
-func! Test_normal10_expand()
+func Test_normal10_expand()
" Test for expand()
10new
call setline(1, ['1', 'ifooar,,cbar'])
@@ -420,7 +420,7 @@ func! Test_normal10_expand()
bw!
endfunc
-func! Test_normal11_showcmd()
+func Test_normal11_showcmd()
" test for 'showcmd'
10new
exe "norm! ofoobar\<esc>"
@@ -435,7 +435,7 @@ func! Test_normal11_showcmd()
bw!
endfunc
-func! Test_normal12_nv_error()
+func Test_normal12_nv_error()
" Test for nv_error
10new
call setline(1, range(1,5))
@@ -445,7 +445,7 @@ func! Test_normal12_nv_error()
bw!
endfunc
-func! Test_normal13_help()
+func Test_normal13_help()
" Test for F1
call assert_equal(1, winnr())
call feedkeys("\<f1>", 'txi')
@@ -454,7 +454,7 @@ func! Test_normal13_help()
bw!
endfunc
-func! Test_normal14_page()
+func Test_normal14_page()
" basic test for Ctrl-F and Ctrl-B
call Setup_NewWindow()
exe "norm! \<c-f>"
@@ -488,7 +488,7 @@ func! Test_normal14_page()
bw!
endfunc
-func! Test_normal14_page_eol()
+func Test_normal14_page_eol()
10new
norm oxxxxxxx
exe "norm 2\<c-f>"
@@ -497,7 +497,7 @@ func! Test_normal14_page_eol()
bw!
endfunc
-func! Test_normal15_z_scroll_vert()
+func Test_normal15_z_scroll_vert()
" basic test for z commands that scroll the window
call Setup_NewWindow()
100
@@ -586,7 +586,7 @@ func! Test_normal15_z_scroll_vert()
bw!
endfunc
-func! Test_normal16_z_scroll_hor()
+func Test_normal16_z_scroll_hor()
" basic test for z commands that scroll the window
10new
15vsp
@@ -652,7 +652,7 @@ func! Test_normal16_z_scroll_hor()
bw!
endfunc
-func! Test_normal17_z_scroll_hor2()
+func Test_normal17_z_scroll_hor2()
" basic test for z commands that scroll the window
" using 'sidescrolloff' setting
10new
@@ -719,7 +719,7 @@ func! Test_normal17_z_scroll_hor2()
bw!
endfunc
-func! Test_normal18_z_fold()
+func Test_normal18_z_fold()
" basic tests for foldopen/folddelete
if !has("folding")
return
@@ -1090,7 +1090,7 @@ func! Test_normal18_z_fold()
bw!
endfunc
-func! Test_normal19_z_spell()
+func Test_normal19_z_spell()
if !has("spell") || !has('syntax')
return
endif
@@ -1245,7 +1245,7 @@ func! Test_normal19_z_spell()
bw!
endfunc
-func! Test_normal20_exmode()
+func Test_normal20_exmode()
if !has("unix")
" Reading from redirected file doesn't work on MS-Windows
return
@@ -1263,24 +1263,38 @@ func! Test_normal20_exmode()
bw!
endfunc
-func! Test_normal21_nv_hat()
- set hidden
- new
- " to many buffers opened already, will not work
- "call assert_fails(":b#", 'E23')
- "call assert_equal('', @#)
- e Xfoobar
- e Xfile2
- call feedkeys("\<c-^>", 't')
- call assert_equal("Xfile2", fnamemodify(bufname('%'), ':t'))
- call feedkeys("f\<c-^>", 't')
- call assert_equal("Xfile2", fnamemodify(bufname('%'), ':t'))
- " clean up
- set nohidden
- bw!
+func Test_normal21_nv_hat()
+
+ " Edit a fresh file and wipe the buffer list so that there is no alternate
+ " file present. Next, check for the expected command failures.
+ edit Xfoo | %bw
+ call assert_fails(':buffer #', 'E86')
+ call assert_fails(':execute "normal! \<C-^>"', 'E23')
+
+ " Test for the expected behavior when switching between two named buffers.
+ edit Xfoo | edit Xbar
+ call feedkeys("\<C-^>", 'tx')
+ call assert_equal('Xfoo', fnamemodify(bufname('%'), ':t'))
+ call feedkeys("\<C-^>", 'tx')
+ call assert_equal('Xbar', fnamemodify(bufname('%'), ':t'))
+
+ " Test for the expected behavior when only one buffer is named.
+ enew | let l:nr = bufnr('%')
+ call feedkeys("\<C-^>", 'tx')
+ call assert_equal('Xbar', fnamemodify(bufname('%'), ':t'))
+ call feedkeys("\<C-^>", 'tx')
+ call assert_equal('', bufname('%'))
+ call assert_equal(l:nr, bufnr('%'))
+
+ " Test that no action is taken by "<C-^>" when an operator is pending.
+ edit Xfoo
+ call feedkeys("ci\<C-^>", 'tx')
+ call assert_equal('Xfoo', fnamemodify(bufname('%'), ':t'))
+
+ %bw!
endfunc
-func! Test_normal22_zet()
+func Test_normal22_zet()
" Test for ZZ
" let shell = &shell
" let &shell = 'sh'
@@ -1308,7 +1322,7 @@ func! Test_normal22_zet()
" let &shell = shell
endfunc
-func! Test_normal23_K()
+func Test_normal23_K()
" Test for K command
new
call append(0, ['helphelp.txt', 'man', 'aa%bb', 'cc|dd'])
@@ -1353,8 +1367,9 @@ func! Test_normal23_K()
return
endif
- if has('mac')
- " In MacOS, the option for specifying a pager is different
+ let not_gnu_man = has('mac') || has('bsd')
+ if not_gnu_man
+ " In MacOS and BSD, the option for specifying a pager is different
set keywordprg=man\ -P\ cat
else
set keywordprg=man\ --pager=cat
@@ -1362,7 +1377,7 @@ func! Test_normal23_K()
" Test for using man
2
let a = execute('unsilent norm! K')
- if has('mac')
+ if not_gnu_man
call assert_match("man -P cat 'man'", a)
else
call assert_match("man --pager=cat 'man'", a)
@@ -1373,7 +1388,7 @@ func! Test_normal23_K()
bw!
endfunc
-func! Test_normal24_rot13()
+func Test_normal24_rot13()
" Testing for g?? g?g?
new
call append(0, 'abcdefghijklmnopqrstuvwxyzäüö')
@@ -1387,7 +1402,7 @@ func! Test_normal24_rot13()
bw!
endfunc
-func! Test_normal25_tag()
+func Test_normal25_tag()
" Testing for CTRL-] g CTRL-] g]
" CTRL-W g] CTRL-W CTRL-] CTRL-W g CTRL-]
h
@@ -1454,7 +1469,7 @@ func! Test_normal25_tag()
helpclose
endfunc
-func! Test_normal26_put()
+func Test_normal26_put()
" Test for ]p ]P [p and [P
new
call append(0, ['while read LINE', 'do', ' ((count++))', ' if [ $? -ne 0 ]; then', " echo 'Error writing file'", ' fi', 'done'])
@@ -1473,7 +1488,7 @@ func! Test_normal26_put()
bw!
endfunc
-func! Test_normal27_bracket()
+func Test_normal27_bracket()
" Test for [' [` ]' ]`
call Setup_NewWindow()
1,21s/.\+/ & b/
@@ -1524,7 +1539,7 @@ func! Test_normal27_bracket()
bw!
endfunc
-func! Test_normal28_parenthesis()
+func Test_normal28_parenthesis()
" basic testing for ( and )
new
call append(0, ['This is a test. With some sentences!', '', 'Even with a question? And one more. And no sentence here'])
@@ -1549,52 +1564,94 @@ endfunc
fun! Test_normal29_brace()
" basic test for { and } movements
- let text= ['A paragraph begins after each empty line, and also at each of a set of',
- \ 'paragraph macros, specified by the pairs of characters in the ''paragraphs''',
- \ 'option. The default is "IPLPPPQPP TPHPLIPpLpItpplpipbp", which corresponds to',
- \ 'the macros ".IP", ".LP", etc. (These are nroff macros, so the dot must be in',
- \ 'the first column). A section boundary is also a paragraph boundary.',
- \ 'Note that a blank line (only containing white space) is NOT a paragraph',
- \ 'boundary.',
- \ '',
- \ '',
- \ 'Also note that this does not include a ''{'' or ''}'' in the first column. When',
- \ 'the ''{'' flag is in ''cpoptions'' then ''{'' in the first column is used as a',
- \ 'paragraph boundary |posix|.',
- \ '{',
- \ 'This is no paragraph',
- \ 'unless the ''{'' is set',
- \ 'in ''cpoptions''',
- \ '}',
- \ '.IP',
- \ 'The nroff macros IP separates a paragraph',
- \ 'That means, it must be a ''.''',
- \ 'followed by IP',
- \ '.LPIt does not matter, if afterwards some',
- \ 'more characters follow.',
- \ '.SHAlso section boundaries from the nroff',
- \ 'macros terminate a paragraph. That means',
- \ 'a character like this:',
- \ '.NH',
- \ 'End of text here']
+ let text =<< trim [DATA]
+ A paragraph begins after each empty line, and also at each of a set of
+ paragraph macros, specified by the pairs of characters in the 'paragraphs'
+ option. The default is "IPLPPPQPP TPHPLIPpLpItpplpipbp", which corresponds to
+ the macros ".IP", ".LP", etc. (These are nroff macros, so the dot must be in
+ the first column). A section boundary is also a paragraph boundary.
+ Note that a blank line (only containing white space) is NOT a paragraph
+ boundary.
+
+
+ Also note that this does not include a '{' or '}' in the first column. When
+ the '{' flag is in 'cpoptions' then '{' in the first column is used as a
+ paragraph boundary |posix|.
+ {
+ This is no paragraph
+ unless the '{' is set
+ in 'cpoptions'
+ }
+ .IP
+ The nroff macros IP separates a paragraph
+ That means, it must be a '.'
+ followed by IP
+ .LPIt does not matter, if afterwards some
+ more characters follow.
+ .SHAlso section boundaries from the nroff
+ macros terminate a paragraph. That means
+ a character like this:
+ .NH
+ End of text here
+ [DATA]
+
new
call append(0, text)
1
norm! 0d2}
- call assert_equal(['.IP',
- \ 'The nroff macros IP separates a paragraph', 'That means, it must be a ''.''', 'followed by IP',
- \ '.LPIt does not matter, if afterwards some', 'more characters follow.', '.SHAlso section boundaries from the nroff',
- \ 'macros terminate a paragraph. That means', 'a character like this:', '.NH', 'End of text here', ''], getline(1,'$'))
+
+ let expected =<< trim [DATA]
+ .IP
+ The nroff macros IP separates a paragraph
+ That means, it must be a '.'
+ followed by IP
+ .LPIt does not matter, if afterwards some
+ more characters follow.
+ .SHAlso section boundaries from the nroff
+ macros terminate a paragraph. That means
+ a character like this:
+ .NH
+ End of text here
+
+ [DATA]
+ call assert_equal(expected, getline(1, '$'))
+
norm! 0d}
- call assert_equal(['.LPIt does not matter, if afterwards some', 'more characters follow.',
- \ '.SHAlso section boundaries from the nroff', 'macros terminate a paragraph. That means',
- \ 'a character like this:', '.NH', 'End of text here', ''], getline(1, '$'))
+
+ let expected =<< trim [DATA]
+ .LPIt does not matter, if afterwards some
+ more characters follow.
+ .SHAlso section boundaries from the nroff
+ macros terminate a paragraph. That means
+ a character like this:
+ .NH
+ End of text here
+
+ [DATA]
+ call assert_equal(expected, getline(1, '$'))
+
$
norm! d{
- call assert_equal(['.LPIt does not matter, if afterwards some', 'more characters follow.',
- \ '.SHAlso section boundaries from the nroff', 'macros terminate a paragraph. That means', 'a character like this:', ''], getline(1, '$'))
+
+ let expected =<< trim [DATA]
+ .LPIt does not matter, if afterwards some
+ more characters follow.
+ .SHAlso section boundaries from the nroff
+ macros terminate a paragraph. That means
+ a character like this:
+
+ [DATA]
+ call assert_equal(expected, getline(1, '$'))
+
norm! d{
- call assert_equal(['.LPIt does not matter, if afterwards some', 'more characters follow.', ''], getline(1,'$'))
+
+ let expected =<< trim [DATA]
+ .LPIt does not matter, if afterwards some
+ more characters follow.
+
+ [DATA]
+ call assert_equal(expected, getline(1, '$'))
+
" Test with { in cpooptions
%d
call append(0, text)
@@ -1602,21 +1659,62 @@ fun! Test_normal29_brace()
" set cpo+={
" 1
" norm! 0d2}
- " call assert_equal(['{', 'This is no paragraph', 'unless the ''{'' is set', 'in ''cpoptions''', '}',
- " \ '.IP', 'The nroff macros IP separates a paragraph', 'That means, it must be a ''.''',
- " \ 'followed by IP', '.LPIt does not matter, if afterwards some', 'more characters follow.',
- " \ '.SHAlso section boundaries from the nroff', 'macros terminate a paragraph. That means',
- " \ 'a character like this:', '.NH', 'End of text here', ''], getline(1,'$'))
+ " let expected =<< trim [DATA]
+ " {
+ " This is no paragraph
+ " unless the '{' is set
+ " in 'cpoptions'
+ " }
+ " .IP
+ " The nroff macros IP separates a paragraph
+ " That means, it must be a '.'
+ " followed by IP
+ " .LPIt does not matter, if afterwards some
+ " more characters follow.
+ " .SHAlso section boundaries from the nroff
+ " macros terminate a paragraph. That means
+ " a character like this:
+ " .NH
+ " End of text here
+ "
+ " [DATA]
+ " call assert_equal(expected, getline(1, '$'))
+ "
" $
" norm! d}
- " call assert_equal(['{', 'This is no paragraph', 'unless the ''{'' is set', 'in ''cpoptions''', '}',
- " \ '.IP', 'The nroff macros IP separates a paragraph', 'That means, it must be a ''.''',
- " \ 'followed by IP', '.LPIt does not matter, if afterwards some', 'more characters follow.',
- " \ '.SHAlso section boundaries from the nroff', 'macros terminate a paragraph. That means',
- " \ 'a character like this:', '.NH', 'End of text here', ''], getline(1,'$'))
+ " let expected =<< trim [DATA]
+ " {
+ " This is no paragraph
+ " unless the '{' is set
+ " in 'cpoptions'
+ " }
+ " .IP
+ " The nroff macros IP separates a paragraph
+ " That means, it must be a '.'
+ " followed by IP
+ " .LPIt does not matter, if afterwards some
+ " more characters follow.
+ " .SHAlso section boundaries from the nroff
+ " macros terminate a paragraph. That means
+ " a character like this:
+ " .NH
+ " End of text here
+ "
+ " [DATA]
+ " call assert_equal(expected, getline(1, '$'))
+ "
" norm! gg}
" norm! d5}
- " call assert_equal(['{', 'This is no paragraph', 'unless the ''{'' is set', 'in ''cpoptions''', '}', ''], getline(1,'$'))
+ "
+ " let expected =<< trim [DATA]
+ " {
+ " This is no paragraph
+ " unless the '{' is set
+ " in 'cpoptions'
+ " }
+
+ " [DATA]
+ " call assert_equal(expected, getline(1, '$'))
" clean up
set cpo-={
@@ -1718,7 +1816,7 @@ fun! Test_normal31_r_cmd()
bw!
endfunc
-func! Test_normal32_g_cmd1()
+func Test_normal32_g_cmd1()
" Test for g*, g#
new
call append(0, ['abc.x_foo', 'x_foobar.abc'])
@@ -1798,6 +1896,7 @@ fun! Test_normal33_g_cmd2()
set wrap listchars= sbr=
let lineA='abcdefghijklmnopqrstuvwxyz'
let lineB='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+ let lineC='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
$put =lineA
$put =lineB
@@ -1831,9 +1930,30 @@ fun! Test_normal33_g_cmd2()
call assert_equal(15, col('.'))
call assert_equal('l', getreg(0))
+ norm! 2ggdd
+ $put =lineC
+
+ " Test for gM
+ norm! gMyl
+ call assert_equal(73, col('.'))
+ call assert_equal('0', getreg(0))
+ " Test for 20gM
+ norm! 20gMyl
+ call assert_equal(29, col('.'))
+ call assert_equal('S', getreg(0))
+ " Test for 60gM
+ norm! 60gMyl
+ call assert_equal(87, col('.'))
+ call assert_equal('E', getreg(0))
+
+ " Test for g Ctrl-G
+ set ff=unix
+ let a=execute(":norm! g\<c-g>")
+ call assert_match('Col 87 of 144; Line 2 of 2; Word 1 of 1; Byte 88 of 146', a)
+
" Test for gI
norm! gIfoo
- call assert_equal(['', 'fooabcdefghijk lmno0123456789AMNOPQRSTUVWXYZ'], getline(1,'$'))
+ call assert_equal(['', 'foo0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'], getline(1,'$'))
" Test for gi
wincmd c
@@ -1849,7 +1969,7 @@ fun! Test_normal33_g_cmd2()
bw!
endfunc
-func! Test_g_ctrl_g()
+func Test_g_ctrl_g()
new
let a = execute(":norm! g\<c-g>")
@@ -2139,7 +2259,7 @@ fun! Test_normal41_insert_reg()
bw!
endfunc
-func! Test_normal42_halfpage()
+func Test_normal42_halfpage()
" basic test for Ctrl-D and Ctrl-U
call Setup_NewWindow()
call assert_equal(5, &scroll)
@@ -2207,7 +2327,7 @@ fun! Test_normal43_textobject1()
bw!
endfunc
-func! Test_normal44_textobjects2()
+func Test_normal44_textobjects2()
" basic testing for is and as text objects
new
call append(0, ['This is a test. With some sentences!', '', 'Even with a question? And one more. And no sentence here'])
@@ -2262,7 +2382,7 @@ func! Test_normal44_textobjects2()
bw!
endfunc
-func! Test_normal45_drop()
+func Test_normal45_drop()
if !has('dnd')
" The ~ register does not exist
call assert_beeps('norm! "~')
@@ -2280,7 +2400,7 @@ func! Test_normal45_drop()
bw!
endfunc
-func! Test_normal46_ignore()
+func Test_normal46_ignore()
new
" How to test this?
" let's just for now test, that the buffer
@@ -2299,7 +2419,7 @@ func! Test_normal46_ignore()
bw!
endfunc
-func! Test_normal47_visual_buf_wipe()
+func Test_normal47_visual_buf_wipe()
" This was causing a crash or ml_get error.
enew!
call setline(1,'xxx')
@@ -2313,7 +2433,7 @@ func! Test_normal47_visual_buf_wipe()
set nomodified
endfunc
-func! Test_normal47_autocmd()
+func Test_normal47_autocmd()
" disabled, does not seem to be possible currently
throw "Skipped: not possible to test cursorhold autocmd while waiting for input in normal_cmd"
new
@@ -2331,14 +2451,14 @@ func! Test_normal47_autocmd()
bw!
endfunc
-func! Test_normal48_wincmd()
+func Test_normal48_wincmd()
new
exe "norm! \<c-w>c"
call assert_equal(1, winnr('$'))
call assert_fails(":norm! \<c-w>c", "E444")
endfunc
-func! Test_normal49_counts()
+func Test_normal49_counts()
new
call setline(1, 'one two three four five six seven eight nine ten')
1
@@ -2347,7 +2467,7 @@ func! Test_normal49_counts()
bw!
endfunc
-func! Test_normal50_commandline()
+func Test_normal50_commandline()
if !has("timers") || !has("cmdline_hist") || !has("vertsplit")
return
endif
@@ -2378,7 +2498,7 @@ func! Test_normal50_commandline()
bw!
endfunc
-func! Test_normal51_FileChangedRO()
+func Test_normal51_FileChangedRO()
if !has("autocmd")
return
endif
@@ -2395,7 +2515,7 @@ func! Test_normal51_FileChangedRO()
call delete("Xreadonly.log")
endfunc
-func! Test_normal52_rl()
+func Test_normal52_rl()
if !has("rightleft")
return
endif
@@ -2428,7 +2548,7 @@ func! Test_normal52_rl()
bw!
endfunc
-func! Test_normal53_digraph()
+func Test_normal53_digraph()
if !has('digraphs')
return
endif
@@ -2474,6 +2594,15 @@ func Test_normal_large_count()
bwipe!
endfunc
+func Test_delete_until_paragraph()
+ new
+ normal grádv}
+ call assert_equal('á', getline(1))
+ normal grád}
+ call assert_equal('', getline(1))
+ bwipe!
+endfunc
+
" Test for the gr (virtual replace) command
" Test for the bug fixed by 7.4.387
func Test_gr_command()
@@ -2516,13 +2645,27 @@ func Test_changelist()
let &ul = save_ul
endfunc
-func Test_delete_until_paragraph()
- new
- normal grádv}
- call assert_equal('á', getline(1))
- normal grád}
- call assert_equal('', getline(1))
- bwipe!
+func Test_nv_hat_count()
+ %bwipeout!
+ let l:nr = bufnr('%') + 1
+ call assert_fails(':execute "normal! ' . l:nr . '\<C-^>"', 'E92')
+
+ edit Xfoo
+ let l:foo_nr = bufnr('Xfoo')
+
+ edit Xbar
+ let l:bar_nr = bufnr('Xbar')
+
+ " Make sure we are not just using the alternate file.
+ edit Xbaz
+
+ call feedkeys(l:foo_nr . "\<C-^>", 'tx')
+ call assert_equal('Xfoo', fnamemodify(bufname('%'), ':t'))
+
+ call feedkeys(l:bar_nr . "\<C-^>", 'tx')
+ call assert_equal('Xbar', fnamemodify(bufname('%'), ':t'))
+
+ %bwipeout!
endfunc
func Test_message_when_using_ctrl_c()
@@ -2617,3 +2760,105 @@ Piece of Java
close!
endfunc
+
+fun! Test_normal_gdollar_cmd()
+ if !has("jumplist")
+ return
+ endif
+ " Tests for g cmds
+ call Setup_NewWindow()
+ " Make long lines that will wrap
+ %s/$/\=repeat(' foobar', 10)/
+ 20vsp
+ set wrap
+ " Test for g$ with count
+ norm! gg
+ norm! 0vg$y
+ call assert_equal(20, col("'>"))
+ call assert_equal('1 foobar foobar foob', getreg(0))
+ norm! gg
+ norm! 0v4g$y
+ call assert_equal(72, col("'>"))
+ call assert_equal('1 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.."\n", getreg(0))
+ norm! gg
+ norm! 0v6g$y
+ call assert_equal(40, col("'>"))
+ call assert_equal('1 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '2 foobar foobar foobar foobar foobar foo', getreg(0))
+ set nowrap
+ " clean up
+ norm! gg
+ norm! 0vg$y
+ call assert_equal(20, col("'>"))
+ call assert_equal('1 foobar foobar foob', getreg(0))
+ norm! gg
+ norm! 0v4g$y
+ call assert_equal(20, col("'>"))
+ call assert_equal('1 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '2 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '3 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '4 foobar foobar foob', getreg(0))
+ norm! gg
+ norm! 0v6g$y
+ call assert_equal(20, col("'>"))
+ call assert_equal('1 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '2 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '3 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '4 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '5 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '6 foobar foobar foob', getreg(0))
+ " Move to last line, also down movement is not possible, should still move
+ " the cursor to the last visible char
+ norm! G
+ norm! 0v6g$y
+ call assert_equal(20, col("'>"))
+ call assert_equal('100 foobar foobar fo', getreg(0))
+ bw!
+endfunc
+
+func Test_normal_gk()
+ " needs 80 column new window
+ new
+ vert 80new
+ put =[repeat('x',90)..' {{{1', 'x {{{1']
+ norm! gk
+ " In a 80 column wide terminal the window will be only 78 char
+ " (because Vim will leave space for the other window),
+ " but if the terminal is larger, it will be 80 chars, so verify the
+ " cursor column correctly.
+ call assert_equal(winwidth(0)+1, col('.'))
+ call assert_equal(winwidth(0)+1, virtcol('.'))
+ norm! j
+ call assert_equal(6, col('.'))
+ call assert_equal(6, virtcol('.'))
+ norm! gk
+ call assert_equal(95, col('.'))
+ call assert_equal(95, virtcol('.'))
+ bw!
+ bw!
+
+ " needs 80 column new window
+ new
+ vert 80new
+ set number
+ set numberwidth=10
+ set cpoptions+=n
+ put =[repeat('0',90), repeat('1',90)]
+ norm! 075l
+ call assert_equal(76, col('.'))
+ norm! gk
+ call assert_equal(1, col('.'))
+ norm! gk
+ call assert_equal(76, col('.'))
+ norm! gk
+ call assert_equal(1, col('.'))
+ norm! gj
+ call assert_equal(76, col('.'))
+ norm! gj
+ call assert_equal(1, col('.'))
+ norm! gj
+ call assert_equal(76, col('.'))
+ bw!
+ bw!
+ set cpoptions& number& numberwidth&
+endfunc
diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim
index f4f5cbca61..6fcc372591 100644
--- a/src/nvim/testdir/test_options.vim
+++ b/src/nvim/testdir/test_options.vim
@@ -476,13 +476,19 @@ func Test_shortmess_F2()
call assert_match('file2', execute('bn', ''))
set shortmess+=F
call assert_true(empty(execute('bn', '')))
+ " call assert_false(test_getvalue('need_fileinfo'))
call assert_true(empty(execute('bn', '')))
+ " call assert_false(test_getvalue('need_fileinfo'))
set hidden
call assert_true(empty(execute('bn', '')))
+ " call assert_false(test_getvalue('need_fileinfo'))
call assert_true(empty(execute('bn', '')))
+ " call assert_false(test_getvalue('need_fileinfo'))
set nohidden
call assert_true(empty(execute('bn', '')))
+ " call assert_false(test_getvalue('need_fileinfo'))
call assert_true(empty(execute('bn', '')))
+ " call assert_false(test_getvalue('need_fileinfo'))
" Accommodate Nvim default.
set shortmess-=F
call assert_match('file1', execute('bn', ''))
diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim
index 53df30bb19..8083672808 100644
--- a/src/nvim/testdir/test_popup.vim
+++ b/src/nvim/testdir/test_popup.vim
@@ -1,6 +1,7 @@
" Test for completion menu
source shared.vim
+source screendump.vim
let g:months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
let g:setting = ''
@@ -680,18 +681,15 @@ func Test_popup_and_window_resize()
call term_sendkeys(buf, "\<c-v>")
call term_wait(buf, 100)
" popup first entry "!" must be at the top
- call WaitFor({-> term_getline(buf, 1) =~ "^!"})
- call assert_match('^!\s*$', term_getline(buf, 1))
+ call WaitForAssert({-> assert_match('^!\s*$', term_getline(buf, 1))})
exe 'resize +' . (h - 1)
call term_wait(buf, 100)
redraw!
" popup shifted down, first line is now empty
- call WaitFor({-> term_getline(buf, 1) == ""})
- call assert_equal('', term_getline(buf, 1))
+ call WaitForAssert({-> assert_equal('', term_getline(buf, 1))})
sleep 100m
" popup is below cursor line and shows first match "!"
- call WaitFor({-> term_getline(buf, term_getcursor(buf)[0] + 1) =~ "^!"})
- call assert_match('^!\s*$', term_getline(buf, term_getcursor(buf)[0] + 1))
+ call WaitForAssert({-> assert_match('^!\s*$', term_getline(buf, term_getcursor(buf)[0] + 1))})
" cursor line also shows !
call assert_match('^!\s*$', term_getline(buf, term_getcursor(buf)[0]))
bwipe!
@@ -735,6 +733,39 @@ func Test_popup_and_preview_autocommand()
bw!
endfunc
+func Test_popup_position()
+ if !CanRunVimInTerminal()
+ return
+ endif
+ let lines =<< trim END
+ 123456789_123456789_123456789_a
+ 123456789_123456789_123456789_b
+ 123
+ END
+ call writefile(lines, 'Xtest')
+ let buf = RunVimInTerminal('Xtest', {})
+ call term_sendkeys(buf, ":vsplit\<CR>")
+
+ " default pumwidth in left window: overlap in right window
+ call term_sendkeys(buf, "GA\<C-N>")
+ call VerifyScreenDump(buf, 'Test_popup_position_01', {'rows': 8})
+ call term_sendkeys(buf, "\<Esc>u")
+
+ " default pumwidth: fill until right of window
+ call term_sendkeys(buf, "\<C-W>l")
+ call term_sendkeys(buf, "GA\<C-N>")
+ call VerifyScreenDump(buf, 'Test_popup_position_02', {'rows': 8})
+
+ " larger pumwidth: used as minimum width
+ call term_sendkeys(buf, "\<Esc>u")
+ call term_sendkeys(buf, ":set pumwidth=30\<CR>")
+ call term_sendkeys(buf, "GA\<C-N>")
+ call VerifyScreenDump(buf, 'Test_popup_position_03', {'rows': 8})
+
+ call term_sendkeys(buf, "\<Esc>u")
+ call StopVimInTerminal(buf)
+ call delete('Xtest')
+endfunc
func Test_popup_complete_backwards()
new
@@ -746,6 +777,16 @@ func Test_popup_complete_backwards()
bwipe!
endfunc
+func Test_popup_complete_backwards_ctrl_p()
+ new
+ call setline(1, ['Post', 'Port', 'Po'])
+ let expected=['Post', 'Port', 'Port']
+ call cursor(3,2)
+ call feedkeys("A\<C-P>\<C-N>rt\<cr>", 'tx')
+ call assert_equal(expected, getline(1,'$'))
+ bwipe!
+endfunc
+
fun! Test_complete_o_tab()
throw 'skipped: Nvim does not support test_override()'
let s:o_char_pressed = 0
diff --git a/src/nvim/testdir/test_profile.vim b/src/nvim/testdir/test_profile.vim
index 7e853eeac3..f3eb88abf0 100644
--- a/src/nvim/testdir/test_profile.vim
+++ b/src/nvim/testdir/test_profile.vim
@@ -6,34 +6,34 @@ endif
source screendump.vim
func Test_profile_func()
- let lines = [
- \ 'profile start Xprofile_func.log',
- \ 'profile func Foo*"',
- \ "func! Foo1()",
- \ "endfunc",
- \ "func! Foo2()",
- \ " let l:count = 100",
- \ " while l:count > 0",
- \ " let l:count = l:count - 1",
- \ " endwhile",
- \ "endfunc",
- \ "func! Foo3()",
- \ "endfunc",
- \ "func! Bar()",
- \ "endfunc",
- \ "call Foo1()",
- \ "call Foo1()",
- \ "profile pause",
- \ "call Foo1()",
- \ "profile continue",
- \ "call Foo2()",
- \ "call Foo3()",
- \ "call Bar()",
- \ "if !v:profiling",
- \ " delfunc Foo2",
- \ "endif",
- \ "delfunc Foo3",
- \ ]
+ let lines =<< trim [CODE]
+ profile start Xprofile_func.log
+ profile func Foo*
+ func! Foo1()
+ endfunc
+ func! Foo2()
+ let l:count = 100
+ while l:count > 0
+ let l:count = l:count - 1
+ endwhile
+ endfunc
+ func! Foo3()
+ endfunc
+ func! Bar()
+ endfunc
+ call Foo1()
+ call Foo1()
+ profile pause
+ call Foo1()
+ profile continue
+ call Foo2()
+ call Foo3()
+ call Bar()
+ if !v:profiling
+ delfunc Foo2
+ endif
+ delfunc Foo3
+ [CODE]
call writefile(lines, 'Xprofile_func.vim')
call system(v:progpath
@@ -54,7 +54,7 @@ func Test_profile_func()
call assert_equal(30, len(lines))
call assert_equal('FUNCTION Foo1()', lines[0])
- call assert_match('Defined:.*Xprofile_func.vim', lines[1])
+ call assert_match('Defined:.*Xprofile_func.vim:3', lines[1])
call assert_equal('Called 2 times', lines[2])
call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[3])
call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[4])
@@ -88,38 +88,38 @@ func Test_profile_func()
endfunc
func Test_profile_func_with_ifelse()
- let lines = [
- \ "func! Foo1()",
- \ " if 1",
- \ " let x = 0",
- \ " elseif 1",
- \ " let x = 1",
- \ " else",
- \ " let x = 2",
- \ " endif",
- \ "endfunc",
- \ "func! Foo2()",
- \ " if 0",
- \ " let x = 0",
- \ " elseif 1",
- \ " let x = 1",
- \ " else",
- \ " let x = 2",
- \ " endif",
- \ "endfunc",
- \ "func! Foo3()",
- \ " if 0",
- \ " let x = 0",
- \ " elseif 0",
- \ " let x = 1",
- \ " else",
- \ " let x = 2",
- \ " endif",
- \ "endfunc",
- \ "call Foo1()",
- \ "call Foo2()",
- \ "call Foo3()",
- \ ]
+ let lines =<< trim [CODE]
+ func! Foo1()
+ if 1
+ let x = 0
+ elseif 1
+ let x = 1
+ else
+ let x = 2
+ endif
+ endfunc
+ func! Foo2()
+ if 0
+ let x = 0
+ elseif 1
+ let x = 1
+ else
+ let x = 2
+ endif
+ endfunc
+ func! Foo3()
+ if 0
+ let x = 0
+ elseif 0
+ let x = 1
+ else
+ let x = 2
+ endif
+ endfunc
+ call Foo1()
+ call Foo2()
+ call Foo3()
+ [CODE]
call writefile(lines, 'Xprofile_func.vim')
call system(v:progpath
@@ -198,41 +198,41 @@ func Test_profile_func_with_ifelse()
endfunc
func Test_profile_func_with_trycatch()
- let lines = [
- \ "func! Foo1()",
- \ " try",
- \ " let x = 0",
- \ " catch",
- \ " let x = 1",
- \ " finally",
- \ " let x = 2",
- \ " endtry",
- \ "endfunc",
- \ "func! Foo2()",
- \ " try",
- \ " throw 0",
- \ " catch",
- \ " let x = 1",
- \ " finally",
- \ " let x = 2",
- \ " endtry",
- \ "endfunc",
- \ "func! Foo3()",
- \ " try",
- \ " throw 0",
- \ " catch",
- \ " throw 1",
- \ " finally",
- \ " let x = 2",
- \ " endtry",
- \ "endfunc",
- \ "call Foo1()",
- \ "call Foo2()",
- \ "try",
- \ " call Foo3()",
- \ "catch",
- \ "endtry",
- \ ]
+ let lines =<< trim [CODE]
+ func! Foo1()
+ try
+ let x = 0
+ catch
+ let x = 1
+ finally
+ let x = 2
+ endtry
+ endfunc
+ func! Foo2()
+ try
+ throw 0
+ catch
+ let x = 1
+ finally
+ let x = 2
+ endtry
+ endfunc
+ func! Foo3()
+ try
+ throw 0
+ catch
+ throw 1
+ finally
+ let x = 2
+ endtry
+ endfunc
+ call Foo1()
+ call Foo2()
+ try
+ call Foo3()
+ catch
+ endtry
+ [CODE]
call writefile(lines, 'Xprofile_func.vim')
call system(v:progpath
@@ -311,15 +311,15 @@ func Test_profile_func_with_trycatch()
endfunc
func Test_profile_file()
- let lines = [
- \ 'func! Foo()',
- \ 'endfunc',
- \ 'for i in range(10)',
- \ ' " a comment',
- \ ' call Foo()',
- \ 'endfor',
- \ 'call Foo()',
- \ ]
+ let lines =<< trim [CODE]
+ func! Foo()
+ endfunc
+ for i in range(10)
+ " a comment
+ call Foo()
+ endfor
+ call Foo()
+ [CODE]
call writefile(lines, 'Xprofile_file.vim')
call system(v:progpath
@@ -450,26 +450,27 @@ func Test_profile_truncate_mbyte()
endfunc
func Test_profdel_func()
- let lines = [
- \ 'profile start Xprofile_file.log',
- \ 'func! Foo1()',
- \ 'endfunc',
- \ 'func! Foo2()',
- \ 'endfunc',
- \ 'func! Foo3()',
- \ 'endfunc',
- \ '',
- \ 'profile func Foo1',
- \ 'profile func Foo2',
- \ 'call Foo1()',
- \ 'call Foo2()',
- \ '',
- \ 'profile func Foo3',
- \ 'profdel func Foo2',
- \ 'profdel func Foo3',
- \ 'call Foo1()',
- \ 'call Foo2()',
- \ 'call Foo3()' ]
+ let lines =<< trim [CODE]
+ profile start Xprofile_file.log
+ func! Foo1()
+ endfunc
+ func! Foo2()
+ endfunc
+ func! Foo3()
+ endfunc
+
+ profile func Foo1
+ profile func Foo2
+ call Foo1()
+ call Foo2()
+
+ profile func Foo3
+ profdel func Foo2
+ profdel func Foo3
+ call Foo1()
+ call Foo2()
+ call Foo3()
+ [CODE]
call writefile(lines, 'Xprofile_file.vim')
call system(v:progpath . ' -es --clean -c "so Xprofile_file.vim" -c q')
call assert_equal(0, v:shell_error)
@@ -496,14 +497,15 @@ endfunc
func Test_profdel_star()
" Foo() is invoked once before and once after 'profdel *'.
" So profiling should report it only once.
- let lines = [
- \ 'profile start Xprofile_file.log',
- \ 'func! Foo()',
- \ 'endfunc',
- \ 'profile func Foo',
- \ 'call Foo()',
- \ 'profdel *',
- \ 'call Foo()' ]
+ let lines =<< trim [CODE]
+ profile start Xprofile_file.log
+ func! Foo()
+ endfunc
+ profile func Foo
+ call Foo()
+ profdel *
+ call Foo()
+ [CODE]
call writefile(lines, 'Xprofile_file.vim')
call system(v:progpath . ' -es --clean -c "so Xprofile_file.vim" -c q')
call assert_equal(0, v:shell_error)
diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index b7f45aeeb1..d7b387c2c9 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -37,6 +37,8 @@ func s:setup_commands(cchar)
command! -nargs=* Xgrepadd <mods> grepadd <args>
command! -nargs=* Xhelpgrep helpgrep <args>
command! -nargs=0 -count Xcc <count>cc
+ command! -count=1 -nargs=0 Xbelow <mods><count>cbelow
+ command! -count=1 -nargs=0 Xabove <mods><count>cabove
let g:Xgetlist = function('getqflist')
let g:Xsetlist = function('setqflist')
call setqflist([], 'f')
@@ -70,6 +72,8 @@ func s:setup_commands(cchar)
command! -nargs=* Xgrepadd <mods> lgrepadd <args>
command! -nargs=* Xhelpgrep lhelpgrep <args>
command! -nargs=0 -count Xcc <count>ll
+ command! -count=1 -nargs=0 Xbelow <mods><count>lbelow
+ command! -count=1 -nargs=0 Xabove <mods><count>labove
let g:Xgetlist = function('getloclist', [0])
let g:Xsetlist = function('setloclist', [0])
call setloclist(0, [], 'f')
@@ -163,6 +167,12 @@ endfunc
func XageTests(cchar)
call s:setup_commands(a:cchar)
+ if a:cchar == 'l'
+ " No location list for the current window
+ call assert_fails('lolder', 'E776:')
+ call assert_fails('lnewer', 'E776:')
+ endif
+
let list = [{'bufnr': bufnr('%'), 'lnum': 1}]
call g:Xsetlist(list)
@@ -273,6 +283,27 @@ func Test_cwindow()
call XwindowTests('l')
endfunc
+func Test_copenHeight()
+ copen
+ wincmd H
+ let height = winheight(0)
+ copen 10
+ call assert_equal(height, winheight(0))
+ quit
+endfunc
+
+func Test_copenHeight_tabline()
+ set tabline=foo showtabline=2
+ copen
+ wincmd H
+ let height = winheight(0)
+ copen 10
+ call assert_equal(height, winheight(0))
+ quit
+ set tabline& showtabline&
+endfunc
+
+
" Tests for the :cfile, :lfile, :caddfile, :laddfile, :cgetfile and :lgetfile
" commands.
func XfileTests(cchar)
@@ -540,6 +571,8 @@ func s:test_xhelpgrep(cchar)
" Search for non existing help string
call assert_fails('Xhelpgrep a1b2c3', 'E480:')
+ " Invalid regular expression
+ call assert_fails('Xhelpgrep \@<!', 'E480:')
endfunc
func Test_helpgrep()
@@ -775,68 +808,68 @@ func Test_efm1()
return
endif
- let l = [
- \ '"Xtestfile", line 4.12: 1506-045 (S) Undeclared identifier fd_set.',
- \ '"Xtestfile", line 6 col 19; this is an error',
- \ 'gcc -c -DHAVE_CONFIsing-prototypes -I/usr/X11R6/include version.c',
- \ 'Xtestfile:9: parse error before `asd''',
- \ 'make: *** [vim] Error 1',
- \ 'in file "Xtestfile" linenr 10: there is an error',
- \ '',
- \ '2 returned',
- \ '"Xtestfile", line 11 col 1; this is an error',
- \ '"Xtestfile", line 12 col 2; this is another error',
- \ '"Xtestfile", line 14:10; this is an error in column 10',
- \ '=Xtestfile=, line 15:10; this is another error, but in vcol 10 this time',
- \ '"Xtestfile", linenr 16: yet another problem',
- \ 'Error in "Xtestfile" at line 17:',
- \ 'x should be a dot',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 17',
- \ ' ^',
- \ 'Error in "Xtestfile" at line 18:',
- \ 'x should be a dot',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 18',
- \ '.............^',
- \ 'Error in "Xtestfile" at line 19:',
- \ 'x should be a dot',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 19',
- \ '--------------^',
- \ 'Error in "Xtestfile" at line 20:',
- \ 'x should be a dot',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 20',
- \ ' ^',
- \ '',
- \ 'Does anyone know what is the problem and how to correction it?',
- \ '"Xtestfile", line 21 col 9: What is the title of the quickfix window?',
- \ '"Xtestfile", line 22 col 9: What is the title of the quickfix window?'
- \ ]
+ let l =<< trim [DATA]
+ "Xtestfile", line 4.12: 1506-045 (S) Undeclared identifier fd_set.
+ "Xtestfile", line 6 col 19; this is an error
+ gcc -c -DHAVE_CONFIsing-prototypes -I/usr/X11R6/include version.c
+ Xtestfile:9: parse error before `asd'
+ make: *** [vim] Error 1
+ in file "Xtestfile" linenr 10: there is an error
+
+ 2 returned
+ "Xtestfile", line 11 col 1; this is an error
+ "Xtestfile", line 12 col 2; this is another error
+ "Xtestfile", line 14:10; this is an error in column 10
+ =Xtestfile=, line 15:10; this is another error, but in vcol 10 this time
+ "Xtestfile", linenr 16: yet another problem
+ Error in "Xtestfile" at line 17:
+ x should be a dot
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 17
+ ^
+ Error in "Xtestfile" at line 18:
+ x should be a dot
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 18
+ .............^
+ Error in "Xtestfile" at line 19:
+ x should be a dot
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 19
+ --------------^
+ Error in "Xtestfile" at line 20:
+ x should be a dot
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 20
+ ^
+
+ Does anyone know what is the problem and how to correction it?
+ "Xtestfile", line 21 col 9: What is the title of the quickfix window?
+ "Xtestfile", line 22 col 9: What is the title of the quickfix window?
+ [DATA]
call writefile(l, 'Xerrorfile1')
call writefile(l[:-2], 'Xerrorfile2')
- let m = [
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 2',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 3',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 4',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 5',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 6',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 7',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 8',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 9',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 10',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 11',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 12',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 13',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 14',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 15',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 16',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 17',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 18',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 19',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 20',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 21',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 22'
- \ ]
+ let m =<< [DATA]
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 2
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 3
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 4
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 5
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 6
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 7
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 8
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 9
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 10
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 11
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 12
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 13
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 14
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 15
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 16
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 17
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 18
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 19
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 20
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 21
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 22
+[DATA]
call writefile(m, 'Xtestfile')
let save_efm = &efm
@@ -895,20 +928,21 @@ func s:dir_stack_tests(cchar)
let save_efm=&efm
set efm=%DEntering\ dir\ '%f',%f:%l:%m,%XLeaving\ dir\ '%f'
- let lines = ["Entering dir 'dir1/a'",
- \ 'habits2.txt:1:Nine Healthy Habits',
- \ "Entering dir 'b'",
- \ 'habits3.txt:2:0 Hours of television',
- \ 'habits2.txt:7:5 Small meals',
- \ "Entering dir 'dir1/c'",
- \ 'habits4.txt:3:1 Hour of exercise',
- \ "Leaving dir 'dir1/c'",
- \ "Leaving dir 'dir1/a'",
- \ 'habits1.txt:4:2 Liters of water',
- \ "Entering dir 'dir2'",
- \ 'habits5.txt:5:3 Cups of hot green tea',
- \ "Leaving dir 'dir2'"
- \]
+ let lines =<< trim [DATA]
+ Entering dir 'dir1/a'
+ habits2.txt:1:Nine Healthy Habits
+ Entering dir 'b'
+ habits3.txt:2:0 Hours of television
+ habits2.txt:7:5 Small meals
+ Entering dir 'dir1/c'
+ habits4.txt:3:1 Hour of exercise
+ Leaving dir 'dir1/c'
+ Leaving dir 'dir1/a'
+ habits1.txt:4:2 Liters of water
+ Entering dir 'dir2'
+ habits5.txt:5:3 Cups of hot green tea
+ Leaving dir 'dir2
+ [DATA]
Xexpr ""
for l in lines
@@ -942,18 +976,20 @@ func Test_efm_dirstack()
call mkdir('dir1/c')
call mkdir('dir2')
- let lines = ["Nine Healthy Habits",
- \ "0 Hours of television",
- \ "1 Hour of exercise",
- \ "2 Liters of water",
- \ "3 Cups of hot green tea",
- \ "4 Short mental breaks",
- \ "5 Small meals",
- \ "6 AM wake up time",
- \ "7 Minutes of laughter",
- \ "8 Hours of sleep (at least)",
- \ "9 PM end of the day and off to bed"
- \ ]
+ let lines =<< trim [DATA]
+ Nine Healthy Habits,
+ 0 Hours of television,
+ 1 Hour of exercise,
+ 2 Liters of water,
+ 3 Cups of hot green tea,
+ 4 Short mental breaks,
+ 5 Small meals,
+ 6 AM wake up time,
+ 7 Minutes of laughter,
+ 8 Hours of sleep (at least),
+ 9 PM end of the day and off to bed
+ [DATA]
+
call writefile(lines, 'habits1.txt')
call writefile(lines, 'dir1/a/habits2.txt')
call writefile(lines, 'dir1/a/b/habits3.txt')
@@ -1042,28 +1078,29 @@ func Test_efm2()
set efm=%f:%s
cexpr 'Xtestfile:Line search text'
let l = getqflist()
- call assert_equal(l[0].pattern, '^\VLine search text\$')
- call assert_equal(l[0].lnum, 0)
+ call assert_equal('^\VLine search text\$', l[0].pattern)
+ call assert_equal(0, l[0].lnum)
let l = split(execute('clist', ''), "\n")
call assert_equal([' 1 Xtestfile:^\VLine search text\$: '], l)
" Test for %P, %Q and %t format specifiers
- let lines=["[Xtestfile1]",
- \ "(1,17) error: ';' missing",
- \ "(21,2) warning: variable 'z' not defined",
- \ "(67,3) error: end of file found before string ended",
- \ "--",
- \ "",
- \ "[Xtestfile2]",
- \ "--",
- \ "",
- \ "[Xtestfile3]",
- \ "NEW compiler v1.1",
- \ "(2,2) warning: variable 'x' not defined",
- \ "(67,3) warning: 's' already defined",
- \ "--"
- \]
+ let lines =<< trim [DATA]
+ [Xtestfile1]
+ (1,17) error: ';' missing
+ (21,2) warning: variable 'z' not defined
+ (67,3) error: end of file found before string ended
+ --
+
+ [Xtestfile2]
+ --
+
+ [Xtestfile3]
+ NEW compiler v1.1
+ (2,2) warning: variable 'x' not defined
+ (67,3) warning: 's' already defined
+ --
+ [DATA]
set efm=%+P[%f]%r,(%l\\,%c)%*[\ ]%t%*[^:]:\ %m,%+Q--%r
" To exercise the push/pop file functionality in quickfix, the test files
" need to be created.
@@ -1085,11 +1122,13 @@ func Test_efm2()
call delete('Xtestfile3')
" Tests for %E, %C and %Z format specifiers
- let lines = ["Error 275",
- \ "line 42",
- \ "column 3",
- \ "' ' expected after '--'"
- \]
+ let lines =<< trim [DATA]
+ Error 275
+ line 42
+ column 3
+ ' ' expected after '--'
+ [DATA]
+
set efm=%EError\ %n,%Cline\ %l,%Ccolumn\ %c,%Z%m
cgetexpr lines
let l = getqflist()
@@ -1100,9 +1139,11 @@ func Test_efm2()
call assert_equal("\n' ' expected after '--'", l[0].text)
" Test for %>
- let lines = ["Error in line 147 of foo.c:",
- \"unknown variable 'i'"
- \]
+ let lines =<< trim [DATA]
+ Error in line 147 of foo.c:
+ unknown variable 'i'
+ [DATA]
+
set efm=unknown\ variable\ %m,%E%>Error\ in\ line\ %l\ of\ %f:,%Z%m
cgetexpr lines
let l = getqflist()
@@ -1111,21 +1152,21 @@ func Test_efm2()
call assert_equal("\nunknown variable 'i'", l[0].text)
" Test for %A, %C and other formats
- let lines = [
- \"==============================================================",
- \"FAIL: testGetTypeIdCachesResult (dbfacadeTest.DjsDBFacadeTest)",
- \"--------------------------------------------------------------",
- \"Traceback (most recent call last):",
- \' File "unittests/dbfacadeTest.py", line 89, in testFoo',
- \" self.assertEquals(34, dtid)",
- \' File "/usr/lib/python2.2/unittest.py", line 286, in',
- \" failUnlessEqual",
- \" raise self.failureException, \\",
- \"AssertionError: 34 != 33",
- \"",
- \"--------------------------------------------------------------",
- \"Ran 27 tests in 0.063s"
- \]
+ let lines =<< trim [DATA]
+ ==============================================================
+ FAIL: testGetTypeIdCachesResult (dbfacadeTest.DjsDBFacadeTest)
+ --------------------------------------------------------------
+ Traceback (most recent call last):
+ File "unittests/dbfacadeTest.py", line 89, in testFoo
+ self.assertEquals(34, dtid)
+ File "/usr/lib/python2.2/unittest.py", line 286, in
+ failUnlessEqual
+ raise self.failureException, \\
+ AssertionError: 34 != 33
+
+ --------------------------------------------------------------
+ Ran 27 tests in 0.063s
+ [DATA]
set efm=%C\ %.%#,%A\ \ File\ \"%f\"\\,\ line\ %l%.%#,%Z%[%^\ ]%\\@=%m
cgetexpr lines
let l = getqflist()
@@ -1279,6 +1320,28 @@ func SetXlistTests(cchar, bnum)
let l = g:Xgetlist()
call g:Xsetlist(l)
call assert_equal(0, g:Xgetlist()[0].valid)
+ " Adding a non-valid entry should not mark the list as having valid entries
+ call g:Xsetlist([{'bufnr':a:bnum, 'lnum':5, 'valid':0}], 'a')
+ Xwindow
+ call assert_equal(1, winnr('$'))
+
+ " :cnext/:cprev should still work even with invalid entries in the list
+ let l = [{'bufnr' : a:bnum, 'lnum' : 1, 'text' : '1', 'valid' : 0},
+ \ {'bufnr' : a:bnum, 'lnum' : 2, 'text' : '2', 'valid' : 0}]
+ call g:Xsetlist(l)
+ Xnext
+ call assert_equal(2, g:Xgetlist({'idx' : 0}).idx)
+ Xprev
+ call assert_equal(1, g:Xgetlist({'idx' : 0}).idx)
+ " :cnext/:cprev should still work after appending invalid entries to an
+ " empty list
+ call g:Xsetlist([])
+ call g:Xsetlist(l, 'a')
+ Xnext
+ call assert_equal(2, g:Xgetlist({'idx' : 0}).idx)
+ Xprev
+ call assert_equal(1, g:Xgetlist({'idx' : 0}).idx)
+
call g:Xsetlist([{'text':'Text1', 'valid':1}])
Xwindow
call assert_equal(2, winnr('$'))
@@ -1579,6 +1642,14 @@ func Test_switchbuf()
call assert_equal(3, tabpagenr('$'))
tabfirst | enew | tabonly | only
+ set switchbuf=uselast
+ split
+ let last_winid = win_getid()
+ copen
+ exe "normal 1G\<CR>"
+ call assert_equal(last_winid, win_getid())
+ enew | only
+
set switchbuf=
edit Xqftestfile1
let file1_winid = win_getid()
@@ -1955,6 +2026,18 @@ func Xproperty_tests(cchar)
call g:Xsetlist([], 'r', {'items' : [{'filename' : 'F1', 'lnum' : 10, 'text' : 'L10'}]})
call assert_equal('TestTitle', g:Xgetlist({'title' : 1}).title)
+ " Test for getting id of window associated with a location list window
+ if a:cchar == 'l'
+ only
+ call assert_equal(0, g:Xgetlist({'all' : 1}).filewinid)
+ let wid = win_getid()
+ Xopen
+ call assert_equal(wid, g:Xgetlist({'filewinid' : 1}).filewinid)
+ wincmd w
+ call assert_equal(0, g:Xgetlist({'filewinid' : 1}).filewinid)
+ only
+ endif
+
" The following used to crash Vim with address sanitizer
call g:Xsetlist([], 'f')
call g:Xsetlist([], 'a', {'items' : [{'filename':'F1', 'lnum':10}]})
@@ -2317,6 +2400,12 @@ func XvimgrepTests(cchar)
call assert_equal('Xtestfile2', bufname(''))
call assert_equal('Editor:Emacs EmAcS', l[0].text)
+ " Test for unloading a buffer after vimgrep searched the buffer
+ %bwipe
+ Xvimgrep /Editor/j Xtestfile*
+ call assert_equal(0, getbufinfo('Xtestfile1')[0].loaded)
+ call assert_equal([], getbufinfo('Xtestfile2'))
+
call delete('Xtestfile1')
call delete('Xtestfile2')
endfunc
@@ -2489,7 +2578,7 @@ func Test_file_from_copen()
cclose
augroup! QF_Test
-endfunction
+endfunc
func Test_resize_from_copen()
augroup QF_Test
@@ -2864,185 +2953,101 @@ func Test_qf_id()
call Xqfid_tests('l')
endfunc
-func Test_getqflist_invalid_nr()
- " The following commands used to crash Vim
- cexpr ""
- call getqflist({'nr' : $XXX_DOES_NOT_EXIST_XXX})
-
- " Cleanup
- call setqflist([], 'r')
-endfunc
-
-" Test for shortening/simplifying the file name when opening the
-" quickfix window or when displaying the quickfix list
-func Test_shorten_fname()
- if !has('unix')
- return
- endif
- %bwipe
- " Create a quickfix list with a absolute path filename
- let fname = getcwd() . '/test_quickfix.vim'
- call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'})
- call assert_equal(fname, bufname('test_quickfix.vim'))
- " Opening the quickfix window should simplify the file path
- cwindow
- call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim'))
- cclose
- %bwipe
- " Create a quickfix list with a absolute path filename
- call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'})
- call assert_equal(fname, bufname('test_quickfix.vim'))
- " Displaying the quickfix list should simplify the file path
- silent! clist
- call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim'))
-endfunc
-
-" Quickfix title tests
-" In the below tests, 'exe "cmd"' is used to invoke the quickfix commands.
-" Otherwise due to indentation, the title is set with spaces at the beginning
-" of the command.
-func Test_qftitle()
- call writefile(["F1:1:Line1"], 'Xerr')
-
- " :cexpr
- exe "cexpr readfile('Xerr')"
- call assert_equal(":cexpr readfile('Xerr')", getqflist({'title' : 1}).title)
-
- " :cgetexpr
- exe "cgetexpr readfile('Xerr')"
- call assert_equal(":cgetexpr readfile('Xerr')",
- \ getqflist({'title' : 1}).title)
-
- " :caddexpr
- call setqflist([], 'f')
- exe "caddexpr readfile('Xerr')"
- call assert_equal(":caddexpr readfile('Xerr')",
- \ getqflist({'title' : 1}).title)
-
- " :cbuffer
- new Xerr
- exe "cbuffer"
- call assert_equal(':cbuffer (Xerr)', getqflist({'title' : 1}).title)
-
- " :cgetbuffer
- edit Xerr
- exe "cgetbuffer"
- call assert_equal(':cgetbuffer (Xerr)', getqflist({'title' : 1}).title)
-
- " :caddbuffer
- call setqflist([], 'f')
- edit Xerr
- exe "caddbuffer"
- call assert_equal(':caddbuffer (Xerr)', getqflist({'title' : 1}).title)
-
- " :cfile
- exe "cfile Xerr"
- call assert_equal(':cfile Xerr', getqflist({'title' : 1}).title)
-
- " :cgetfile
- exe "cgetfile Xerr"
- call assert_equal(':cgetfile Xerr', getqflist({'title' : 1}).title)
-
- " :caddfile
- call setqflist([], 'f')
- exe "caddfile Xerr"
- call assert_equal(':caddfile Xerr', getqflist({'title' : 1}).title)
-
- " :grep
- set grepprg=internal
- exe "grep F1 Xerr"
- call assert_equal(':grep F1 Xerr', getqflist({'title' : 1}).title)
-
- " :grepadd
- call setqflist([], 'f')
- exe "grepadd F1 Xerr"
- call assert_equal(':grepadd F1 Xerr', getqflist({'title' : 1}).title)
- set grepprg&vim
-
- " :vimgrep
- exe "vimgrep F1 Xerr"
- call assert_equal(':vimgrep F1 Xerr', getqflist({'title' : 1}).title)
+func Xqfjump_tests(cchar)
+ call s:setup_commands(a:cchar)
- " :vimgrepadd
- call setqflist([], 'f')
- exe "vimgrepadd F1 Xerr"
- call assert_equal(':vimgrepadd F1 Xerr', getqflist({'title' : 1}).title)
+ call writefile(["Line1\tFoo", "Line2"], 'F1')
+ call writefile(["Line1\tBar", "Line2"], 'F2')
+ call writefile(["Line1\tBaz", "Line2"], 'F3')
- call setqflist(['F1:10:L10'], ' ')
- call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+ call g:Xsetlist([], 'f')
- call setqflist([], 'f')
- call setqflist(['F1:10:L10'], 'a')
- call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+ " Tests for
+ " Jumping to a line using a pattern
+ " Jumping to a column greater than the last column in a line
+ " Jumping to a line greater than the last line in the file
+ let l = []
+ for i in range(1, 7)
+ call add(l, {})
+ endfor
+ let l[0].filename='F1'
+ let l[0].pattern='Line1'
+ let l[1].filename='F2'
+ let l[1].pattern='Line1'
+ let l[2].filename='F3'
+ let l[2].pattern='Line1'
+ let l[3].filename='F3'
+ let l[3].lnum=1
+ let l[3].col=9
+ let l[3].vcol=1
+ let l[4].filename='F3'
+ let l[4].lnum=99
+ let l[5].filename='F3'
+ let l[5].lnum=1
+ let l[5].col=99
+ let l[5].vcol=1
+ let l[6].filename='F3'
+ let l[6].pattern='abcxyz'
- call setqflist([], 'f')
- call setqflist(['F1:10:L10'], 'r')
- call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+ call g:Xsetlist([], ' ', {'items' : l})
+ Xopen | only
+ 2Xnext
+ call assert_equal(3, g:Xgetlist({'idx' : 0}).idx)
+ call assert_equal('F3', bufname('%'))
+ Xnext
+ call assert_equal(7, col('.'))
+ Xnext
+ call assert_equal(2, line('.'))
+ Xnext
+ call assert_equal(9, col('.'))
+ 2
+ Xnext
+ call assert_equal(2, line('.'))
- close
- call delete('Xerr')
+ if a:cchar == 'l'
+ " When jumping to a location list entry in the location list window and
+ " no usable windows are available, then a new window should be opened.
+ enew! | new | only
+ call g:Xsetlist([], 'f')
+ setlocal buftype=nofile
+ new
+ call g:Xsetlist([], ' ', {'lines' : ['F1:1:1:Line1', 'F1:2:2:Line2', 'F2:1:1:Line1', 'F2:2:2:Line2', 'F3:1:1:Line1', 'F3:2:2:Line2']})
+ Xopen
+ let winid = win_getid()
+ wincmd p
+ close
+ call win_gotoid(winid)
+ Xnext
+ call assert_equal(3, winnr('$'))
+ call assert_equal(1, winnr())
+ call assert_equal(2, line('.'))
- call setqflist([], ' ', {'title' : 'Errors'})
- copen
- call assert_equal('Errors', w:quickfix_title)
- call setqflist([], 'r', {'items' : [{'filename' : 'a.c', 'lnum' : 10}]})
- call assert_equal('Errors', w:quickfix_title)
- cclose
-endfunc
+ " When jumping to an entry in the location list window and the window
+ " associated with the location list is not present and a window containing
+ " the file is already present, then that window should be used.
+ close
+ belowright new
+ call g:Xsetlist([], 'f')
+ edit F3
+ call win_gotoid(winid)
+ Xlast
+ call assert_equal(3, winnr())
+ call assert_equal(6, g:Xgetlist({'size' : 1}).size)
+ call assert_equal(winid, g:Xgetlist({'winid' : 1}).winid)
+ endif
-" Test for the position of the quickfix and location list window
-func Test_qfwin_pos()
- " Open two windows
+ " Cleanup
+ enew!
new | only
- new
- cexpr ['F1:10:L10']
- copen
- " Quickfix window should be the bottom most window
- call assert_equal(3, winnr())
- close
- " Open at the very top
- wincmd t
- topleft copen
- call assert_equal(1, winnr())
- close
- " open left of the current window
- wincmd t
- below new
- leftabove copen
- call assert_equal(2, winnr())
- close
- " open right of the current window
- rightbelow copen
- call assert_equal(3, winnr())
- close
-endfunc
-" The following test used to crash Vim
-func Test_lhelpgrep_autocmd()
- lhelpgrep quickfix
- autocmd QuickFixCmdPost * call setloclist(0, [], 'f')
- lhelpgrep buffer
- call assert_equal('help', &filetype)
- call assert_equal(0, getloclist(0, {'nr' : '$'}).nr)
- lhelpgrep tabpage
- call assert_equal('help', &filetype)
- call assert_equal(1, getloclist(0, {'nr' : '$'}).nr)
- au! QuickFixCmdPost
- new | only
+ call delete('F1')
+ call delete('F2')
+ call delete('F3')
endfunc
-" Test to make sure that an empty quickfix buffer is not reused for loading
-" a normal buffer.
-func Test_empty_qfbuf()
- enew | only
- call writefile(["Test"], 'Xfile1')
- call setqflist([], 'f')
- copen | only
- let qfbuf = bufnr('')
- edit Xfile1
- call assert_notequal(qfbuf, bufnr(''))
- enew
- call delete('Xfile1')
+func Test_qfjump()
+ call Xqfjump_tests('c')
+ call Xqfjump_tests('l')
endfunc
" Tests for the getqflist() and getloclist() functions when the list is not
@@ -3061,7 +3066,17 @@ func Xgetlist_empty_tests(cchar)
call assert_equal('', g:Xgetlist({'title' : 0}).title)
call assert_equal(0, g:Xgetlist({'winid' : 0}).winid)
call assert_equal(0, g:Xgetlist({'changedtick' : 0}).changedtick)
- call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, 'changedtick': 0}, g:Xgetlist({'all' : 0}))
+ if a:cchar == 'c'
+ call assert_equal({'context' : '', 'id' : 0, 'idx' : 0,
+ \ 'items' : [], 'nr' : 0, 'size' : 0,
+ \ 'title' : '', 'winid' : 0, 'changedtick': 0},
+ \ g:Xgetlist({'all' : 0}))
+ else
+ call assert_equal({'context' : '', 'id' : 0, 'idx' : 0,
+ \ 'items' : [], 'nr' : 0, 'size' : 0, 'title' : '',
+ \ 'winid' : 0, 'changedtick': 0, 'filewinid' : 0},
+ \ g:Xgetlist({'all' : 0}))
+ endif
" Quickfix window with empty stack
silent! Xopen
@@ -3094,7 +3109,16 @@ func Xgetlist_empty_tests(cchar)
call assert_equal('', g:Xgetlist({'id' : qfid, 'title' : 0}).title)
call assert_equal(0, g:Xgetlist({'id' : qfid, 'winid' : 0}).winid)
call assert_equal(0, g:Xgetlist({'id' : qfid, 'changedtick' : 0}).changedtick)
- call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, 'changedtick' : 0}, g:Xgetlist({'id' : qfid, 'all' : 0}))
+ if a:cchar == 'c'
+ call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [],
+ \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0,
+ \ 'changedtick' : 0}, g:Xgetlist({'id' : qfid, 'all' : 0}))
+ else
+ call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [],
+ \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0,
+ \ 'changedtick' : 0, 'filewinid' : 0},
+ \ g:Xgetlist({'id' : qfid, 'all' : 0}))
+ endif
" Non-existing quickfix list number
call assert_equal('', g:Xgetlist({'nr' : 5, 'context' : 0}).context)
@@ -3106,7 +3130,16 @@ func Xgetlist_empty_tests(cchar)
call assert_equal('', g:Xgetlist({'nr' : 5, 'title' : 0}).title)
call assert_equal(0, g:Xgetlist({'nr' : 5, 'winid' : 0}).winid)
call assert_equal(0, g:Xgetlist({'nr' : 5, 'changedtick' : 0}).changedtick)
- call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, 'changedtick' : 0}, g:Xgetlist({'nr' : 5, 'all' : 0}))
+ if a:cchar == 'c'
+ call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [],
+ \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0,
+ \ 'changedtick' : 0}, g:Xgetlist({'nr' : 5, 'all' : 0}))
+ else
+ call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [],
+ \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0,
+ \ 'changedtick' : 0, 'filewinid' : 0},
+ \ g:Xgetlist({'nr' : 5, 'all' : 0}))
+ endif
endfunc
func Test_getqflist()
@@ -3114,6 +3147,16 @@ func Test_getqflist()
call Xgetlist_empty_tests('l')
endfunc
+
+func Test_getqflist_invalid_nr()
+ " The following commands used to crash Vim
+ cexpr ""
+ call getqflist({'nr' : $XXX_DOES_NOT_EXIST_XXX})
+
+ " Cleanup
+ call setqflist([], 'r')
+endfunc
+
" Tests for the quickfix/location list changedtick
func Xqftick_tests(cchar)
call s:setup_commands(a:cchar)
@@ -3172,6 +3215,41 @@ func Test_qf_tick()
call Xqftick_tests('l')
endfunc
+" Test helpgrep with lang specifier
+func Xtest_helpgrep_with_lang_specifier(cchar)
+ call s:setup_commands(a:cchar)
+ Xhelpgrep Vim@en
+ call assert_equal('help', &filetype)
+ call assert_notequal(0, g:Xgetlist({'nr' : '$'}).nr)
+ new | only
+endfunc
+
+func Test_helpgrep_with_lang_specifier()
+ call Xtest_helpgrep_with_lang_specifier('c')
+ call Xtest_helpgrep_with_lang_specifier('l')
+endfunc
+
+" The following test used to crash Vim.
+" Open the location list window and close the regular window associated with
+" the location list. When the garbage collection runs now, it incorrectly
+" marks the location list context as not in use and frees the context.
+func Test_ll_window_ctx()
+ call setloclist(0, [], 'f')
+ call setloclist(0, [], 'a', {'context' : []})
+ lopen | only
+ call test_garbagecollect_now()
+ echo getloclist(0, {'context' : 1}).context
+ enew | only
+endfunc
+
+" The following test used to crash vim
+func Test_lfile_crash()
+ sp Xtest
+ au QuickFixCmdPre * bw
+ call assert_fails('lfile', 'E40')
+ au! QuickFixCmdPre
+endfunc
+
" The following test used to crash vim
func Test_lbuffer_crash()
sv Xtest
@@ -3195,7 +3273,28 @@ func Test_lexpr_crash()
augroup QF_Test
au!
augroup END
+
+ enew | only
+ augroup QF_Test
+ au!
+ au BufNew * call setloclist(0, [], 'f')
+ augroup END
+ lexpr 'x:1:x'
+ augroup QF_Test
+ au!
+ augroup END
+
enew | only
+ lexpr ''
+ lopen
+ augroup QF_Test
+ au!
+ au FileType * call setloclist(0, [], 'f')
+ augroup END
+ lexpr ''
+ augroup QF_Test
+ au!
+ augroup END
endfunc
" The following test used to crash Vim
@@ -3212,122 +3311,31 @@ func Test_lvimgrep_crash()
enew | only
endfunc
-func Xqfjump_tests(cchar)
- call s:setup_commands(a:cchar)
-
- call writefile(["Line1\tFoo", "Line2"], 'F1')
- call writefile(["Line1\tBar", "Line2"], 'F2')
- call writefile(["Line1\tBaz", "Line2"], 'F3')
-
- call g:Xsetlist([], 'f')
-
- " Tests for
- " Jumping to a line using a pattern
- " Jumping to a column greater than the last column in a line
- " Jumping to a line greater than the last line in the file
- let l = []
- for i in range(1, 7)
- call add(l, {})
- endfor
- let l[0].filename='F1'
- let l[0].pattern='Line1'
- let l[1].filename='F2'
- let l[1].pattern='Line1'
- let l[2].filename='F3'
- let l[2].pattern='Line1'
- let l[3].filename='F3'
- let l[3].lnum=1
- let l[3].col=9
- let l[3].vcol=1
- let l[4].filename='F3'
- let l[4].lnum=99
- let l[5].filename='F3'
- let l[5].lnum=1
- let l[5].col=99
- let l[5].vcol=1
- let l[6].filename='F3'
- let l[6].pattern='abcxyz'
-
- call g:Xsetlist([], ' ', {'items' : l})
- Xopen | only
- 2Xnext
- call assert_equal(3, g:Xgetlist({'idx' : 0}).idx)
- call assert_equal('F3', bufname('%'))
- Xnext
- call assert_equal(7, col('.'))
- Xnext
- call assert_equal(2, line('.'))
- Xnext
- call assert_equal(9, col('.'))
- 2
- Xnext
- call assert_equal(2, line('.'))
-
- if a:cchar == 'l'
- " When jumping to a location list entry in the location list window and
- " no usable windows are available, then a new window should be opened.
- enew! | new | only
- call g:Xsetlist([], 'f')
- setlocal buftype=nofile
- new
- call g:Xsetlist([], ' ', {'lines' : ['F1:1:1:Line1', 'F1:2:2:Line2', 'F2:1:1:Line1', 'F2:2:2:Line2', 'F3:1:1:Line1', 'F3:2:2:Line2']})
- Xopen
- let winid = win_getid()
- wincmd p
- close
- call win_gotoid(winid)
- Xnext
- call assert_equal(3, winnr('$'))
- call assert_equal(1, winnr())
- call assert_equal(2, line('.'))
-
- " When jumping to an entry in the location list window and the window
- " associated with the location list is not present and a window containing
- " the file is already present, then that window should be used.
- close
- belowright new
- call g:Xsetlist([], 'f')
- edit F3
- call win_gotoid(winid)
- Xlast
- call assert_equal(3, winnr())
- call assert_equal(6, g:Xgetlist({'size' : 1}).size)
- call assert_equal(winid, g:Xgetlist({'winid' : 1}).winid)
- endif
-
- " Cleanup
- enew!
+" Test for the position of the quickfix and location list window
+func Test_qfwin_pos()
+ " Open two windows
new | only
-
- call delete('F1')
- call delete('F2')
- call delete('F3')
-endfunc
-
-func Test_qfjump()
- call Xqfjump_tests('c')
- call Xqfjump_tests('l')
-endfunc
-
-" The following test used to crash Vim.
-" Open the location list window and close the regular window associated with
-" the location list. When the garbage collection runs now, it incorrectly
-" marks the location list context as not in use and frees the context.
-func Test_ll_window_ctx()
- call setloclist(0, [], 'f')
- call setloclist(0, [], 'a', {'context' : []})
- lopen | only
- call test_garbagecollect_now()
- echo getloclist(0, {'context' : 1}).context
- enew | only
-endfunc
-
-" The following test used to crash vim
-func Test_lfile_crash()
- sp Xtest
- au QuickFixCmdPre * bw
- call assert_fails('lfile', 'E40')
- au! QuickFixCmdPre
+ new
+ cexpr ['F1:10:L10']
+ copen
+ " Quickfix window should be the bottom most window
+ call assert_equal(3, winnr())
+ close
+ " Open at the very top
+ wincmd t
+ topleft copen
+ call assert_equal(1, winnr())
+ close
+ " open left of the current window
+ wincmd t
+ below new
+ leftabove copen
+ call assert_equal(2, winnr())
+ close
+ " open right of the current window
+ rightbelow copen
+ call assert_equal(3, winnr())
+ close
endfunc
" Tests for quickfix/location lists changed by autocommands when
@@ -3371,6 +3379,137 @@ func Test_vimgrep_autocmd()
call setqflist([], 'f')
endfunc
+" The following test used to crash Vim
+func Test_lhelpgrep_autocmd()
+ lhelpgrep quickfix
+ autocmd QuickFixCmdPost * call setloclist(0, [], 'f')
+ lhelpgrep buffer
+ call assert_equal('help', &filetype)
+ call assert_equal(0, getloclist(0, {'nr' : '$'}).nr)
+ lhelpgrep tabpage
+ call assert_equal('help', &filetype)
+ call assert_equal(1, getloclist(0, {'nr' : '$'}).nr)
+ au! QuickFixCmdPost
+ new | only
+endfunc
+
+" Test for shortening/simplifying the file name when opening the
+" quickfix window or when displaying the quickfix list
+func Test_shorten_fname()
+ if !has('unix')
+ return
+ endif
+ %bwipe
+ " Create a quickfix list with a absolute path filename
+ let fname = getcwd() . '/test_quickfix.vim'
+ call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'})
+ call assert_equal(fname, bufname('test_quickfix.vim'))
+ " Opening the quickfix window should simplify the file path
+ cwindow
+ call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim'))
+ cclose
+ %bwipe
+ " Create a quickfix list with a absolute path filename
+ call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'})
+ call assert_equal(fname, bufname('test_quickfix.vim'))
+ " Displaying the quickfix list should simplify the file path
+ silent! clist
+ call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim'))
+endfunc
+
+" Quickfix title tests
+" In the below tests, 'exe "cmd"' is used to invoke the quickfix commands.
+" Otherwise due to indentation, the title is set with spaces at the beginning
+" of the command.
+func Test_qftitle()
+ call writefile(["F1:1:Line1"], 'Xerr')
+
+ " :cexpr
+ exe "cexpr readfile('Xerr')"
+ call assert_equal(":cexpr readfile('Xerr')", getqflist({'title' : 1}).title)
+
+ " :cgetexpr
+ exe "cgetexpr readfile('Xerr')"
+ call assert_equal(":cgetexpr readfile('Xerr')",
+ \ getqflist({'title' : 1}).title)
+
+ " :caddexpr
+ call setqflist([], 'f')
+ exe "caddexpr readfile('Xerr')"
+ call assert_equal(":caddexpr readfile('Xerr')",
+ \ getqflist({'title' : 1}).title)
+
+ " :cbuffer
+ new Xerr
+ exe "cbuffer"
+ call assert_equal(':cbuffer (Xerr)', getqflist({'title' : 1}).title)
+
+ " :cgetbuffer
+ edit Xerr
+ exe "cgetbuffer"
+ call assert_equal(':cgetbuffer (Xerr)', getqflist({'title' : 1}).title)
+
+ " :caddbuffer
+ call setqflist([], 'f')
+ edit Xerr
+ exe "caddbuffer"
+ call assert_equal(':caddbuffer (Xerr)', getqflist({'title' : 1}).title)
+
+ " :cfile
+ exe "cfile Xerr"
+ call assert_equal(':cfile Xerr', getqflist({'title' : 1}).title)
+
+ " :cgetfile
+ exe "cgetfile Xerr"
+ call assert_equal(':cgetfile Xerr', getqflist({'title' : 1}).title)
+
+ " :caddfile
+ call setqflist([], 'f')
+ exe "caddfile Xerr"
+ call assert_equal(':caddfile Xerr', getqflist({'title' : 1}).title)
+
+ " :grep
+ set grepprg=internal
+ exe "grep F1 Xerr"
+ call assert_equal(':grep F1 Xerr', getqflist({'title' : 1}).title)
+
+ " :grepadd
+ call setqflist([], 'f')
+ exe "grepadd F1 Xerr"
+ call assert_equal(':grepadd F1 Xerr', getqflist({'title' : 1}).title)
+ set grepprg&vim
+
+ " :vimgrep
+ exe "vimgrep F1 Xerr"
+ call assert_equal(':vimgrep F1 Xerr', getqflist({'title' : 1}).title)
+
+ " :vimgrepadd
+ call setqflist([], 'f')
+ exe "vimgrepadd F1 Xerr"
+ call assert_equal(':vimgrepadd F1 Xerr', getqflist({'title' : 1}).title)
+
+ call setqflist(['F1:10:L10'], ' ')
+ call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+
+ call setqflist([], 'f')
+ call setqflist(['F1:10:L10'], 'a')
+ call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+
+ call setqflist([], 'f')
+ call setqflist(['F1:10:L10'], 'r')
+ call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+
+ close
+ call delete('Xerr')
+
+ call setqflist([], ' ', {'title' : 'Errors'})
+ copen
+ call assert_equal('Errors', w:quickfix_title)
+ call setqflist([], 'r', {'items' : [{'filename' : 'a.c', 'lnum' : 10}]})
+ call assert_equal('Errors', w:quickfix_title)
+ cclose
+endfunc
+
func Test_lbuffer_with_bwipe()
new
new
@@ -3383,23 +3522,6 @@ func Test_lbuffer_with_bwipe()
augroup END
endfunc
-" Tests for the ':filter /pat/ clist' command
-func Test_filter_clist()
- cexpr ['Xfile1:10:10:Line 10', 'Xfile2:15:15:Line 15']
- call assert_equal([' 2 Xfile2:15 col 15: Line 15'],
- \ split(execute('filter /Line 15/ clist'), "\n"))
- call assert_equal([' 1 Xfile1:10 col 10: Line 10'],
- \ split(execute('filter /Xfile1/ clist'), "\n"))
- call assert_equal([], split(execute('filter /abc/ clist'), "\n"))
-
- call setqflist([{'module' : 'abc', 'pattern' : 'pat1'},
- \ {'module' : 'pqr', 'pattern' : 'pat2'}], ' ')
- call assert_equal([' 2 pqr:pat2: '],
- \ split(execute('filter /pqr/ clist'), "\n"))
- call assert_equal([' 1 abc:pat1: '],
- \ split(execute('filter /pat1/ clist'), "\n"))
-endfunc
-
" Test for an autocmd freeing the quickfix/location list when cexpr/lexpr is
" running
func Xexpr_acmd_freelist(cchar)
@@ -3549,6 +3671,23 @@ func Test_autocmd_changelist()
call Xautocmd_changelist('l')
endfunc
+" Tests for the ':filter /pat/ clist' command
+func Test_filter_clist()
+ cexpr ['Xfile1:10:10:Line 10', 'Xfile2:15:15:Line 15']
+ call assert_equal([' 2 Xfile2:15 col 15: Line 15'],
+ \ split(execute('filter /Line 15/ clist'), "\n"))
+ call assert_equal([' 1 Xfile1:10 col 10: Line 10'],
+ \ split(execute('filter /Xfile1/ clist'), "\n"))
+ call assert_equal([], split(execute('filter /abc/ clist'), "\n"))
+
+ call setqflist([{'module' : 'abc', 'pattern' : 'pat1'},
+ \ {'module' : 'pqr', 'pattern' : 'pat2'}], ' ')
+ call assert_equal([' 2 pqr:pat2: '],
+ \ split(execute('filter /pqr/ clist'), "\n"))
+ call assert_equal([' 1 abc:pat1: '],
+ \ split(execute('filter /pat1/ clist'), "\n"))
+endfunc
+
" Tests for the "CTRL-W <CR>" command.
func Xview_result_split_tests(cchar)
call s:setup_commands(a:cchar)
@@ -3575,3 +3714,269 @@ func Test_curswant()
call assert_equal(getcurpos()[4], virtcol('.'))
cclose | helpclose
endfunc
+
+" Test for parsing entries using visual screen column
+func Test_viscol()
+ enew
+ call writefile(["Col1\tCol2\tCol3"], 'Xfile1')
+ edit Xfile1
+
+ " Use byte offset for column number
+ set efm&
+ cexpr "Xfile1:1:5:XX\nXfile1:1:9:YY\nXfile1:1:20:ZZ"
+ call assert_equal([5, 8], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([9, 12], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([14, 20], [col('.'), virtcol('.')])
+
+ " Use screen column offset for column number
+ set efm=%f:%l:%v:%m
+ cexpr "Xfile1:1:8:XX\nXfile1:1:12:YY\nXfile1:1:20:ZZ"
+ call assert_equal([5, 8], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([9, 12], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([14, 20], [col('.'), virtcol('.')])
+ cexpr "Xfile1:1:6:XX\nXfile1:1:15:YY\nXfile1:1:24:ZZ"
+ call assert_equal([5, 8], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([10, 16], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([14, 20], [col('.'), virtcol('.')])
+
+ enew
+ call writefile(["Col1\täü\töß\tCol4"], 'Xfile1')
+
+ " Use byte offset for column number
+ set efm&
+ cexpr "Xfile1:1:8:XX\nXfile1:1:11:YY\nXfile1:1:16:ZZ"
+ call assert_equal([8, 10], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([11, 17], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([16, 25], [col('.'), virtcol('.')])
+
+ " Use screen column offset for column number
+ set efm=%f:%l:%v:%m
+ cexpr "Xfile1:1:10:XX\nXfile1:1:17:YY\nXfile1:1:25:ZZ"
+ call assert_equal([8, 10], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([11, 17], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([16, 25], [col('.'), virtcol('.')])
+
+ enew | only
+ set efm&
+ call delete('Xfile1')
+endfunc
+
+" Test to make sure that an empty quickfix buffer is not reused for loading
+" a normal buffer.
+func Test_empty_qfbuf()
+ enew | only
+ call writefile(["Test"], 'Xfile1')
+ call setqflist([], 'f')
+ copen | only
+ let qfbuf = bufnr('')
+ edit Xfile1
+ call assert_notequal(qfbuf, bufnr(''))
+ enew
+ call delete('Xfile1')
+endfunc
+
+" Test for the :cbelow, :cabove, :lbelow and :labove commands.
+func Xtest_below(cchar)
+ call s:setup_commands(a:cchar)
+
+ " No quickfix/location list
+ call assert_fails('Xbelow', 'E42:')
+ call assert_fails('Xabove', 'E42:')
+
+ " Empty quickfix/location list
+ call g:Xsetlist([])
+ call assert_fails('Xbelow', 'E42:')
+ call assert_fails('Xabove', 'E42:')
+
+ call s:create_test_file('X1')
+ call s:create_test_file('X2')
+ call s:create_test_file('X3')
+ call s:create_test_file('X4')
+
+ " Invalid entries
+ edit X1
+ call g:Xsetlist(["E1", "E2"])
+ call assert_fails('Xbelow', 'E42:')
+ call assert_fails('Xabove', 'E42:')
+ call assert_fails('3Xbelow', 'E42:')
+ call assert_fails('4Xabove', 'E42:')
+
+ " Test the commands with various arguments
+ Xexpr ["X1:5:L5", "X2:5:L5", "X2:10:L10", "X2:15:L15", "X3:3:L3"]
+ edit +7 X2
+ Xabove
+ call assert_equal(['X2', 5], [bufname(''), line('.')])
+ call assert_fails('Xabove', 'E553:')
+ normal 2j
+ Xbelow
+ call assert_equal(['X2', 10], [bufname(''), line('.')])
+ " Last error in this file
+ Xbelow 99
+ call assert_equal(['X2', 15], [bufname(''), line('.')])
+ call assert_fails('Xbelow', 'E553:')
+ " First error in this file
+ Xabove 99
+ call assert_equal(['X2', 5], [bufname(''), line('.')])
+ call assert_fails('Xabove', 'E553:')
+ normal gg
+ Xbelow 2
+ call assert_equal(['X2', 10], [bufname(''), line('.')])
+ normal G
+ Xabove 2
+ call assert_equal(['X2', 10], [bufname(''), line('.')])
+ edit X4
+ call assert_fails('Xabove', 'E42:')
+ call assert_fails('Xbelow', 'E42:')
+ if a:cchar == 'l'
+ " If a buffer has location list entries from some other window but not
+ " from the current window, then the commands should fail.
+ edit X1 | split | call setloclist(0, [], 'f')
+ call assert_fails('Xabove', 'E776:')
+ call assert_fails('Xbelow', 'E776:')
+ close
+ endif
+
+ " Test for lines with multiple quickfix entries
+ Xexpr ["X1:5:L5", "X2:5:1:L5_1", "X2:5:2:L5_2", "X2:5:3:L5_3",
+ \ "X2:10:1:L10_1", "X2:10:2:L10_2", "X2:10:3:L10_3",
+ \ "X2:15:1:L15_1", "X2:15:2:L15_2", "X2:15:3:L15_3", "X3:3:L3"]
+ edit +1 X2
+ Xbelow 2
+ call assert_equal(['X2', 10, 1], [bufname(''), line('.'), col('.')])
+ normal gg
+ Xbelow 99
+ call assert_equal(['X2', 15, 1], [bufname(''), line('.'), col('.')])
+ normal G
+ Xabove 2
+ call assert_equal(['X2', 10, 1], [bufname(''), line('.'), col('.')])
+ normal G
+ Xabove 99
+ call assert_equal(['X2', 5, 1], [bufname(''), line('.'), col('.')])
+ normal 10G
+ Xabove
+ call assert_equal(['X2', 5, 1], [bufname(''), line('.'), col('.')])
+ normal 10G
+ Xbelow
+ call assert_equal(['X2', 15, 1], [bufname(''), line('.'), col('.')])
+
+ " Invalid range
+ if a:cchar == 'c'
+ call assert_fails('-2cbelow', 'E553:')
+ " TODO: should go to first error in the current line?
+ 0cabove
+ else
+ call assert_fails('-2lbelow', 'E553:')
+ " TODO: should go to first error in the current line?
+ 0labove
+ endif
+
+ call delete('X1')
+ call delete('X2')
+ call delete('X3')
+ call delete('X4')
+endfunc
+
+func Test_cbelow()
+ call Xtest_below('c')
+ call Xtest_below('l')
+endfunc
+
+" Test for aborting quickfix commands using QuickFixCmdPre
+func Xtest_qfcmd_abort(cchar)
+ call s:setup_commands(a:cchar)
+
+ call g:Xsetlist([], 'f')
+
+ " cexpr/lexpr
+ let e = ''
+ try
+ Xexpr ["F1:10:Line10", "F2:20:Line20"]
+ catch /.*/
+ let e = v:exception
+ endtry
+ call assert_equal('AbortCmd', e)
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+
+ " cfile/lfile
+ call writefile(["F1:10:Line10", "F2:20:Line20"], 'Xfile1')
+ let e = ''
+ try
+ Xfile Xfile1
+ catch /.*/
+ let e = v:exception
+ endtry
+ call assert_equal('AbortCmd', e)
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+ call delete('Xfile1')
+
+ " cgetbuffer/lgetbuffer
+ enew!
+ call append(0, ["F1:10:Line10", "F2:20:Line20"])
+ let e = ''
+ try
+ Xgetbuffer
+ catch /.*/
+ let e = v:exception
+ endtry
+ call assert_equal('AbortCmd', e)
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+ enew!
+
+ " vimgrep/lvimgrep
+ let e = ''
+ try
+ Xvimgrep /func/ test_quickfix.vim
+ catch /.*/
+ let e = v:exception
+ endtry
+ call assert_equal('AbortCmd', e)
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+
+ " helpgrep/lhelpgrep
+ let e = ''
+ try
+ Xhelpgrep quickfix
+ catch /.*/
+ let e = v:exception
+ endtry
+ call assert_equal('AbortCmd', e)
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+
+ " grep/lgrep
+ if has('unix')
+ let e = ''
+ try
+ silent Xgrep func test_quickfix.vim
+ catch /.*/
+ let e = v:exception
+ endtry
+ call assert_equal('AbortCmd', e)
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+ endif
+endfunc
+
+func Test_qfcmd_abort()
+ augroup QF_Test
+ au!
+ autocmd QuickFixCmdPre * throw "AbortCmd"
+ augroup END
+
+ call Xtest_qfcmd_abort('c')
+ call Xtest_qfcmd_abort('l')
+
+ augroup QF_Test
+ au!
+ augroup END
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_quotestar.vim b/src/nvim/testdir/test_quotestar.vim
index b83fbe40e8..ce5a9ee827 100644
--- a/src/nvim/testdir/test_quotestar.vim
+++ b/src/nvim/testdir/test_quotestar.vim
@@ -54,34 +54,33 @@ func Do_test_quotestar_for_x11()
" Make sure a previous server has exited
try
call remote_send(name, ":qa!\<CR>")
- call WaitFor('serverlist() !~ "' . name . '"')
catch /E241:/
endtry
- call assert_notmatch(name, serverlist())
+ call WaitForAssert({-> assert_notmatch(name, serverlist())})
let cmd .= ' --servername ' . name
let job = job_start(cmd, {'stoponexit': 'kill', 'out_io': 'null'})
- call WaitFor({-> job_status(job) == "run"})
+ call WaitForAssert({-> assert_equal("run", job_status(job))})
" Takes a short while for the server to be active.
- call WaitFor('serverlist() =~ "' . name . '"')
+ call WaitForAssert({-> assert_match(name, serverlist())})
" Wait for the server to be up and answering requests. One second is not
" always sufficient.
- call WaitFor('remote_expr("' . name . '", "v:version", "", 2) != ""')
+ call WaitForAssert({-> assert_notequal('', remote_expr(name, "v:version", "", 2))})
" Clear the *-register of this vim instance and wait for it to be picked up
" by the server.
let @* = 'no'
call remote_foreground(name)
- call WaitFor('remote_expr("' . name . '", "@*", "", 1) == "no"')
+ call WaitForAssert({-> assert_equal("no", remote_expr(name, "@*", "", 1))})
" Set the * register on the server.
call remote_send(name, ":let @* = 'yes'\<CR>")
- call WaitFor('remote_expr("' . name . '", "@*", "", 1) == "yes"')
+ call WaitForAssert({-> assert_equal("yes", remote_expr(name, "@*", "", 1))})
" Check that the *-register of this vim instance is changed as expected.
- call WaitFor('@* == "yes"')
+ call WaitForAssert({-> assert_equal("yes", @*)})
" Handle the large selection over 262040 byte.
let length = 262044
@@ -109,18 +108,17 @@ func Do_test_quotestar_for_x11()
call remote_send(name, ":gui -f\<CR>")
endif
" Wait for the server in the GUI to be up and answering requests.
- call WaitFor('remote_expr("' . name . '", "has(\"gui_running\")", "", 1) =~ "1"')
+ call WaitForAssert({-> assert_match("1", remote_expr(name, "has('gui_running')", "", 1))})
call remote_send(name, ":let @* = 'maybe'\<CR>")
- call WaitFor('remote_expr("' . name . '", "@*", "", 1) == "maybe"')
- call assert_equal('maybe', remote_expr(name, "@*", "", 2))
+ call WaitForAssert({-> assert_equal("maybe", remote_expr(name, "@*", "", 2))})
call assert_equal('maybe', @*)
endif
call remote_send(name, ":qa!\<CR>")
try
- call WaitFor({-> job_status(job) == "dead"})
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
finally
if job_status(job) != 'dead'
call assert_report('Server did not exit')
diff --git a/src/nvim/testdir/test_recover.vim b/src/nvim/testdir/test_recover.vim
index 09c8d1cda6..fc073cacd2 100644
--- a/src/nvim/testdir/test_recover.vim
+++ b/src/nvim/testdir/test_recover.vim
@@ -14,6 +14,12 @@ func Test_recover_root_dir()
set dir=/notexist/
endif
call assert_fails('split Xtest', 'E303:')
+
+ " No error with empty 'directory' setting.
+ set directory=
+ split XtestOK
+ close!
+
set dir&
endfunc
diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim
index 298268a994..d4f58af10a 100644
--- a/src/nvim/testdir/test_registers.vim
+++ b/src/nvim/testdir/test_registers.vim
@@ -1,3 +1,16 @@
+"
+" Tests for register operations
+"
+
+" This test must be executed first to check for empty and unset registers.
+func Test_aaa_empty_reg_test()
+ call assert_fails('normal @@', 'E748:')
+ call assert_fails('normal @%', 'E354:')
+ call assert_fails('normal @#', 'E354:')
+ call assert_fails('normal @!', 'E354:')
+ call assert_fails('normal @:', 'E30:')
+ call assert_fails('normal @.', 'E29:')
+endfunc
func Test_yank_shows_register()
enew
@@ -82,3 +95,76 @@ func Test_recording_esc_sequence()
let &t_F2 = save_F2
endif
endfunc
+
+" Test for executing the last used register (@)
+func Test_last_used_exec_reg()
+ " Test for the @: command
+ let a = ''
+ call feedkeys(":let a ..= 'Vim'\<CR>", 'xt')
+ normal @:
+ call assert_equal('VimVim', a)
+
+ " Test for the @= command
+ let x = ''
+ let a = ":let x ..= 'Vim'\<CR>"
+ exe "normal @=a\<CR>"
+ normal @@
+ call assert_equal('VimVim', x)
+
+ " Test for the @. command
+ let a = ''
+ call feedkeys("i:let a ..= 'Edit'\<CR>", 'xt')
+ normal @.
+ normal @@
+ call assert_equal('EditEdit', a)
+
+ enew!
+endfunc
+
+func Test_get_register()
+ enew
+ edit Xfile1
+ edit Xfile2
+ call assert_equal('Xfile2', getreg('%'))
+ call assert_equal('Xfile1', getreg('#'))
+
+ call feedkeys("iTwo\<Esc>", 'xt')
+ call assert_equal('Two', getreg('.'))
+ call assert_equal('', getreg('_'))
+ call assert_beeps('normal ":yy')
+ call assert_beeps('normal "%yy')
+ call assert_beeps('normal ".yy')
+
+ call assert_equal('', getreg("\<C-F>"))
+ call assert_equal('', getreg("\<C-W>"))
+ call assert_equal('', getreg("\<C-L>"))
+
+ call assert_equal('', getregtype('!'))
+
+ enew!
+endfunc
+
+func Test_set_register()
+ call assert_fails("call setreg('#', 200)", 'E86:')
+
+ edit Xfile_alt_1
+ let b1 = bufnr('')
+ edit Xfile_alt_2
+ let b2 = bufnr('')
+ edit Xfile_alt_3
+ let b3 = bufnr('')
+ call setreg('#', 'alt_1')
+ call assert_equal('Xfile_alt_1', getreg('#'))
+ call setreg('#', b2)
+ call assert_equal('Xfile_alt_2', getreg('#'))
+
+ let ab = 'regwrite'
+ call setreg('=', '')
+ call setreg('=', 'a', 'a')
+ call setreg('=', 'b', 'a')
+ call assert_equal('regwrite', getreg('='))
+
+ enew!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim
index 5d4c2a015f..68eb311e3c 100644
--- a/src/nvim/testdir/test_search.vim
+++ b/src/nvim/testdir/test_search.vim
@@ -57,7 +57,7 @@ func Test_search_cmdline()
call feedkeys("/the".repeat("\<C-G>", 6)."\<cr>", 'tx')
call assert_equal(' 8 them', getline('.'))
:1
- " eigth match
+ " eighth match
call feedkeys("/the".repeat("\<C-G>", 7)."\<cr>", 'tx')
call assert_equal(' 9 these', getline('.'))
:1
@@ -99,7 +99,7 @@ func Test_search_cmdline()
call feedkeys("/the".repeat("\<C-G>", 6)."\<cr>", 'tx')
call assert_equal(' 8 them', getline('.'))
:1
- " eigth match
+ " eighth match
call feedkeys("/the".repeat("\<C-G>", 7)."\<cr>", 'tx')
call assert_equal(' 9 these', getline('.'))
:1
diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim
index e49b5542fa..e2016d7927 100644
--- a/src/nvim/testdir/test_spell.vim
+++ b/src/nvim/testdir/test_spell.vim
@@ -283,9 +283,9 @@ func Test_zz_affix()
\ ])
call LoadAffAndDic(g:test_data_aff7, g:test_data_dic7)
- call RunGoodBad("meea1 meea\xE9 bar prebar barmeat prebarmeat leadprebar lead tail leadtail leadmiddletail",
+ call RunGoodBad("meea1 meezero meea\xE9 bar prebar barmeat prebarmeat leadprebar lead tail leadtail leadmiddletail",
\ "bad: mee meea2 prabar probarmaat middle leadmiddle middletail taillead leadprobar",
- \ ["bar", "barmeat", "lead", "meea1", "meea\xE9", "prebar", "prebarmeat", "tail"],
+ \ ["bar", "barmeat", "lead", "meea1", "meea\xE9", "meezero", "prebar", "prebarmeat", "tail"],
\ [
\ ["bad", ["bar", "lead", "tail"]],
\ ["mee", ["meea1", "meea\xE9", "bar"]],
@@ -320,6 +320,19 @@ func Test_zz_Numbers()
\ ])
endfunc
+" Affix flags
+func Test_zz_affix_flags()
+ call LoadAffAndDic(g:test_data_aff10, g:test_data_dic10)
+ call RunGoodBad("drink drinkable drinkables drinktable drinkabletable",
+ \ "bad: drinks drinkstable drinkablestable",
+ \ ["drink", "drinkable", "drinkables", "table"],
+ \ [['bad', []],
+ \ ['drinks', ['drink']],
+ \ ['drinkstable', ['drinktable', 'drinkable', 'drink table']],
+ \ ['drinkablestable', ['drinkabletable', 'drinkables table', 'drinkable table']],
+ \ ])
+endfunc
+
function FirstSpellWord()
call feedkeys("/^start:\n", 'tx')
normal ]smm
@@ -713,6 +726,9 @@ let g:test_data_aff7 = [
\"SFX 61003 Y 1",
\"SFX 61003 0 meat .",
\"",
+ \"SFX 0 Y 1",
+ \"SFX 0 0 zero .",
+ \"",
\"SFX 391 Y 1",
\"SFX 391 0 a1 .",
\"",
@@ -724,7 +740,7 @@ let g:test_data_aff7 = [
\ ]
let g:test_data_dic7 = [
\"1234",
- \"mee/391,111,9999",
+ \"mee/0,391,111,9999",
\"bar/17,61003,123",
\"lead/2",
\"tail/123",
@@ -748,6 +764,21 @@ let g:test_data_dic9 = [
\"foo",
\"bar",
\ ]
+let g:test_data_aff10 = [
+ \"COMPOUNDRULE se",
+ \"COMPOUNDPERMITFLAG p",
+ \"",
+ \"SFX A Y 1",
+ \"SFX A 0 able/Mp .",
+ \"",
+ \"SFX M Y 1",
+ \"SFX M 0 s .",
+ \ ]
+let g:test_data_dic10 = [
+ \"1234",
+ \"drink/As",
+ \"table/e",
+ \ ]
let g:test_data_aff_sal = [
\"SET ISO8859-1",
\"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ",
diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim
index 1e70f28a00..a38625cca8 100644
--- a/src/nvim/testdir/test_startup.vim
+++ b/src/nvim/testdir/test_startup.vim
@@ -20,25 +20,27 @@ func Test_after_comes_later()
if !has('packages')
return
endif
- let before = [
- \ 'set nocp viminfo+=nviminfo',
- \ 'set guioptions+=M',
- \ 'let $HOME = "/does/not/exist"',
- \ 'set loadplugins',
- \ 'set rtp=Xhere,Xafter,Xanother',
- \ 'set packpath=Xhere,Xafter',
- \ 'set nomore',
- \ 'let g:sequence = ""',
- \ ]
- let after = [
- \ 'redir! > Xtestout',
- \ 'scriptnames',
- \ 'redir END',
- \ 'redir! > Xsequence',
- \ 'echo g:sequence',
- \ 'redir END',
- \ 'quit',
- \ ]
+ let before =<< trim [CODE]
+ set nocp viminfo+=nviminfo
+ set guioptions+=M
+ let $HOME = "/does/not/exist"
+ set loadplugins
+ set rtp=Xhere,Xafter,Xanother
+ set packpath=Xhere,Xafter
+ set nomore
+ let g:sequence = ""
+ [CODE]
+
+ let after =<< trim [CODE]
+ redir! > Xtestout
+ scriptnames
+ redir END
+ redir! > Xsequence
+ echo g:sequence
+ redir END
+ quit
+ [CODE]
+
call mkdir('Xhere/plugin', 'p')
call writefile(['let g:sequence .= "here "'], 'Xhere/plugin/here.vim')
call mkdir('Xanother/plugin', 'p')
@@ -77,15 +79,16 @@ func Test_pack_in_rtp_when_plugins_run()
if !has('packages')
return
endif
- let before = [
- \ 'set nocp viminfo+=nviminfo',
- \ 'set guioptions+=M',
- \ 'let $HOME = "/does/not/exist"',
- \ 'set loadplugins',
- \ 'set rtp=Xhere',
- \ 'set packpath=Xhere',
- \ 'set nomore',
- \ ]
+ let before =<< trim [CODE]
+ set nocp viminfo+=nviminfo
+ set guioptions+=M
+ let $HOME = "/does/not/exist"
+ set loadplugins
+ set rtp=Xhere
+ set packpath=Xhere
+ set nomore
+ [CODE]
+
let after = [
\ 'quit',
\ ]
@@ -133,11 +136,12 @@ endfunc
func Test_compatible_args()
throw "skipped: Nvim is always 'nocompatible'"
- let after = [
- \ 'call writefile([string(&compatible)], "Xtestout")',
- \ 'set viminfo+=nviminfo',
- \ 'quit',
- \ ]
+ let after =<< trim [CODE]
+ call writefile([string(&compatible)], "Xtestout")
+ set viminfo+=nviminfo
+ quit
+ [CODE]
+
if RunVim([], after, '-C')
let lines = readfile('Xtestout')
call assert_equal('1', lines[0])
@@ -154,14 +158,15 @@ endfunc
" Test the -o[N] and -O[N] arguments to open N windows split
" horizontally or vertically.
func Test_o_arg()
- let after = [
- \ 'call writefile([winnr("$"),
- \ winheight(1), winheight(2), &lines,
- \ winwidth(1), winwidth(2), &columns,
- \ bufname(winbufnr(1)), bufname(winbufnr(2))],
- \ "Xtestout")',
- \ 'qall',
- \ ]
+ let after =<< trim [CODE]
+ call writefile([winnr("$"),
+ \ winheight(1), winheight(2), &lines,
+ \ winwidth(1), winwidth(2), &columns,
+ \ bufname(winbufnr(1)), bufname(winbufnr(2))],
+ \ "Xtestout")
+ qall
+ [CODE]
+
if RunVim([], after, '-o2')
" Open 2 windows split horizontally. Expect:
" - 2 windows
@@ -230,10 +235,11 @@ endfunc
" Test the -p[N] argument to open N tabpages.
func Test_p_arg()
- let after = [
- \ 'call writefile(split(execute("tabs"), "\n"), "Xtestout")',
- \ 'qall',
- \ ]
+ let after =<< trim [CODE]
+ call writefile(split(execute("tabs"), "\n"), "Xtestout")
+ qall
+ [CODE]
+
if RunVim([], after, '-p2')
let lines = readfile('Xtestout')
call assert_equal(4, len(lines))
@@ -290,10 +296,11 @@ endfunc
" -M resets 'modifiable' and 'write'
" -R sets 'readonly'
func Test_m_M_R()
- let after = [
- \ 'call writefile([&write, &modifiable, &readonly, &updatecount], "Xtestout")',
- \ 'qall',
- \ ]
+ let after =<< trim [CODE]
+ call writefile([&write, &modifiable, &readonly, &updatecount], "Xtestout")
+ qall
+ [CODE]
+
if RunVim([], after, '')
let lines = readfile('Xtestout')
call assert_equal(['1', '1', '0', '200'], lines)
@@ -316,10 +323,11 @@ endfunc
" Test the -A, -F and -H arguments (Arabic, Farsi and Hebrew modes).
func Test_A_F_H_arg()
- let after = [
- \ 'call writefile([&rightleft, &arabic, 0, &hkmap], "Xtestout")',
- \ 'qall',
- \ ]
+ let after =<< trim [CODE]
+ call writefile([&rightleft, &arabic, 0, &hkmap], "Xtestout")
+ qall
+ [CODE]
+
" Use silent Ex mode to avoid the hit-Enter prompt for the warning that
" 'encoding' is not utf-8.
if has('arabic') && &encoding == 'utf-8' && RunVim([], after, '-e -s -A')
@@ -423,10 +431,11 @@ func Test_invalid_args()
endfunc
func Test_file_args()
- let after = [
- \ 'call writefile(argv(), "Xtestout")',
- \ 'qall',
- \ ]
+ let after =<< trim [CODE]
+ call writefile(argv(), "Xtestout")
+ qall
+ [CODE]
+
if RunVim([], after, '')
let lines = readfile('Xtestout')
call assert_equal(0, len(lines))
@@ -487,10 +496,11 @@ func Test_startuptime()
endfunc
func Test_read_stdin()
- let after = [
- \ 'write Xtestout',
- \ 'quit!',
- \ ]
+ let after =<< trim [CODE]
+ write Xtestout
+ quit!
+ [CODE]
+
if RunVimPiped([], after, '-', 'echo something | ')
let lines = readfile('Xtestout')
" MS-Windows adds a space after the word
@@ -540,20 +550,22 @@ endfunc
func Test_zzz_startinsert()
" Test :startinsert
call writefile(['123456'], 'Xtestout')
- let after = [
- \ ':startinsert',
- \ 'call feedkeys("foobar\<c-o>:wq\<cr>","t")'
- \ ]
+ let after =<< trim [CODE]
+ :startinsert
+ call feedkeys("foobar\<c-o>:wq\<cr>","t")
+ [CODE]
+
if RunVim([], after, 'Xtestout')
let lines = readfile('Xtestout')
call assert_equal(['foobar123456'], lines)
endif
" Test :startinsert!
call writefile(['123456'], 'Xtestout')
- let after = [
- \ ':startinsert!',
- \ 'call feedkeys("foobar\<c-o>:wq\<cr>","t")'
- \ ]
+ let after =<< trim [CODE]
+ :startinsert!
+ call feedkeys("foobar\<c-o>:wq\<cr>","t")
+ [CODE]
+
if RunVim([], after, 'Xtestout')
let lines = readfile('Xtestout')
call assert_equal(['123456foobar'], lines)
diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim
index b86340a23a..48ec777ffd 100644
--- a/src/nvim/testdir/test_statusline.vim
+++ b/src/nvim/testdir/test_statusline.vim
@@ -347,3 +347,25 @@ func Test_statusline()
set laststatus&
set splitbelow&
endfunc
+
+func Test_statusline_visual()
+ func CallWordcount()
+ call wordcount()
+ endfunc
+ new x1
+ setl statusline=count=%{CallWordcount()}
+ " buffer must not be empty
+ call setline(1, 'hello')
+
+ " window with more lines than x1
+ new x2
+ call setline(1, range(10))
+ $
+ " Visual mode in line below liast line in x1 should not give ml_get error
+ call feedkeys("\<C-V>", "xt")
+ redraw
+
+ delfunc CallWordcount
+ bwipe! x1
+ bwipe! x2
+endfunc
diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim
index b29b678129..e94bd22cea 100644
--- a/src/nvim/testdir/test_substitute.vim
+++ b/src/nvim/testdir/test_substitute.vim
@@ -149,6 +149,7 @@ func Run_SubCmd_Tests(tests)
for t in a:tests
let start = line('.') + 1
let end = start + len(t[2]) - 1
+ " TODO: why is there a one second delay the first time we get here?
exe "normal o" . t[0]
call cursor(start, 1)
exe t[1]
@@ -240,7 +241,7 @@ func Test_sub_cmd_3()
call Run_SubCmd_Tests(tests)
endfunc
-" Test for submatch() on :substitue.
+" Test for submatch() on :substitute.
func Test_sub_cmd_4()
set magic&
set cpo&
@@ -717,3 +718,12 @@ one two
close!
endfunc
+
+func Test_sub_beyond_end()
+ new
+ call setline(1, '#')
+ let @/ = '^#\n\zs'
+ s///e
+ call assert_equal('#', getline(1))
+ bwipe!
+endfunc
diff --git a/src/nvim/testdir/test_swap.vim b/src/nvim/testdir/test_swap.vim
index 11eb324488..e072e9ed7f 100644
--- a/src/nvim/testdir/test_swap.vim
+++ b/src/nvim/testdir/test_swap.vim
@@ -221,3 +221,87 @@ func Test_swapfile_delete()
augroup END
augroup! test_swapfile_delete
endfunc
+
+func Test_swap_recover()
+ autocmd! SwapExists
+ augroup test_swap_recover
+ autocmd!
+ autocmd SwapExists * let v:swapchoice = 'r'
+ augroup END
+
+
+ call mkdir('Xswap')
+ let $Xswap = 'foo' " Check for issue #4369.
+ set dir=Xswap//
+ " Create a valid swapfile by editing a file.
+ split Xswap/text
+ call setline(1, ['one', 'two', 'three'])
+ write " file is written, not modified
+ " read the swapfile as a Blob
+ let swapfile_name = swapname('%')
+ let swapfile_bytes = readfile(swapfile_name, 'B')
+
+ " Close the file and recreate the swap file.
+ quit
+ call writefile(swapfile_bytes, swapfile_name)
+ " Edit the file again. This triggers recovery.
+ try
+ split Xswap/text
+ catch
+ " E308 should be caught, not E305.
+ call assert_exception('E308:') " Original file may have been changed
+ endtry
+ " The file should be recovered.
+ call assert_equal(['one', 'two', 'three'], getline(1, 3))
+ quit!
+
+ call delete('Xswap/text')
+ call delete(swapfile_name)
+ call delete('Xswap', 'd')
+ unlet $Xswap
+ set dir&
+ augroup test_swap_recover
+ autocmd!
+ augroup END
+ augroup! test_swap_recover
+endfunc
+
+func Test_swap_recover_ext()
+ autocmd! SwapExists
+ augroup test_swap_recover_ext
+ autocmd!
+ autocmd SwapExists * let v:swapchoice = 'r'
+ augroup END
+
+
+ " Create a valid swapfile by editing a file with a special extension.
+ split Xtest.scr
+ call setline(1, ['one', 'two', 'three'])
+ write " file is written, not modified
+ write " write again to make sure the swapfile is created
+ " read the swapfile as a Blob
+ let swapfile_name = swapname('%')
+ let swapfile_bytes = readfile(swapfile_name, 'B')
+
+ " Close and delete the file and recreate the swap file.
+ quit
+ call delete('Xtest.scr')
+ call writefile(swapfile_bytes, swapfile_name)
+ " Edit the file again. This triggers recovery.
+ try
+ split Xtest.scr
+ catch
+ " E308 should be caught, not E306.
+ call assert_exception('E308:') " Original file may have been changed
+ endtry
+ " The file should be recovered.
+ call assert_equal(['one', 'two', 'three'], getline(1, 3))
+ quit!
+
+ call delete('Xtest.scr')
+ call delete(swapfile_name)
+ augroup test_swap_recover_ext
+ autocmd!
+ augroup END
+ augroup! test_swap_recover_ext
+endfunc
diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim
index 598a00476c..6cada1503f 100644
--- a/src/nvim/testdir/test_syntax.vim
+++ b/src/nvim/testdir/test_syntax.vim
@@ -1,6 +1,7 @@
" Test for syntax and syntax iskeyword option
source view_util.vim
+source screendump.vim
func GetSyntaxItem(pat)
let c = ''
@@ -501,9 +502,7 @@ func Test_syntax_c()
endif
call writefile([
\ '/* comment line at the top */',
- \ ' int',
- \ 'main(int argc, char **argv)// another comment',
- \ '{',
+ \ 'int main(int argc, char **argv) { // another comment',
\ '#if 0',
\ ' int not_used;',
\ '#else',
@@ -518,6 +517,7 @@ func Test_syntax_c()
\ ' for (int i = 0; i < count; ++i) {',
\ ' break;',
\ ' }',
+ \ " Note: asdf",
\ '}',
\ ], 'Xtest.c')
@@ -526,7 +526,8 @@ func Test_syntax_c()
let $COLORFGBG = '15;0'
let buf = RunVimInTerminal('Xtest.c', {})
- call VerifyScreenDump(buf, 'Test_syntax_c_01')
+ call term_sendkeys(buf, ":syn keyword Search Note\r")
+ call VerifyScreenDump(buf, 'Test_syntax_c_01', {})
call StopVimInTerminal(buf)
let $COLORFGBG = ''
diff --git a/src/nvim/testdir/test_tagfunc.vim b/src/nvim/testdir/test_tagfunc.vim
new file mode 100644
index 0000000000..242aa3a235
--- /dev/null
+++ b/src/nvim/testdir/test_tagfunc.vim
@@ -0,0 +1,84 @@
+" Test 'tagfunc'
+
+func TagFunc(pat, flag, info)
+ let g:tagfunc_args = [a:pat, a:flag, a:info]
+ let tags = []
+ for num in range(1,10)
+ let tags += [{
+ \ 'cmd': '2', 'name': 'nothing'.num, 'kind': 'm',
+ \ 'filename': 'Xfile1', 'user_data': 'somedata'.num,
+ \}]
+ endfor
+ return tags
+endfunc
+
+func Test_tagfunc()
+ set tagfunc=TagFunc
+ new Xfile1
+ call setline(1, ['empty', 'one()', 'empty'])
+ write
+
+ call assert_equal({'cmd': '2', 'static': 0,
+ \ 'name': 'nothing2', 'user_data': 'somedata2',
+ \ 'kind': 'm', 'filename': 'Xfile1'}, taglist('.')[1])
+
+ call settagstack(win_getid(), {'items': []})
+
+ tag arbitrary
+ call assert_equal('arbitrary', g:tagfunc_args[0])
+ call assert_equal('', g:tagfunc_args[1])
+ call assert_equal('somedata1', gettagstack().items[0].user_data)
+ 5tag arbitrary
+ call assert_equal('arbitrary', g:tagfunc_args[0])
+ call assert_equal('', g:tagfunc_args[1])
+ call assert_equal('somedata5', gettagstack().items[1].user_data)
+ pop
+ tag
+ call assert_equal('arbitrary', g:tagfunc_args[0])
+ call assert_equal('', g:tagfunc_args[1])
+ call assert_equal('somedata5', gettagstack().items[1].user_data)
+
+ let g:tagfunc_args=[]
+ execute "normal! \<c-]>"
+ call assert_equal('one', g:tagfunc_args[0])
+ call assert_equal('c', g:tagfunc_args[1])
+
+ set cpt=t
+ let g:tagfunc_args=[]
+ execute "normal! i\<c-n>\<c-y>"
+ call assert_equal('ci', g:tagfunc_args[1])
+ call assert_equal('nothing1', getline('.')[0:7])
+
+ func BadTagFunc1(...)
+ return 0
+ endfunc
+ func BadTagFunc2(...)
+ return [1]
+ endfunc
+ func BadTagFunc3(...)
+ return [{'name': 'foo'}]
+ endfunc
+
+ for &tagfunc in ['BadTagFunc1', 'BadTagFunc2', 'BadTagFunc3']
+ try
+ tag nothing
+ call assert_false(1, 'tag command should have failed')
+ catch
+ call assert_exception('E987:')
+ endtry
+ exe 'delf' &tagfunc
+ endfor
+
+ func NullTagFunc(...)
+ return v:null
+ endfunc
+ set tags= tfu=NullTagFunc
+ call assert_fails('tag nothing', 'E426')
+ delf NullTagFunc
+
+ bwipe!
+ set tags& tfu& cpt&
+ call delete('Xfile1')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim
index ce527a5e1d..fe98ef1ae2 100644
--- a/src/nvim/testdir/test_tagjump.vim
+++ b/src/nvim/testdir/test_tagjump.vim
@@ -449,7 +449,8 @@ func Test_tag_line_toolong()
call assert_report(v:exception)
catch /.*/
endtry
- call assert_equal('Ignoring long line in tags file', split(execute('messages'), '\n')[-1])
+ call assert_equal('Searching tags file Xtags', split(execute('messages'), '\n')[-1])
+
call writefile([
\ '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567 django/contrib/admin/templates/admin/edit_inline/stacked.html 16;" j line:16 language:HTML'
\ ], 'Xtags')
@@ -460,10 +461,52 @@ func Test_tag_line_toolong()
call assert_report(v:exception)
catch /.*/
endtry
- call assert_equal('Ignoring long line in tags file', split(execute('messages'), '\n')[-1])
+ call assert_equal('Searching tags file Xtags', split(execute('messages'), '\n')[-1])
+
+ " binary search works in file with long line
+ call writefile([
+ \ 'asdfasfd nowhere 16',
+ \ 'foobar Xsomewhere 3; " 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567',
+ \ 'zasdfasfd nowhere 16',
+ \ ], 'Xtags')
+ call writefile([
+ \ 'one',
+ \ 'two',
+ \ 'trhee',
+ \ 'four',
+ \ ], 'Xsomewhere')
+ tag foobar
+ call assert_equal('Xsomewhere', expand('%'))
+ call assert_equal(3, getcurpos()[1])
+
call delete('Xtags')
+ call delete('Xsomewhere')
set tags&
let &verbose = old_vbs
endfunc
+func Test_tagline()
+ call writefile([
+ \ 'provision Xtest.py /^ def provision(self, **kwargs):$/;" m line:1 language:Python class:Foo',
+ \ 'provision Xtest.py /^ def provision(self, **kwargs):$/;" m line:3 language:Python class:Bar',
+ \], 'Xtags')
+ call writefile([
+ \ ' def provision(self, **kwargs):',
+ \ ' pass',
+ \ ' def provision(self, **kwargs):',
+ \ ' pass',
+ \], 'Xtest.py')
+
+ set tags=Xtags
+
+ 1tag provision
+ call assert_equal(line('.'), 1)
+ 2tag provision
+ call assert_equal(line('.'), 3)
+
+ call delete('Xtags')
+ call delete('Xtest.py')
+ set tags&
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_textobjects.vim b/src/nvim/testdir/test_textobjects.vim
index 9194e0014d..b20c4df311 100644
--- a/src/nvim/testdir/test_textobjects.vim
+++ b/src/nvim/testdir/test_textobjects.vim
@@ -46,8 +46,18 @@ func Test_quote_selection_selection_exclusive()
new
call setline(1, "a 'bcde' f")
set selection=exclusive
+
exe "norm! fdvhi'y"
call assert_equal('bcde', @")
+
+ let @"='dummy'
+ exe "norm! $gevi'y"
+ call assert_equal('bcde', @")
+
+ let @"='dummy'
+ exe "norm! 0fbhvi'y"
+ call assert_equal('bcde', @")
+
set selection&vim
bw!
endfunc
diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim
index 24c735865c..bd63d94729 100644
--- a/src/nvim/testdir/test_timers.vim
+++ b/src/nvim/testdir/test_timers.vim
@@ -141,7 +141,7 @@ endfunc
func Test_delete_myself()
let g:called = 0
let t = timer_start(10, 'StopMyself', {'repeat': -1})
- call WaitFor('g:called == 2')
+ call WaitForAssert({-> assert_equal(2, g:called)})
call assert_equal(2, g:called)
call assert_equal([], timer_info(t))
endfunc
@@ -208,7 +208,7 @@ func Test_timer_errors()
let g:call_count = 0
let timer = timer_start(10, 'FuncWithError', {'repeat': -1})
" Timer will be stopped after failing 3 out of 3 times.
- call WaitFor('g:call_count == 3')
+ call WaitForAssert({-> assert_equal(3, g:call_count)})
sleep 50m
call assert_equal(3, g:call_count)
endfunc
@@ -226,7 +226,7 @@ func Test_timer_catch_error()
let g:call_count = 0
let timer = timer_start(10, 'FuncWithCaughtError', {'repeat': 4})
" Timer will not be stopped.
- call WaitFor('g:call_count == 4')
+ call WaitForAssert({-> assert_equal(4, g:call_count)})
sleep 50m
call assert_equal(4, g:call_count)
endfunc
@@ -252,4 +252,15 @@ func Test_peek_and_get_char()
call timer_stop(intr)
endfunc
+func Test_ex_mode()
+ " Function with an empty line.
+ func Foo(...)
+
+ endfunc
+ let timer = timer_start(40, function('g:Foo'), {'repeat':-1})
+ " This used to throw error E749.
+ exe "normal Qsleep 100m\rvi\r"
+ call timer_stop(timer)
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim
index f39e53d6dd..d2f13ff072 100644
--- a/src/nvim/testdir/test_vimscript.vim
+++ b/src/nvim/testdir/test_vimscript.vim
@@ -1284,7 +1284,7 @@ func s:DoNothing()
endfunc
func Test_script_local_func()
- set nocp viminfo+=nviminfo
+ set nocp nomore viminfo+=nviminfo
new
nnoremap <buffer> _x :call <SID>DoNothing()<bar>call <SID>DoLast()<bar>delfunc <SID>DoNothing<bar>delfunc <SID>DoLast<cr>
@@ -1409,6 +1409,76 @@ func Test_compound_assignment_operators()
let @/ = ''
endfunc
+func Test_function_defined_line()
+ if has('gui_running')
+ " Can't catch the output of gvim.
+ return
+ endif
+
+ let lines =<< trim [CODE]
+ " F1
+ func F1()
+ " F2
+ func F2()
+ "
+ "
+ "
+ return
+ endfunc
+ " F3
+ execute "func F3()\n\n\n\nreturn\nendfunc"
+ " F4
+ execute "func F4()\n
+ \\n
+ \\n
+ \\n
+ \return\n
+ \endfunc"
+ endfunc
+ " F5
+ execute "func F5()\n\n\n\nreturn\nendfunc"
+ " F6
+ execute "func F6()\n
+ \\n
+ \\n
+ \\n
+ \return\n
+ \endfunc"
+ call F1()
+ verbose func F1
+ verbose func F2
+ verbose func F3
+ verbose func F4
+ verbose func F5
+ verbose func F6
+ qall!
+ [CODE]
+
+ call writefile(lines, 'Xtest.vim')
+ let res = system(v:progpath .. ' --clean -es -X -S Xtest.vim')
+ call assert_equal(0, v:shell_error)
+
+ let m = matchstr(res, 'function F1()[^[:print:]]*[[:print:]]*')
+ call assert_match(' line 2$', m)
+
+ let m = matchstr(res, 'function F2()[^[:print:]]*[[:print:]]*')
+ call assert_match(' line 4$', m)
+
+ let m = matchstr(res, 'function F3()[^[:print:]]*[[:print:]]*')
+ call assert_match(' line 11$', m)
+
+ let m = matchstr(res, 'function F4()[^[:print:]]*[[:print:]]*')
+ call assert_match(' line 13$', m)
+
+ let m = matchstr(res, 'function F5()[^[:print:]]*[[:print:]]*')
+ call assert_match(' line 21$', m)
+
+ let m = matchstr(res, 'function F6()[^[:print:]]*[[:print:]]*')
+ call assert_match(' line 23$', m)
+
+ call delete('Xtest.vim')
+endfunc
+
"-------------------------------------------------------------------------------
" Modelines {{{1
" vim: ts=8 sw=4 tw=80 fdm=marker
diff --git a/src/nvim/testdir/test_virtualedit.vim b/src/nvim/testdir/test_virtualedit.vim
index 67adede8d7..8f992f7501 100644
--- a/src/nvim/testdir/test_virtualedit.vim
+++ b/src/nvim/testdir/test_virtualedit.vim
@@ -73,3 +73,147 @@ func Test_edit_CTRL_G()
bwipe!
set virtualedit=
endfunc
+
+func Test_edit_change()
+ new
+ set virtualedit=all
+ call setline(1, "\t⒌")
+ normal Cx
+ call assert_equal('x', getline(1))
+ bwipe!
+endfunc
+
+" Insert "keyword keyw", ESC, C CTRL-N, shows "keyword ykeyword".
+" Repeating CTRL-N fixes it. (Mary Ellen Foster)
+func Test_ve_completion()
+ new
+ set completeopt&vim
+ set virtualedit=all
+ exe "normal ikeyword keyw\<Esc>C\<C-N>"
+ call assert_equal('keyword keyword', getline(1))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Using "C" then then <CR> moves the last remaining character to the next
+" line. (Mary Ellen Foster)
+func Test_ve_del_to_eol()
+ new
+ set virtualedit=all
+ call append(0, 'all your base are belong to us')
+ call search('are', 'w')
+ exe "normal C\<CR>are belong to vim"
+ call assert_equal(['all your base ', 'are belong to vim'], getline(1, 2))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" When past the end of a line that ends in a single character "b" skips
+" that word.
+func Test_ve_b_past_eol()
+ new
+ set virtualedit=all
+ call append(0, '1 2 3 4 5 6')
+ normal gg^$15lbC7
+ call assert_equal('1 2 3 4 5 7', getline(1))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Make sure 'i', 'C', 'a', 'A' and 'D' works
+func Test_ve_ins_del()
+ new
+ set virtualedit=all
+ call append(0, ["'i'", "'C'", "'a'", "'A'", "'D'"])
+ call cursor(1, 1)
+ normal $4lix
+ call assert_equal("'i' x", getline(1))
+ call cursor(2, 1)
+ normal $4lCx
+ call assert_equal("'C' x", getline(2))
+ call cursor(3, 1)
+ normal $4lax
+ call assert_equal("'a' x", getline(3))
+ call cursor(4, 1)
+ normal $4lAx
+ call assert_equal("'A'x", getline(4))
+ call cursor(5, 1)
+ normal $4lDix
+ call assert_equal("'D' x", getline(5))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test for yank bug reported by Mark Waggoner.
+func Test_yank_block()
+ new
+ set virtualedit=block
+ call append(0, repeat(['this is a test'], 3))
+ exe "normal gg^2w\<C-V>3jy"
+ call assert_equal("a\na\na\n ", @")
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test "r" beyond the end of the line
+func Test_replace_after_eol()
+ new
+ set virtualedit=all
+ call append(0, '"r"')
+ normal gg$5lrxa
+ call assert_equal('"r" x', getline(1))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test "r" on a tab
+" Note that for this test, 'ts' must be 8 (the default).
+func Test_replace_on_tab()
+ new
+ set virtualedit=all
+ call append(0, "'r'\t")
+ normal gg^5lrxAy
+ call assert_equal("'r' x y", getline(1))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test to make sure 'x' can delete control characters
+func Test_ve_del_ctrl_chars()
+ new
+ set virtualedit=all
+ call append(0, "a\<C-V>b\<CR>sd")
+ set display=uhex
+ normal gg^xxxxxxi[text]
+ set display=
+ call assert_equal('[text]', getline(1))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test for ^Y/^E due to bad w_virtcol value, reported by
+" Roy <royl@netropolis.net>.
+func Test_ins_copy_char()
+ new
+ set virtualedit=all
+ call append(0, 'abcv8efi.him2kl')
+ exe "normal gg^O\<Esc>3li\<C-E>\<Esc>4li\<C-E>\<Esc>4li\<C-E> <--"
+ exe "normal j^o\<Esc>4li\<C-Y>\<Esc>4li\<C-Y>\<Esc>4li\<C-Y> <--"
+ call assert_equal(' v i m <--', getline(1))
+ call assert_equal(' 8 . 2 <--', getline(3))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test for yanking and pasting using the small delete register
+func Test_yank_paste_small_del_reg()
+ new
+ set virtualedit=all
+ call append(0, "foo, bar")
+ normal ggdewve"-p
+ call assert_equal(', foo', getline(1))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim
index c87c0a0af4..aaa291f87d 100644
--- a/src/nvim/testdir/test_window_cmd.vim
+++ b/src/nvim/testdir/test_window_cmd.vim
@@ -69,18 +69,6 @@ function Test_window_cmd_wincmd_gf()
augroup! test_window_cmd_wincmd_gf
endfunc
-func Test_next_split_all()
- " This was causing an illegal memory access.
- n x
- norm axxx
- split
- split
- s/x
- s/x
- all
- bwipe!
-endfunc
-
func Test_window_quit()
e Xa
split Xb
@@ -117,15 +105,71 @@ func Test_window_vertical_split()
bw
endfunc
+" Test the ":wincmd ^" and "<C-W>^" commands.
func Test_window_split_edit_alternate()
- e Xa
- e Xb
+ " Test for failure when the alternate buffer/file no longer exists.
+ edit Xfoo | %bw
+ call assert_fails(':wincmd ^', 'E23')
+
+ " Test for the expected behavior when we have two named buffers.
+ edit Xfoo | edit Xbar
wincmd ^
- call assert_equal('Xa', bufname(winbufnr(1)))
- call assert_equal('Xb', bufname(winbufnr(2)))
+ call assert_equal('Xfoo', bufname(winbufnr(1)))
+ call assert_equal('Xbar', bufname(winbufnr(2)))
+ only
- bw Xa Xb
+ " Test for the expected behavior when the alternate buffer is not named.
+ enew | let l:nr1 = bufnr('%')
+ edit Xfoo | let l:nr2 = bufnr('%')
+ wincmd ^
+ call assert_equal(l:nr1, winbufnr(1))
+ call assert_equal(l:nr2, winbufnr(2))
+ only
+
+ " FIXME: this currently fails on AppVeyor, but passes locally
+ if !has('win32')
+ " Test the Normal mode command.
+ call feedkeys("\<C-W>\<C-^>", 'tx')
+ call assert_equal(l:nr2, winbufnr(1))
+ call assert_equal(l:nr1, winbufnr(2))
+ endif
+
+ %bw!
+endfunc
+
+" Test the ":[count]wincmd ^" and "[count]<C-W>^" commands.
+func Test_window_split_edit_bufnr()
+
+ %bwipeout
+ let l:nr = bufnr('%') + 1
+ call assert_fails(':execute "normal! ' . l:nr . '\<C-W>\<C-^>"', 'E92')
+ call assert_fails(':' . l:nr . 'wincmd ^', 'E16')
+ call assert_fails(':0wincmd ^', 'E16')
+
+ edit Xfoo | edit Xbar | edit Xbaz
+ let l:foo_nr = bufnr('Xfoo')
+ let l:bar_nr = bufnr('Xbar')
+ let l:baz_nr = bufnr('Xbaz')
+
+ " FIXME: this currently fails on AppVeyor, but passes locally
+ if !has('win32')
+ call feedkeys(l:foo_nr . "\<C-W>\<C-^>", 'tx')
+ call assert_equal('Xfoo', bufname(winbufnr(1)))
+ call assert_equal('Xbaz', bufname(winbufnr(2)))
+ only
+
+ call feedkeys(l:bar_nr . "\<C-W>\<C-^>", 'tx')
+ call assert_equal('Xbar', bufname(winbufnr(1)))
+ call assert_equal('Xfoo', bufname(winbufnr(2)))
+ only
+
+ execute l:baz_nr . 'wincmd ^'
+ call assert_equal('Xbaz', bufname(winbufnr(1)))
+ call assert_equal('Xbar', bufname(winbufnr(2)))
+ endif
+
+ %bw!
endfunc
func Test_window_preview()
@@ -446,6 +490,17 @@ func Test_window_newtab()
%bw!
endfunc
+func Test_next_split_all()
+ " This was causing an illegal memory access.
+ n x
+ norm axxx
+ split
+ split
+ s/x
+ s/x
+ all
+ bwipe!
+endfunc
" Tests for adjusting window and contents
func GetScreenStr(row)
@@ -485,6 +540,11 @@ func Test_window_contents()
call test_garbagecollect_now()
endfunc
+func Test_window_colon_command()
+ " This was reading invalid memory.
+ exe "norm! v\<C-W>:\<C-U>echo v:version"
+endfunc
+
func Test_access_freed_mem()
" This was accessing freed memory
au * 0 vs xxx
@@ -700,6 +760,42 @@ func Test_relative_cursor_second_line_after_resize()
let &so = so_save
endfunc
+func Test_split_noscroll()
+ let so_save = &so
+ enew
+ call setline(1, range(1, 8))
+ normal 100%
+ split
+
+ 1wincmd w
+ let winid1 = win_getid()
+ let info1 = getwininfo(winid1)[0]
+
+ 2wincmd w
+ let winid2 = win_getid()
+ let info2 = getwininfo(winid2)[0]
+
+ call assert_equal(1, info1.topline)
+ call assert_equal(1, info2.topline)
+
+ " window that fits all lines by itself, but not when split: closing other
+ " window should restore fraction.
+ only!
+ call setline(1, range(1, &lines - 10))
+ exe &lines / 4
+ let winid1 = win_getid()
+ let info1 = getwininfo(winid1)[0]
+ call assert_equal(1, info1.topline)
+ new
+ redraw
+ close
+ let info1 = getwininfo(winid1)[0]
+ call assert_equal(1, info1.topline)
+
+ bwipe!
+ let &so = so_save
+endfunc
+
" Tests for the winnr() function
func Test_winnr()
only | tabonly
@@ -745,9 +841,4 @@ func Test_winnr()
only | tabonly
endfunc
-func Test_window_colon_command()
- " This was reading invalid memory.
- exe "norm! v\<C-W>:\<C-U>echo v:version"
-endfunc
-
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/view_util.vim b/src/nvim/testdir/view_util.vim
index 29ea073f97..520f65c1e7 100644
--- a/src/nvim/testdir/view_util.vim
+++ b/src/nvim/testdir/view_util.vim
@@ -42,6 +42,7 @@ endfunction
function! NewWindow(height, width) abort
exe a:height . 'new'
exe a:width . 'vsp'
+ set winfixwidth winfixheight
redraw!
endfunction
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index ed9b410a19..c71378463f 100644
--- a/src/nvim/tui/input.c
+++ b/src/nvim/tui/input.c
@@ -26,7 +26,8 @@ void tinput_init(TermInput *input, Loop *loop)
{
input->loop = loop;
input->paste = 0;
- input->in_fd = 0;
+ input->in_fd = STDIN_FILENO;
+ input->waiting_for_bg_response = 0;
input->key_buffer = rbuffer_new(KEY_BUFFER_SIZE);
uv_mutex_init(&input->key_buffer_mutex);
uv_cond_init(&input->key_buffer_cond);
@@ -35,7 +36,7 @@ void tinput_init(TermInput *input, Loop *loop)
// echo q | nvim -es
// ls *.md | xargs nvim
#ifdef WIN32
- if (!os_isatty(0)) {
+ if (!os_isatty(input->in_fd)) {
const HANDLE conin_handle = CreateFile("CONIN$",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
@@ -45,8 +46,8 @@ void tinput_init(TermInput *input, Loop *loop)
assert(input->in_fd != -1);
}
#else
- if (!os_isatty(0) && os_isatty(2)) {
- input->in_fd = 2;
+ if (!os_isatty(input->in_fd) && os_isatty(STDERR_FILENO)) {
+ input->in_fd = STDERR_FILENO;
}
#endif
input_global_fd_init(input->in_fd);
@@ -443,6 +444,9 @@ static void set_bg_deferred(void **argv)
// [1] https://en.wikipedia.org/wiki/Luma_%28video%29
static bool handle_background_color(TermInput *input)
{
+ if (input->waiting_for_bg_response <= 0) {
+ return false;
+ }
size_t count = 0;
size_t component = 0;
size_t header_size = 0;
@@ -461,8 +465,13 @@ static bool handle_background_color(TermInput *input)
header_size = 10;
num_components = 4;
} else {
+ input->waiting_for_bg_response--;
+ if (input->waiting_for_bg_response == 0) {
+ DLOG("did not get a response for terminal background query");
+ }
return false;
}
+ input->waiting_for_bg_response = 0;
rbuffer_consumed(input->read_stream.buffer, header_size);
RBUFFER_EACH(input->read_stream.buffer, c, i) {
count = i + 1;
@@ -503,6 +512,12 @@ static bool handle_background_color(TermInput *input)
}
return true;
}
+#ifdef UNIT_TESTING
+bool ut_handle_background_color(TermInput *input)
+{
+ return handle_background_color(input);
+}
+#endif
static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_,
void *data, bool eof)
diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h
index a4071fab40..77bd6fa132 100644
--- a/src/nvim/tui/input.h
+++ b/src/nvim/tui/input.h
@@ -12,6 +12,7 @@ typedef struct term_input {
// Phases: -1=all 0=disabled 1=first-chunk 2=continue 3=last-chunk
int8_t paste;
bool waiting;
+ int8_t waiting_for_bg_response;
TermKey *tk;
#if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18
TermKey_Terminfo_Getstr_Hook *tk_ti_hook_fn; ///< libtermkey terminfo hook
@@ -28,4 +29,8 @@ typedef struct term_input {
# include "tui/input.h.generated.h"
#endif
+#ifdef UNIT_TESTING
+bool ut_handle_background_color(TermInput *input);
+#endif
+
#endif // NVIM_TUI_INPUT_H
diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c
index 14023ce2cb..03173afe07 100644
--- a/src/nvim/tui/terminfo.c
+++ b/src/nvim/tui/terminfo.c
@@ -47,9 +47,8 @@ bool terminfo_is_bsd_console(const char *term)
// like cursor-shaping. Assume that TERM=xterm is degraded. #8644
return strequal(term, "xterm") && !!os_getenv("XTERM_VERSION");
# endif
-#else
- return false;
#endif
+ return false;
}
/// Loads a built-in terminfo db when we (unibilium) failed to load a terminfo
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index 791756e5c5..60e1353000 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -93,7 +93,7 @@ typedef struct {
int out_fd;
bool scroll_region_is_full_screen;
bool can_change_scroll_region;
- bool can_set_lr_margin;
+ bool can_set_lr_margin; // smglr
bool can_set_left_right_margin;
bool can_scroll;
bool can_erase_chars;
@@ -220,7 +220,7 @@ static void terminfo_start(UI *ui)
data->unibi_ext.reset_cursor_style = -1;
data->unibi_ext.get_bg = -1;
data->unibi_ext.set_underline_color = -1;
- data->out_fd = 1;
+ data->out_fd = STDOUT_FILENO;
data->out_isatty = os_isatty(data->out_fd);
const char *term = os_getenv("TERM");
@@ -234,7 +234,9 @@ static void terminfo_start(UI *ui)
// Set up unibilium/terminfo.
char *termname = NULL;
if (term) {
+ os_env_var_lock();
data->ut = unibi_from_term(term);
+ os_env_var_unlock();
if (data->ut) {
termname = xstrdup(term);
}
@@ -296,6 +298,7 @@ static void terminfo_start(UI *ui)
unibi_out(ui, unibi_keypad_xmit);
unibi_out(ui, unibi_clear_screen);
// Ask the terminal to send us the background color.
+ data->input.waiting_for_bg_response = 5;
unibi_out_ext(ui, data->unibi_ext.get_bg);
// Enable bracketed paste
unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste);
@@ -312,6 +315,7 @@ static void terminfo_start(UI *ui)
uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0);
uv_pipe_open(&data->output_handle.pipe, data->out_fd);
}
+ flush_buf(ui);
}
static void terminfo_stop(UI *ui)
@@ -363,6 +367,7 @@ static void tui_terminal_after_startup(UI *ui)
// Emit this after Nvim startup, not during. This works around a tmux
// 2.3 bug(?) which caused slow drawing during startup. #7649
unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting);
+ flush_buf(ui);
}
static void tui_terminal_stop(UI *ui)
@@ -430,9 +435,6 @@ static void tui_main(UIBridgeData *bridge, UI *ui)
}
if (!tui_is_stopped(ui)) {
tui_terminal_after_startup(ui);
- // Tickle `main_loop` with a dummy event, else the initial "focus-gained"
- // terminal response may not get processed until user hits a key.
- loop_schedule_deferred(&main_loop, event_create(loop_dummy_event, 0));
}
// "Passive" (I/O-driven) loop: TUI thread "main loop".
while (!tui_is_stopped(ui)) {
@@ -513,20 +515,8 @@ static void update_attrs(UI *ui, int attr_id)
}
data->print_attr_id = attr_id;
HlAttrs attrs = kv_A(data->attrs, (size_t)attr_id);
-
- int fg = ui->rgb ? attrs.rgb_fg_color : (attrs.cterm_fg_color - 1);
- if (fg == -1) {
- fg = ui->rgb ? data->clear_attrs.rgb_fg_color
- : (data->clear_attrs.cterm_fg_color - 1);
- }
-
- int bg = ui->rgb ? attrs.rgb_bg_color : (attrs.cterm_bg_color - 1);
- if (bg == -1) {
- bg = ui->rgb ? data->clear_attrs.rgb_bg_color
- : (data->clear_attrs.cterm_bg_color - 1);
- }
-
int attr = ui->rgb ? attrs.rgb_ae_attr : attrs.cterm_ae_attr;
+
bool bold = attr & HL_BOLD;
bool italic = attr & HL_ITALIC;
bool reverse = attr & HL_INVERSE;
@@ -594,14 +584,29 @@ static void update_attrs(UI *ui, int attr_id)
unibi_out_ext(ui, data->unibi_ext.set_underline_color);
}
}
- if (ui->rgb) {
+
+ int fg, bg;
+ if (ui->rgb && !(attr & HL_FG_INDEXED)) {
+ fg = ((attrs.rgb_fg_color != -1)
+ ? attrs.rgb_fg_color : data->clear_attrs.rgb_fg_color);
if (fg != -1) {
UNIBI_SET_NUM_VAR(data->params[0], (fg >> 16) & 0xff); // red
UNIBI_SET_NUM_VAR(data->params[1], (fg >> 8) & 0xff); // green
UNIBI_SET_NUM_VAR(data->params[2], fg & 0xff); // blue
unibi_out_ext(ui, data->unibi_ext.set_rgb_foreground);
}
+ } else {
+ fg = (attrs.cterm_fg_color
+ ? attrs.cterm_fg_color - 1 : (data->clear_attrs.cterm_fg_color - 1));
+ if (fg != -1) {
+ UNIBI_SET_NUM_VAR(data->params[0], fg);
+ unibi_out(ui, unibi_set_a_foreground);
+ }
+ }
+ if (ui->rgb && !(attr & HL_BG_INDEXED)) {
+ bg = ((attrs.rgb_bg_color != -1)
+ ? attrs.rgb_bg_color : data->clear_attrs.rgb_bg_color);
if (bg != -1) {
UNIBI_SET_NUM_VAR(data->params[0], (bg >> 16) & 0xff); // red
UNIBI_SET_NUM_VAR(data->params[1], (bg >> 8) & 0xff); // green
@@ -609,17 +614,15 @@ static void update_attrs(UI *ui, int attr_id)
unibi_out_ext(ui, data->unibi_ext.set_rgb_background);
}
} else {
- if (fg != -1) {
- UNIBI_SET_NUM_VAR(data->params[0], fg);
- unibi_out(ui, unibi_set_a_foreground);
- }
-
+ bg = (attrs.cterm_bg_color
+ ? attrs.cterm_bg_color - 1 : (data->clear_attrs.cterm_bg_color - 1));
if (bg != -1) {
UNIBI_SET_NUM_VAR(data->params[0], bg);
unibi_out(ui, unibi_set_a_background);
}
}
+
data->default_attr = fg == -1 && bg == -1
&& !bold && !italic && !underline && !undercurl && !reverse && !standout
&& !strikethrough;
@@ -1598,6 +1601,12 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
unibi_set_if_empty(ut, unibi_set_lr_margin, "\x1b[%i%p1%d;%p2%ds");
unibi_set_if_empty(ut, unibi_set_left_margin_parm, "\x1b[%i%p1%ds");
unibi_set_if_empty(ut, unibi_set_right_margin_parm, "\x1b[%i;%p2%ds");
+ } else {
+ // Fix things advertised via TERM=xterm, for non-xterm.
+ if (unibi_get_str(ut, unibi_set_lr_margin)) {
+ ILOG("Disabling smglr with TERM=xterm for non-xterm.");
+ unibi_set_str(ut, unibi_set_lr_margin, NULL);
+ }
}
#ifdef WIN32
diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c
index 20ffc1b88e..e582d8f859 100644
--- a/src/nvim/ui_compositor.c
+++ b/src/nvim/ui_compositor.c
@@ -425,6 +425,15 @@ static void compose_line(Integer row, Integer startcol, Integer endcol,
flags = flags & ~kLineFlagWrap;
}
+ for (int i = skipstart; i < (endcol-skipend)-startcol; i++) {
+ if (attrbuf[i] < 0) {
+ if (rdb_flags & RDB_INVALID) {
+ abort();
+ } else {
+ attrbuf[i] = 0;
+ }
+ }
+ }
ui_composed_call_raw_line(1, row, startcol+skipstart,
endcol-skipend, endcol-skipend, 0, flags,
(const schar_T *)linebuf+skipstart,
@@ -535,6 +544,11 @@ static void ui_comp_raw_line(UI *ui, Integer grid, Integer row,
} else {
compose_debug(row, row+1, startcol, endcol, dbghl_normal, false);
compose_debug(row, row+1, endcol, clearcol, dbghl_clear, true);
+#ifndef NDEBUG
+ for (int i = 0; i < endcol-startcol; i++) {
+ assert(attrs[i] >= 0);
+ }
+#endif
ui_composed_call_raw_line(1, row, startcol, endcol, clearcol, clearattr,
flags, chunk, attrs);
}
@@ -612,7 +626,7 @@ static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top,
if (covered || curgrid->blending) {
// TODO(bfredl):
// 1. check if rectangles actually overlap
- // 2. calulate subareas that can scroll.
+ // 2. calculate subareas that can scroll.
compose_debug(top, bot, left, right, dbghl_recompose, true);
for (int r = (int)(top + MAX(-rows, 0)); r < bot - MAX(rows, 0); r++) {
// TODO(bfredl): workaround for win_update() performing two scrolls in a
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 035613c7fd..539d42765d 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -91,7 +91,9 @@
#include "nvim/fileio.h"
#include "nvim/fold.h"
#include "nvim/buffer_updates.h"
+#include "nvim/pos.h" // MAXLNUM
#include "nvim/mark.h"
+#include "nvim/mark_extended.h"
#include "nvim/memline.h"
#include "nvim/message.h"
#include "nvim/misc1.h"
@@ -106,6 +108,7 @@
#include "nvim/types.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
+#include "nvim/lib/kvec.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "undo.c.generated.h"
@@ -222,9 +225,6 @@ int u_save_cursor(void)
*/
int u_save(linenr_T top, linenr_T bot)
{
- if (undo_off)
- return OK;
-
if (top >= bot || bot > (curbuf->b_ml.ml_line_count + 1)) {
return FAIL; /* rely on caller to do error messages */
}
@@ -243,10 +243,7 @@ int u_save(linenr_T top, linenr_T bot)
*/
int u_savesub(linenr_T lnum)
{
- if (undo_off)
- return OK;
-
- return u_savecommon(lnum - 1, lnum + 1, lnum + 1, FALSE);
+ return u_savecommon(lnum - 1, lnum + 1, lnum + 1, false);
}
/*
@@ -257,10 +254,7 @@ int u_savesub(linenr_T lnum)
*/
int u_inssub(linenr_T lnum)
{
- if (undo_off)
- return OK;
-
- return u_savecommon(lnum - 1, lnum, lnum + 1, FALSE);
+ return u_savecommon(lnum - 1, lnum, lnum + 1, false);
}
/*
@@ -272,9 +266,6 @@ int u_inssub(linenr_T lnum)
*/
int u_savedel(linenr_T lnum, long nlines)
{
- if (undo_off)
- return OK;
-
return u_savecommon(lnum - 1, lnum + nlines,
nlines == curbuf->b_ml.ml_line_count ? 2 : lnum, FALSE);
}
@@ -384,6 +375,7 @@ int u_savecommon(linenr_T top, linenr_T bot, linenr_T newbot, int reload)
* up the undo info when out of memory.
*/
uhp = xmalloc(sizeof(u_header_T));
+ kv_init(uhp->uh_extmark);
#ifdef U_DEBUG
uhp->uh_magic = UH_MAGIC;
#endif
@@ -2249,10 +2241,10 @@ static void u_undoredo(int undo, bool do_buf_event)
xfree((char_u *)uep->ue_array);
}
- /* adjust marks */
+ // Adjust marks
if (oldsize != newsize) {
mark_adjust(top + 1, top + oldsize, (long)MAXLNUM,
- (long)newsize - (long)oldsize, false);
+ (long)newsize - (long)oldsize, false, kExtmarkNOOP);
if (curbuf->b_op_start.lnum > top + oldsize) {
curbuf->b_op_start.lnum += newsize - oldsize;
}
@@ -2285,6 +2277,23 @@ static void u_undoredo(int undo, bool do_buf_event)
newlist = uep;
}
+ // Adjust Extmarks
+ ExtmarkUndoObject undo_info;
+ if (undo) {
+ for (i = (int)kv_size(curhead->uh_extmark) - 1; i > -1; i--) {
+ undo_info = kv_A(curhead->uh_extmark, i);
+ extmark_apply_undo(undo_info, undo);
+ }
+ // redo
+ } else {
+ for (i = 0; i < (int)kv_size(curhead->uh_extmark); i++) {
+ undo_info = kv_A(curhead->uh_extmark, i);
+ extmark_apply_undo(undo_info, undo);
+ }
+ }
+ // finish Adjusting extmarks
+
+
curhead->uh_entry = newlist;
curhead->uh_flags = new_flags;
if ((old_flags & UH_EMPTYBUF) && BUFEMPTY()) {
@@ -2828,6 +2837,8 @@ u_freeentries(
u_freeentry(uep, uep->ue_size);
}
+ kv_destroy(uhp->uh_extmark);
+
#ifdef U_DEBUG
uhp->uh_magic = 0;
#endif
@@ -2902,9 +2913,6 @@ void u_undoline(void)
colnr_T t;
char_u *oldp;
- if (undo_off)
- return;
-
if (curbuf->b_u_line_ptr == NULL
|| curbuf->b_u_line_lnum > curbuf->b_ml.ml_line_count) {
beep_flush();
@@ -3022,3 +3030,34 @@ list_T *u_eval_tree(const u_header_T *const first_uhp)
return list;
}
+
+// Given the buffer, Return the undo header. If none is set, set one first.
+// NULL will be returned if e.g undolevels = -1 (undo disabled)
+u_header_T *u_force_get_undo_header(buf_T *buf)
+{
+ u_header_T *uhp = NULL;
+ if (buf->b_u_curhead != NULL) {
+ uhp = buf->b_u_curhead;
+ } else if (buf->b_u_newhead) {
+ uhp = buf->b_u_newhead;
+ }
+ // Create the first undo header for the buffer
+ if (!uhp) {
+ // Undo is normally invoked in change code, which already has swapped
+ // curbuf.
+ buf_T *save_curbuf = curbuf;
+ curbuf = buf;
+ // Args are tricky: this means replace empty range by empty range..
+ u_savecommon(0, 1, 1, true);
+ curbuf = save_curbuf;
+
+ uhp = buf->b_u_curhead;
+ if (!uhp) {
+ uhp = buf->b_u_newhead;
+ if (get_undolevel() > 0 && !uhp) {
+ abort();
+ }
+ }
+ }
+ return uhp;
+}
diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h
index 6c7e2bba41..0fa3b415ec 100644
--- a/src/nvim/undo_defs.h
+++ b/src/nvim/undo_defs.h
@@ -4,6 +4,7 @@
#include <time.h> // for time_t
#include "nvim/pos.h"
+#include "nvim/mark_extended_defs.h"
#include "nvim/mark_defs.h"
typedef struct u_header u_header_T;
@@ -56,14 +57,15 @@ struct u_header {
u_entry_T *uh_getbot_entry; /* pointer to where ue_bot must be set */
pos_T uh_cursor; /* cursor position before saving */
long uh_cursor_vcol;
- int uh_flags; /* see below */
- fmark_T uh_namedm[NMARKS]; /* marks before undo/after redo */
- visualinfo_T uh_visual; /* Visual areas before undo/after redo */
- time_t uh_time; /* timestamp when the change was made */
- long uh_save_nr; /* set when the file was saved after the
- changes in this block */
+ int uh_flags; // see below
+ fmark_T uh_namedm[NMARKS]; // marks before undo/after redo
+ extmark_undo_vec_t uh_extmark; // info to move extmarks
+ visualinfo_T uh_visual; // Visual areas before undo/after redo
+ time_t uh_time; // timestamp when the change was made
+ long uh_save_nr; // set when the file was saved after the
+ // changes in this block
#ifdef U_DEBUG
- int uh_magic; /* magic number to check allocation */
+ int uh_magic; // magic number to check allocation
#endif
};
diff --git a/src/nvim/version.c b/src/nvim/version.c
index b0619d6273..4cadc9fd6c 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -83,7 +83,7 @@ static const int included_patches[] = {
1838,
1837,
1836,
- // 1835,
+ 1835,
1834,
1833,
1832,
@@ -106,7 +106,7 @@ static const int included_patches[] = {
1815,
1814,
1813,
- // 1812,
+ 1812,
1811,
1810,
1809,
@@ -120,12 +120,12 @@ static const int included_patches[] = {
1801,
1800,
1799,
- // 1798,
+ 1798,
1797,
1796,
1795,
// 1794,
- // 1793,
+ 1793,
1792,
1791,
1790,
@@ -142,13 +142,13 @@ static const int included_patches[] = {
1779,
1778,
1777,
- // 1776,
+ 1776,
1775,
// 1774,
1773,
- // 1772,
- // 1771,
- // 1770,
+ 1772,
+ 1771,
+ 1770,
// 1769,
1768,
// 1767,
@@ -164,7 +164,7 @@ static const int included_patches[] = {
1757,
1756,
1755,
- // 1754,
+ 1754,
1753,
1752,
1751,
@@ -176,7 +176,7 @@ static const int included_patches[] = {
// 1745,
// 1744,
// 1743,
- // 1742,
+ 1742,
1741,
1740,
1739,
@@ -185,7 +185,7 @@ static const int included_patches[] = {
1736,
1735,
1734,
- // 1733,
+ 1733,
// 1732,
1731,
1730,
@@ -193,10 +193,10 @@ static const int included_patches[] = {
1728,
1727,
1726,
- // 1725,
+ 1725,
1724,
1723,
- // 1722,
+ 1722,
1721,
1720,
1719,
@@ -207,7 +207,7 @@ static const int included_patches[] = {
1714,
1713,
// 1712,
- // 1711,
+ 1711,
1710,
1709,
1708,
@@ -233,7 +233,7 @@ static const int included_patches[] = {
1688,
1687,
1686,
- // 1685,
+ 1685,
1684,
1683,
1682,
@@ -252,8 +252,8 @@ static const int included_patches[] = {
1669,
// 1668,
1667,
- // 1666,
- // 1665,
+ 1666,
+ 1665,
1664,
1663,
1662,
@@ -294,12 +294,12 @@ static const int included_patches[] = {
1627,
1626,
1625,
- // 1624,
+ 1624,
1623,
1622,
- // 1621,
+ 1621,
1620,
- // 1619,
+ 1619,
1618,
// 1617,
// 1616,
@@ -336,7 +336,7 @@ static const int included_patches[] = {
1585,
1584,
1583,
- // 1582,
+ 1582,
1581,
1580,
1579,
@@ -379,7 +379,7 @@ static const int included_patches[] = {
1542,
1541,
// 1540,
- // 1539,
+ 1539,
// 1538,
1537,
1536,
@@ -389,13 +389,13 @@ static const int included_patches[] = {
1532,
// 1531,
1530,
- // 1529,
+ 1529,
1528,
1527,
1526,
// 1525,
1524,
- // 1523,
+ 1523,
// 1522,
1521,
// 1520,
@@ -426,7 +426,7 @@ static const int included_patches[] = {
// 1495,
1494,
1493,
- // 1492,
+ 1492,
// 1491,
1490,
1489,
@@ -467,10 +467,10 @@ static const int included_patches[] = {
// 1454,
1453,
1452,
- // 1451,
+ 1451,
1450,
- // 1449,
- // 1448,
+ 1449,
+ 1448,
1447,
1446,
1445,
@@ -513,7 +513,7 @@ static const int included_patches[] = {
1408,
1407,
1406,
- // 1405,
+ 1405,
1404,
1403,
1402,
@@ -552,7 +552,7 @@ static const int included_patches[] = {
1369,
1368,
// 1367,
- // 1366,
+ 1366,
1365,
1364,
1363,
@@ -742,7 +742,7 @@ static const int included_patches[] = {
1179,
1178,
1177,
- // 1176,
+ 1176,
1175,
1174,
1173,
@@ -784,7 +784,7 @@ static const int included_patches[] = {
1137,
1136,
1135,
- // 1134,
+ 1134,
1133,
1132,
1131,
@@ -809,7 +809,7 @@ static const int included_patches[] = {
1112,
1111,
1110,
- // 1109,
+ 1109,
1108,
1107,
1106,
@@ -880,7 +880,7 @@ static const int included_patches[] = {
1041,
1040,
1039,
- // 1038,
+ 1038,
1037,
1036,
1035,
@@ -948,7 +948,7 @@ static const int included_patches[] = {
973,
972,
971,
- // 970,
+ 970,
969,
968,
967,
@@ -977,14 +977,14 @@ static const int included_patches[] = {
944,
943,
942,
- // 941,
+ 941,
940,
939,
938,
- // 937,
+ 937,
936,
935,
- // 934,
+ 934,
933,
932,
931,
@@ -1004,7 +1004,7 @@ static const int included_patches[] = {
917,
916,
915,
- // 914,
+ 914,
913,
912,
911,
@@ -1093,7 +1093,7 @@ static const int included_patches[] = {
828,
827,
826,
- // 825,
+ 825,
824,
823,
822,
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 1e6de73549..76fc36607c 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -70,8 +70,8 @@ static char *m_onlyone = N_("Already only one window");
/*
* all CTRL-W window commands are handled here, called from normal_cmd().
*/
-void
-do_window (
+void
+do_window(
int nchar,
long Prenum,
int xchar /* extra char from ":wincmd gx" or NUL */
@@ -131,8 +131,20 @@ do_window (
case '^':
CHECK_CMDWIN;
reset_VIsual_and_resel(); // stop Visual mode
- cmd_with_count("split #", (char_u *)cbuf, sizeof(cbuf), Prenum);
- do_cmdline_cmd(cbuf);
+
+ if (buflist_findnr(Prenum == 0 ? curwin->w_alt_fnum : Prenum) == NULL) {
+ if (Prenum == 0) {
+ EMSG(_(e_noalt));
+ } else {
+ EMSGN(_("E92: Buffer %" PRId64 " not found"), Prenum);
+ }
+ break;
+ }
+
+ if (!curbuf_locked() && win_split(0, 0) == OK) {
+ (void)buflist_getfile(Prenum == 0 ? curwin->w_alt_fnum : Prenum,
+ (linenr_T)0, GETF_ALT, false);
+ }
break;
/* open new window */
@@ -621,6 +633,12 @@ void win_set_minimal_style(win_T *wp)
xfree(wp->w_p_scl);
wp->w_p_scl = (char_u *)xstrdup("auto");
}
+
+ // colorcolumn: cleared
+ if (wp->w_p_cc != NULL && *wp->w_p_cc != NUL) {
+ xfree(wp->w_p_cc);
+ wp->w_p_cc = (char_u *)xstrdup("");
+ }
}
void win_config_float(win_T *wp, FloatConfig fconfig)
@@ -1518,17 +1536,22 @@ static void win_init(win_T *newp, win_T *oldp, int flags)
/* Don't copy the location list. */
newp->w_llist = NULL;
newp->w_llist_ref = NULL;
- } else
- copy_loclist(oldp, newp);
+ } else {
+ copy_loclist_stack(oldp, newp);
+ }
newp->w_localdir = (oldp->w_localdir == NULL)
? NULL : vim_strsave(oldp->w_localdir);
/* copy tagstack and folds */
for (i = 0; i < oldp->w_tagstacklen; i++) {
- newp->w_tagstack[i] = oldp->w_tagstack[i];
- if (newp->w_tagstack[i].tagname != NULL)
- newp->w_tagstack[i].tagname =
- vim_strsave(newp->w_tagstack[i].tagname);
+ taggy_T *tag = &newp->w_tagstack[i];
+ *tag = oldp->w_tagstack[i];
+ if (tag->tagname != NULL) {
+ tag->tagname = vim_strsave(tag->tagname);
+ }
+ if (tag->user_data != NULL) {
+ tag->user_data = vim_strsave(tag->user_data);
+ }
}
newp->w_tagstackidx = oldp->w_tagstackidx;
newp->w_tagstacklen = oldp->w_tagstacklen;
@@ -1558,7 +1581,7 @@ static void win_init_some(win_T *newp, win_T *oldp)
/// Check if "win" is a pointer to an existing window in the current tabpage.
///
/// @param win window to check
-bool win_valid(win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+bool win_valid(const win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
if (win == NULL) {
return false;
@@ -2401,6 +2424,7 @@ int win_close(win_T *win, bool free_buf)
bool help_window = false;
tabpage_T *prev_curtab = curtab;
frame_T *win_frame = win->w_floating ? NULL : win->w_frame->fr_parent;
+ const bool had_diffmode = win->w_p_diff;
if (last_window() && !win->w_floating) {
EMSG(_("E444: Cannot close last window"));
@@ -2625,6 +2649,22 @@ int win_close(win_T *win, bool free_buf)
if (help_window)
restore_snapshot(SNAP_HELP_IDX, close_curwin);
+ // If the window had 'diff' set and now there is only one window left in
+ // the tab page with 'diff' set, and "closeoff" is in 'diffopt', then
+ // execute ":diffoff!".
+ if (diffopt_closeoff() && had_diffmode && curtab == prev_curtab) {
+ int diffcount = 0;
+
+ FOR_ALL_WINDOWS_IN_TAB(dwin, curtab) {
+ if (dwin->w_p_diff) {
+ diffcount++;
+ }
+ }
+ if (diffcount == 1) {
+ do_cmdline_cmd("diffoff!");
+ }
+ }
+
curwin->w_pos_changed = true;
redraw_all_later(NOT_VALID);
return OK;
@@ -4332,9 +4372,10 @@ static void win_goto_hor(bool left, long count)
}
}
-/*
- * Make window "wp" the current window.
- */
+/// Make window `wp` the current window.
+///
+/// @warning Autocmds may close the window immediately, so caller must check
+/// win_valid(wp).
void win_enter(win_T *wp, bool undo_sync)
{
win_enter_ext(wp, undo_sync, false, false, true, true);
@@ -4626,8 +4667,10 @@ win_free (
xfree(wp->w_lines);
- for (i = 0; i < wp->w_tagstacklen; ++i)
+ for (i = 0; i < wp->w_tagstacklen; i++) {
xfree(wp->w_tagstack[i].tagname);
+ xfree(wp->w_tagstack[i].user_data);
+ }
xfree(wp->w_localdir);
@@ -5598,10 +5641,14 @@ void scroll_to_fraction(win_T *wp, int prev_height)
int sline, line_size;
int height = wp->w_height_inner;
- // Don't change w_topline when height is zero. Don't set w_topline when
- // 'scrollbind' is set and this isn't the current window.
+ // Don't change w_topline in any of these cases:
+ // - window height is 0
+ // - 'scrollbind' is set and this isn't the current window
+ // - window height is sufficient to display the whole buffer and first line
+ // is visible.
if (height > 0
&& (!wp->w_p_scb || wp == curwin)
+ && (height < wp->w_buffer->b_ml.ml_line_count || wp->w_topline > 1)
) {
/*
* Find a value for w_topline that shows the cursor at the same