aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/CMakeLists.txt6
-rw-r--r--src/nvim/api/deprecated.c11
-rw-r--r--src/nvim/api/extmark.c162
-rw-r--r--src/nvim/api/keysets.lua33
-rw-r--r--src/nvim/api/private/converter.c21
-rw-r--r--src/nvim/api/private/helpers.c34
-rw-r--r--src/nvim/api/vim.c87
-rw-r--r--src/nvim/api/window.c11
-rw-r--r--src/nvim/autocmd.c22
-rw-r--r--src/nvim/autocmd.h1
-rw-r--r--src/nvim/buffer.c135
-rw-r--r--src/nvim/buffer_defs.h7
-rw-r--r--src/nvim/change.c42
-rw-r--r--src/nvim/channel.c1
-rw-r--r--src/nvim/channel.h2
-rw-r--r--src/nvim/charset.c6
-rw-r--r--src/nvim/cursor.c8
-rw-r--r--src/nvim/debugger.c4
-rw-r--r--src/nvim/decoration.c161
-rw-r--r--src/nvim/decoration.h7
-rw-r--r--src/nvim/diff.c216
-rw-r--r--src/nvim/edit.c65
-rw-r--r--src/nvim/eval.c252
-rw-r--r--src/nvim/eval.lua24
-rw-r--r--src/nvim/eval/funcs.c753
-rw-r--r--src/nvim/eval/typval.c5
-rw-r--r--src/nvim/eval/typval.h3
-rw-r--r--src/nvim/ex_cmds.c96
-rw-r--r--src/nvim/ex_cmds.lua20
-rw-r--r--src/nvim/ex_cmds2.c148
-rw-r--r--src/nvim/ex_cmds_defs.h1
-rw-r--r--src/nvim/ex_docmd.c202
-rw-r--r--src/nvim/ex_eval.c8
-rw-r--r--src/nvim/ex_getln.c13
-rw-r--r--src/nvim/ex_session.c70
-rw-r--r--src/nvim/extmark.c298
-rw-r--r--src/nvim/extmark.h5
-rw-r--r--src/nvim/extmark_defs.h12
-rw-r--r--src/nvim/file_search.c14
-rw-r--r--src/nvim/fileio.c54
-rw-r--r--src/nvim/generators/gen_keysets.lua3
-rw-r--r--src/nvim/getchar.c298
-rw-r--r--src/nvim/getchar.h2
-rw-r--r--src/nvim/globals.h9
-rw-r--r--src/nvim/highlight.c215
-rw-r--r--src/nvim/if_cscope.c6
-rw-r--r--src/nvim/indent_c.c228
-rw-r--r--src/nvim/input.c2
-rw-r--r--src/nvim/keymap.c2
-rw-r--r--src/nvim/keymap.h5
-rw-r--r--src/nvim/lua/converter.c8
-rw-r--r--src/nvim/lua/executor.c37
-rw-r--r--src/nvim/lua/stdlib.c7
-rw-r--r--src/nvim/lua/treesitter.c4
-rw-r--r--src/nvim/lua/vim.lua66
-rw-r--r--src/nvim/lua/xdiff.c6
-rw-r--r--src/nvim/macros.h3
-rw-r--r--src/nvim/main.c25
-rw-r--r--src/nvim/map.c5
-rw-r--r--src/nvim/map.h10
-rw-r--r--src/nvim/mark.c2
-rw-r--r--src/nvim/marktree.c201
-rw-r--r--src/nvim/marktree.h90
-rw-r--r--src/nvim/mbyte.c51
-rw-r--r--src/nvim/memfile.c6
-rw-r--r--src/nvim/memline.c6
-rw-r--r--src/nvim/message.c28
-rw-r--r--src/nvim/move.c6
-rw-r--r--src/nvim/normal.c118
-rw-r--r--src/nvim/ops.c350
-rw-r--r--src/nvim/option.c262
-rw-r--r--src/nvim/option.h20
-rw-r--r--src/nvim/option_defs.h15
-rw-r--r--src/nvim/options.lua16
-rw-r--r--src/nvim/os/env.c7
-rw-r--r--src/nvim/os/input.c14
-rw-r--r--src/nvim/path.c65
-rw-r--r--src/nvim/po/ja.euc-jp.po9
-rw-r--r--src/nvim/po/ja.po9
-rw-r--r--src/nvim/popupmnu.c8
-rw-r--r--src/nvim/pos.h4
-rw-r--r--src/nvim/quickfix.c439
-rw-r--r--src/nvim/quickfix.h1
-rw-r--r--src/nvim/regexp.c37
-rw-r--r--src/nvim/regexp_nfa.c7
-rw-r--r--src/nvim/screen.c62
-rw-r--r--src/nvim/search.c551
-rw-r--r--src/nvim/search.h3
-rw-r--r--src/nvim/sign.c8
-rw-r--r--src/nvim/spell.c2
-rw-r--r--src/nvim/state.c33
-rw-r--r--src/nvim/strings.c28
-rw-r--r--src/nvim/syntax.c104
-rw-r--r--src/nvim/tag.c4
-rw-r--r--src/nvim/terminal.c22
-rw-r--r--src/nvim/testdir/check.vim23
-rw-r--r--src/nvim/testdir/shared.vim9
-rw-r--r--src/nvim/testdir/test_alot.vim1
-rw-r--r--src/nvim/testdir/test_alot_utf8.vim1
-rw-r--r--src/nvim/testdir/test_assert.vim174
-rw-r--r--src/nvim/testdir/test_autocmd.vim46
-rw-r--r--src/nvim/testdir/test_breakindent.vim2
-rw-r--r--src/nvim/testdir/test_buffer.vim31
-rw-r--r--src/nvim/testdir/test_cd.vim9
-rw-r--r--src/nvim/testdir/test_cdo.vim205
-rw-r--r--src/nvim/testdir/test_charsearch_utf8.vim2
-rw-r--r--src/nvim/testdir/test_cindent.vim5192
-rw-r--r--src/nvim/testdir/test_cmdline.vim8
-rw-r--r--src/nvim/testdir/test_compiler.vim3
-rw-r--r--src/nvim/testdir/test_conceal.vim4
-rw-r--r--src/nvim/testdir/test_cscope.vim4
-rw-r--r--src/nvim/testdir/test_cursor_func.vim257
-rw-r--r--src/nvim/testdir/test_diffmode.vim29
-rw-r--r--src/nvim/testdir/test_display.vim2
-rw-r--r--src/nvim/testdir/test_edit.vim35
-rw-r--r--src/nvim/testdir/test_eval_stuff.vim8
-rw-r--r--src/nvim/testdir/test_ex_mode.vim15
-rw-r--r--src/nvim/testdir/test_excmd.vim10
-rw-r--r--src/nvim/testdir/test_exec_while_if.vim18
-rw-r--r--src/nvim/testdir/test_execute_func.vim27
-rw-r--r--src/nvim/testdir/test_expand.vim83
-rw-r--r--src/nvim/testdir/test_expr.vim65
-rw-r--r--src/nvim/testdir/test_feedkeys.vim12
-rw-r--r--src/nvim/testdir/test_filechanged.vim2
-rw-r--r--src/nvim/testdir/test_filetype.vim523
-rw-r--r--src/nvim/testdir/test_filetype_lua.vim2
-rw-r--r--src/nvim/testdir/test_filter_cmd.vim35
-rw-r--r--src/nvim/testdir/test_filter_map.vim10
-rw-r--r--src/nvim/testdir/test_findfile.vim22
-rw-r--r--src/nvim/testdir/test_fnamemodify.vim19
-rw-r--r--src/nvim/testdir/test_functions.vim115
-rw-r--r--src/nvim/testdir/test_gf.vim12
-rw-r--r--src/nvim/testdir/test_global.vim30
-rw-r--r--src/nvim/testdir/test_help.vim22
-rw-r--r--src/nvim/testdir/test_help_tagjump.vim43
-rw-r--r--src/nvim/testdir/test_highlight.vim25
-rw-r--r--src/nvim/testdir/test_ins_complete.vim75
-rw-r--r--src/nvim/testdir/test_lambda.vim163
-rw-r--r--src/nvim/testdir/test_listchars.vim32
-rw-r--r--src/nvim/testdir/test_listdict.vim53
-rw-r--r--src/nvim/testdir/test_listlbr_utf8.vim10
-rw-r--r--src/nvim/testdir/test_marks.vim35
-rw-r--r--src/nvim/testdir/test_matchadd_conceal.vim4
-rw-r--r--src/nvim/testdir/test_matchadd_conceal_utf8.vim10
-rw-r--r--src/nvim/testdir/test_matchfuzzy.vim248
-rw-r--r--src/nvim/testdir/test_messages.vim4
-rw-r--r--src/nvim/testdir/test_mksession.vim55
-rw-r--r--src/nvim/testdir/test_normal.vim20
-rw-r--r--src/nvim/testdir/test_number.vim4
-rw-r--r--src/nvim/testdir/test_options.vim77
-rw-r--r--src/nvim/testdir/test_packadd.vim361
-rw-r--r--src/nvim/testdir/test_put.vim35
-rw-r--r--src/nvim/testdir/test_python3.vim1
-rw-r--r--src/nvim/testdir/test_pyx2.vim2
-rw-r--r--src/nvim/testdir/test_quickfix.vim74
-rw-r--r--src/nvim/testdir/test_random.vim51
-rw-r--r--src/nvim/testdir/test_regexp_latin.vim8
-rw-r--r--src/nvim/testdir/test_regexp_utf8.vim13
-rw-r--r--src/nvim/testdir/test_registers.vim318
-rw-r--r--src/nvim/testdir/test_rename.vim4
-rw-r--r--src/nvim/testdir/test_retab.vim24
-rw-r--r--src/nvim/testdir/test_scriptnames.vim6
-rw-r--r--src/nvim/testdir/test_search.vim103
-rw-r--r--src/nvim/testdir/test_search_stat.vim68
-rw-r--r--src/nvim/testdir/test_selectmode.vim57
-rw-r--r--src/nvim/testdir/test_source_utf8.vim27
-rw-r--r--src/nvim/testdir/test_spell.vim8
-rw-r--r--src/nvim/testdir/test_statusline.vim24
-rw-r--r--src/nvim/testdir/test_substitute.vim57
-rw-r--r--src/nvim/testdir/test_swap.vim20
-rw-r--r--src/nvim/testdir/test_syntax.vim50
-rw-r--r--src/nvim/testdir/test_system.vim4
-rw-r--r--src/nvim/testdir/test_tagjump.vim178
-rw-r--r--src/nvim/testdir/test_textformat.vim139
-rw-r--r--src/nvim/testdir/test_timers.vim23
-rw-r--r--src/nvim/testdir/test_usercommands.vim28
-rw-r--r--src/nvim/testdir/test_utf8.vim52
-rw-r--r--src/nvim/testdir/test_utf8_comparisons.vim11
-rw-r--r--src/nvim/testdir/test_vartabs.vim12
-rw-r--r--src/nvim/testdir/test_vimscript.vim98
-rw-r--r--src/nvim/testdir/test_virtualedit.vim262
-rw-r--r--src/nvim/testdir/test_visual.vim137
-rw-r--r--src/nvim/testdir/test_winbuf_close.vim19
-rw-r--r--src/nvim/testdir/test_window_cmd.vim124
-rw-r--r--src/nvim/testdir/test_writefile.vim154
-rw-r--r--src/nvim/tui/input.c3
-rw-r--r--src/nvim/types.h2
-rw-r--r--src/nvim/undo.c4
-rw-r--r--src/nvim/version.c2
-rw-r--r--src/nvim/viml/parser/expressions.c2
-rw-r--r--src/nvim/viml/parser/expressions.h2
-rw-r--r--src/nvim/window.c117
-rw-r--r--src/nvim/window.h33
193 files changed, 14350 insertions, 2841 deletions
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index 9c4b778169..dcc20194f0 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -62,6 +62,7 @@ set(LUA_SHARED_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/shared.lua)
set(LUA_INSPECT_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/inspect.lua)
set(LUA_F_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/F.lua)
set(LUA_META_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/_meta.lua)
+set(LUA_FILETYPE_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/filetype.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")
@@ -334,6 +335,7 @@ add_custom_command(
${LUA_INSPECT_MODULE_SOURCE} inspect_module
${LUA_F_MODULE_SOURCE} lua_F_module
${LUA_META_MODULE_SOURCE} lua_meta_module
+ ${LUA_FILETYPE_MODULE_SOURCE} lua_filetype_module
DEPENDS
${CHAR_BLOB_GENERATOR}
${LUA_VIM_MODULE_SOURCE}
@@ -341,6 +343,7 @@ add_custom_command(
${LUA_INSPECT_MODULE_SOURCE}
${LUA_F_MODULE_SOURCE}
${LUA_META_MODULE_SOURCE}
+ ${LUA_FILETYPE_MODULE_SOURCE}
VERBATIM
)
@@ -490,6 +493,9 @@ add_executable(nvim ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS}
${EXTERNAL_SOURCES} ${EXTERNAL_HEADERS})
target_link_libraries(nvim ${NVIM_EXEC_LINK_LIBRARIES})
install_helper(TARGETS nvim)
+if(MSVC)
+ install(FILES $<TARGET_PDB_FILE:nvim> DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL)
+endif()
set_property(TARGET nvim APPEND PROPERTY
INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS})
diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c
index 76b699800e..18243fec2b 100644
--- a/src/nvim/api/deprecated.c
+++ b/src/nvim/api/deprecated.c
@@ -130,7 +130,7 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Integer src_id, Integer line, A
return 0;
}
- uint64_t ns_id = src2ns(&src_id);
+ uint32_t ns_id = src2ns(&src_id);
int width;
VirtText virt_text = parse_virt_text(chunks, err, &width);
@@ -148,11 +148,12 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Integer src_id, Integer line, A
return src_id;
}
- Decoration *decor = xcalloc(1, sizeof(*decor));
- decor->virt_text = virt_text;
- decor->virt_text_width = width;
+ Decoration decor = DECORATION_INIT;
+ decor.virt_text = virt_text;
+ decor.virt_text_width = width;
+ decor.priority = 0;
- extmark_set(buf, ns_id, NULL, (int)line, 0, -1, -1, decor, true,
+ extmark_set(buf, ns_id, NULL, (int)line, 0, -1, -1, &decor, true,
false, kExtmarkNoUndo);
return src_id;
}
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index 742b953c2a..3a968f07ab 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -85,12 +85,12 @@ const char *describe_ns(NS ns_id)
}
// Is the Namespace in use?
-static bool ns_initialized(uint64_t ns)
+static bool ns_initialized(uint32_t ns)
{
if (ns < 1) {
return false;
}
- return ns < (uint64_t)next_namespace_id;
+ return ns < (uint32_t)next_namespace_id;
}
@@ -106,22 +106,55 @@ static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict)
if (add_dict) {
Dictionary dict = ARRAY_DICT_INIT;
+ PUT(dict, "right_gravity", BOOLEAN_OBJ(extmark.right_gravity));
+
if (extmark.end_row >= 0) {
PUT(dict, "end_row", INTEGER_OBJ(extmark.end_row));
PUT(dict, "end_col", INTEGER_OBJ(extmark.end_col));
+ PUT(dict, "end_right_gravity", BOOLEAN_OBJ(extmark.end_right_gravity));
}
- if (extmark.decor) {
- Decoration *decor = extmark.decor;
- if (decor->hl_id) {
- String name = cstr_to_string((const char *)syn_id2name(decor->hl_id));
- PUT(dict, "hl_group", STRING_OBJ(name));
+ Decoration *decor = &extmark.decor;
+ if (decor->hl_id) {
+ String name = cstr_to_string((const char *)syn_id2name(decor->hl_id));
+ PUT(dict, "hl_group", STRING_OBJ(name));
+ PUT(dict, "hl_eol", BOOLEAN_OBJ(decor->hl_eol));
+ }
+ if (decor->hl_mode) {
+ PUT(dict, "hl_mode", STRING_OBJ(cstr_to_string(hl_mode_str[decor->hl_mode])));
+ }
+
+ if (kv_size(decor->virt_text)) {
+ Array chunks = ARRAY_DICT_INIT;
+ for (size_t i = 0; i < decor->virt_text.size; i++) {
+ Array chunk = ARRAY_DICT_INIT;
+ VirtTextChunk *vtc = &decor->virt_text.items[i];
+ ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text)));
+ if (vtc->hl_id > 0) {
+ ADD(chunk,
+ STRING_OBJ(cstr_to_string((const char *)syn_id2name(vtc->hl_id))));
+ }
+ ADD(chunks, ARRAY_OBJ(chunk));
}
- if (kv_size(decor->virt_text)) {
+ PUT(dict, "virt_text", ARRAY_OBJ(chunks));
+ PUT(dict, "virt_text_hide", BOOLEAN_OBJ(decor->virt_text_hide));
+ if (decor->virt_text_pos == kVTWinCol) {
+ PUT(dict, "virt_text_win_col", INTEGER_OBJ(decor->col));
+ }
+ PUT(dict, "virt_text_pos",
+ STRING_OBJ(cstr_to_string(virt_text_pos_str[decor->virt_text_pos])));
+ }
+
+ if (kv_size(decor->virt_lines)) {
+ Array all_chunks = ARRAY_DICT_INIT;
+ bool virt_lines_leftcol = false;
+ for (size_t i = 0; i < decor->virt_lines.size; i++) {
Array chunks = ARRAY_DICT_INIT;
- for (size_t i = 0; i < decor->virt_text.size; i++) {
+ VirtText *vt = &decor->virt_lines.items[i].line;
+ virt_lines_leftcol = decor->virt_lines.items[i].left_col;
+ for (size_t j = 0; j < vt->size; j++) {
Array chunk = ARRAY_DICT_INIT;
- VirtTextChunk *vtc = &decor->virt_text.items[i];
+ VirtTextChunk *vtc = &vt->items[j];
ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text)));
if (vtc->hl_id > 0) {
ADD(chunk,
@@ -129,9 +162,14 @@ static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict)
}
ADD(chunks, ARRAY_OBJ(chunk));
}
- PUT(dict, "virt_text", ARRAY_OBJ(chunks));
+ ADD(all_chunks, ARRAY_OBJ(chunks));
}
+ PUT(dict, "virt_lines", ARRAY_OBJ(all_chunks));
+ PUT(dict, "virt_lines_above", BOOLEAN_OBJ(decor->virt_lines_above));
+ PUT(dict, "virt_lines_leftcol", BOOLEAN_OBJ(virt_lines_leftcol));
+ }
+ if (decor->hl_id || kv_size(decor->virt_text)) {
PUT(dict, "priority", INTEGER_OBJ(decor->priority));
}
@@ -166,7 +204,7 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
return rv;
}
- if (!ns_initialized((uint64_t)ns_id)) {
+ if (!ns_initialized((uint32_t)ns_id)) {
api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
return rv;
}
@@ -191,7 +229,7 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
}
- ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id);
+ ExtmarkInfo extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id);
if (extmark.row < 0) {
return rv;
}
@@ -252,7 +290,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
return rv;
}
- if (!ns_initialized((uint64_t)ns_id)) {
+ if (!ns_initialized((uint32_t)ns_id)) {
api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
return rv;
}
@@ -310,7 +348,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
}
- ExtmarkInfoArray marks = extmark_get(buf, (uint64_t)ns_id, l_row, l_col,
+ ExtmarkInfoArray marks = extmark_get(buf, (uint32_t)ns_id, l_row, l_col,
u_row, u_col, (int64_t)limit, reverse);
for (size_t i = 0; i < kv_size(marks); i++) {
@@ -404,6 +442,10 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// for left). Defaults to false.
/// - priority: a priority value for the highlight group. For
/// example treesitter highlighting uses a value of 100.
+/// - strict: boolean that indicates extmark should not be placed
+/// if the line or column value is past the end of the
+/// buffer or end of the line respectively. Defaults to true.
+///
/// @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 line, Integer col,
@@ -417,14 +459,14 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
goto error;
}
- if (!ns_initialized((uint64_t)ns_id)) {
+ if (!ns_initialized((uint32_t)ns_id)) {
api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
goto error;
}
- uint64_t id = 0;
+ uint32_t id = 0;
if (opts->id.type == kObjectTypeInteger && opts->id.data.integer > 0) {
- id = (uint64_t)opts->id.data.integer;
+ id = (uint32_t)opts->id.data.integer;
} else if (HAS_KEY(opts->id)) {
api_set_error(err, kErrorTypeValidation, "id is not a positive integer");
goto error;
@@ -441,9 +483,18 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
opts->end_row = opts->end_line;
}
+#define OPTION_TO_BOOL(target, name, val) \
+ target = api_object_to_bool(opts->name, #name, val, err); \
+ if (ERROR_SET(err)) { \
+ goto error; \
+ }
+
+ bool strict = true;
+ OPTION_TO_BOOL(strict, strict, true);
+
if (opts->end_row.type == kObjectTypeInteger) {
Integer val = opts->end_row.data.integer;
- if (val < 0 || val > buf->b_ml.ml_line_count) {
+ if (val < 0 || (val > buf->b_ml.ml_line_count && strict)) {
api_set_error(err, kErrorTypeValidation, "end_row value outside range");
goto error;
} else {
@@ -512,12 +563,6 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
goto error;
}
-#define OPTION_TO_BOOL(target, name, val) \
- target = api_object_to_bool(opts->name, #name, val, err); \
- if (ERROR_SET(err)) { \
- goto error; \
- }
-
OPTION_TO_BOOL(decor.virt_text_hide, virt_text_hide, false);
OPTION_TO_BOOL(decor.hl_eol, hl_eol, false);
@@ -596,16 +641,30 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
bool ephemeral = false;
OPTION_TO_BOOL(ephemeral, ephemeral, false);
- if (line < 0 || line > buf->b_ml.ml_line_count) {
+ if (line < 0) {
api_set_error(err, kErrorTypeValidation, "line value outside range");
goto error;
+ } else if (line > buf->b_ml.ml_line_count) {
+ if (strict) {
+ api_set_error(err, kErrorTypeValidation, "line value outside range");
+ goto error;
+ } else {
+ line = buf->b_ml.ml_line_count;
+ }
} else if (line < buf->b_ml.ml_line_count) {
len = ephemeral ? MAXCOL : STRLEN(ml_get_buf(buf, (linenr_T)line+1, false));
}
if (col == -1) {
col = (Integer)len;
- } else if (col < -1 || col > (Integer)len) {
+ } else if (col > (Integer)len) {
+ if (strict) {
+ api_set_error(err, kErrorTypeValidation, "col value outside range");
+ goto error;
+ } else {
+ col = (Integer)len;
+ }
+ } else if (col < -1) {
api_set_error(err, kErrorTypeValidation, "col value outside range");
goto error;
}
@@ -621,27 +680,17 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
line2 = (int)line;
}
if (col2 > (Integer)len) {
- api_set_error(err, kErrorTypeValidation, "end_col value outside range");
- goto error;
+ if (strict) {
+ api_set_error(err, kErrorTypeValidation, "end_col value outside range");
+ goto error;
+ } else {
+ col2 = (int)len;
+ }
}
} else if (line2 >= 0) {
col2 = 0;
}
- Decoration *d = NULL;
-
- if (ephemeral) {
- d = &decor;
- } else if (kv_size(decor.virt_text) || kv_size(decor.virt_lines)
- || decor.priority != DECOR_PRIORITY_BASE
- || decor.hl_eol) {
- // TODO(bfredl): this is a bit sketchy. eventually we should
- // have predefined decorations for both marks/ephemerals
- d = xcalloc(1, sizeof(*d));
- *d = decor;
- } else if (decor.hl_id) {
- d = decor_hl(decor.hl_id);
- }
// TODO(bfredl): synergize these two branches even more
if (ephemeral && decor_state.buf == buf) {
@@ -652,12 +701,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
goto error;
}
- extmark_set(buf, (uint64_t)ns_id, &id, (int)line, (colnr_T)col, line2, col2,
- d, right_gravity, end_right_gravity, kExtmarkNoUndo);
-
- if (kv_size(decor.virt_lines)) {
- redraw_buf_line_later(buf, MIN(buf->b_ml.ml_line_count, line+1+(decor.virt_lines_above?0:1)));
- }
+ extmark_set(buf, (uint32_t)ns_id, &id, (int)line, (colnr_T)col, line2, col2,
+ &decor, right_gravity, end_right_gravity, kExtmarkNoUndo);
}
return (Integer)id;
@@ -682,23 +727,23 @@ Boolean nvim_buf_del_extmark(Buffer buffer, Integer ns_id, Integer id, Error *er
if (!buf) {
return false;
}
- if (!ns_initialized((uint64_t)ns_id)) {
+ if (!ns_initialized((uint32_t)ns_id)) {
api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
return false;
}
- return extmark_del(buf, (uint64_t)ns_id, (uint64_t)id);
+ return extmark_del(buf, (uint32_t)ns_id, (uint32_t)id);
}
-uint64_t src2ns(Integer *src_id)
+uint32_t src2ns(Integer *src_id)
{
if (*src_id == 0) {
*src_id = nvim_create_namespace((String)STRING_INIT);
}
if (*src_id < 0) {
- return UINT64_MAX;
+ return (((uint32_t)1) << 31) - 1;
} else {
- return (uint64_t)(*src_id);
+ return (uint32_t)(*src_id);
}
}
@@ -753,7 +798,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In
col_end = MAXCOL;
}
- uint64_t ns = src2ns(&ns_id);
+ uint32_t ns = src2ns(&ns_id);
if (!(line < buf->b_ml.ml_line_count)) {
// safety check, we can't add marks outside the range
@@ -773,10 +818,13 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In
end_line++;
}
+ Decoration decor = DECORATION_INIT;
+ decor.hl_id = hl_id;
+
extmark_set(buf, ns, NULL,
(int)line, (colnr_T)col_start,
end_line, (colnr_T)col_end,
- decor_hl(hl_id), true, false, kExtmarkNoUndo);
+ &decor, true, false, kExtmarkNoUndo);
return ns_id;
}
@@ -808,7 +856,7 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
if (line_end < 0 || line_end > MAXLNUM) {
line_end = MAXLNUM;
}
- extmark_clear(buf, (ns_id < 0 ? 0 : (uint64_t)ns_id),
+ extmark_clear(buf, (ns_id < 0 ? 0 : (uint32_t)ns_id),
(int)line_start, 0,
(int)line_end-1, MAXCOL);
}
diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua
index 97ee885ff6..f6dce1905e 100644
--- a/src/nvim/api/keysets.lua
+++ b/src/nvim/api/keysets.lua
@@ -21,6 +21,7 @@ return {
"virt_lines";
"virt_lines_above";
"virt_lines_leftcol";
+ "strict";
};
keymap = {
"noremap";
@@ -43,6 +44,7 @@ return {
"count";
"desc";
"force";
+ "keepscript";
"nargs";
"range";
"register";
@@ -76,5 +78,36 @@ return {
option = {
"scope";
};
+ highlight = {
+ "bold";
+ "standout";
+ "strikethrough";
+ "underline";
+ "undercurl";
+ "italic";
+ "reverse";
+ "nocombine";
+ "default";
+ "global";
+ "cterm";
+ "foreground"; "fg";
+ "background"; "bg";
+ "ctermfg";
+ "ctermbg";
+ "special"; "sp";
+ "link";
+ "fallback";
+ "temp";
+ };
+ highlight_cterm = {
+ "bold";
+ "standout";
+ "strikethrough";
+ "underline";
+ "undercurl";
+ "italic";
+ "reverse";
+ "nocombine";
+ };
}
diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c
index 36da6c13a9..3d4ff202fe 100644
--- a/src/nvim/api/private/converter.c
+++ b/src/nvim/api/private/converter.c
@@ -10,6 +10,9 @@
#include "nvim/api/private/helpers.h"
#include "nvim/assert.h"
#include "nvim/eval/typval.h"
+#include "nvim/eval/userfunc.h"
+#include "nvim/lua/converter.h"
+#include "nvim/lua/executor.h"
/// Helper structure for vim_to_object
typedef struct {
@@ -228,6 +231,14 @@ static inline void typval_encode_dict_end(EncodedData *const edata)
/// @return The converted value
Object vim_to_object(typval_T *obj)
{
+ if (obj->v_type == VAR_FUNC) {
+ ufunc_T *fp = find_func(obj->vval.v_string);
+ assert(fp != NULL);
+ if (fp->uf_cb == nlua_CFunction_func_call) {
+ LuaRef ref = api_new_luaref(((LuaCFunctionState *)fp->uf_cb_state)->lua_callable.func_ref);
+ return LUAREF_OBJ(ref);
+ }
+ }
EncodedData edata;
kvi_init(edata.stack);
const int evo_ret = encode_vim_to_object(&edata, obj,
@@ -340,6 +351,16 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err)
tv->vval.v_dict = dict;
break;
}
+
+ case kObjectTypeLuaRef: {
+ LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState));
+ state->lua_callable.func_ref = api_new_luaref(obj.data.luaref);
+ char_u *name = register_cfunc(&nlua_CFunction_func_call, &nlua_CFunction_func_free, state);
+ tv->v_type = VAR_FUNC;
+ tv->vval.v_string = vim_strsave(name);
+ break;
+ }
+
default:
abort();
}
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 4884a44cea..ddcfff0097 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -139,10 +139,10 @@ bool try_end(Error *err)
got_int = false;
} else if (msg_list != NULL && *msg_list != NULL) {
int should_free;
- char *msg = (char *)get_exception_string(*msg_list,
- ET_ERROR,
- NULL,
- &should_free);
+ char *msg = get_exception_string(*msg_list,
+ ET_ERROR,
+ NULL,
+ &should_free);
api_set_error(err, kErrorTypeException, "%s", msg);
free_global_msglist();
@@ -720,7 +720,6 @@ fail_and_free:
xfree(parsed_args.rhs);
xfree(parsed_args.orig_rhs);
XFREE_CLEAR(parsed_args.desc);
- return;
}
/// Collects `n` buffer lines into array `l`, optionally replacing newlines
@@ -990,18 +989,16 @@ Object copy_object(Object obj)
static void set_option_value_for(char *key, int numval, char *stringval, int opt_flags,
int opt_type, void *from, Error *err)
{
- win_T *save_curwin = NULL;
- tabpage_T *save_curtab = NULL;
+ switchwin_T switchwin;
aco_save_T aco;
try_start();
switch (opt_type)
{
case SREQ_WIN:
- if (switch_win_noblock(&save_curwin, &save_curtab, (win_T *)from,
- win_find_tabpage((win_T *)from), true)
+ if (switch_win_noblock(&switchwin, (win_T *)from, win_find_tabpage((win_T *)from), true)
== FAIL) {
- restore_win_noblock(save_curwin, save_curtab, true);
+ restore_win_noblock(&switchwin, true);
if (try_end(err)) {
return;
}
@@ -1011,7 +1008,7 @@ static void set_option_value_for(char *key, int numval, char *stringval, int opt
return;
}
set_option_value_err(key, numval, stringval, opt_flags, err);
- restore_win_noblock(save_curwin, save_curtab, true);
+ restore_win_noblock(&switchwin, true);
break;
case SREQ_BUF:
aucmd_prepbuf(&aco, (buf_T *)from);
@@ -1141,7 +1138,7 @@ bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int
return false;
}
- ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id);
+ ExtmarkInfo extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id);
if (extmark.row >= 0) {
*row = extmark.row;
*col = extmark.col;
@@ -1387,6 +1384,11 @@ void add_user_command(String name, Object command, Dict(user_command) *opts, int
LuaRef luaref = LUA_NOREF;
LuaRef compl_luaref = LUA_NOREF;
+ if (mb_islower(name.data[0])) {
+ api_set_error(err, kErrorTypeValidation, "'name' must begin with an uppercase letter");
+ goto err;
+ }
+
if (HAS_KEY(opts->range) && HAS_KEY(opts->count)) {
api_set_error(err, kErrorTypeValidation, "'range' and 'count' are mutually exclusive");
goto err;
@@ -1505,7 +1507,13 @@ void add_user_command(String name, Object command, Dict(user_command) *opts, int
goto err;
}
- bool force = api_object_to_bool(opts->force, "force", false, err);
+ if (api_object_to_bool(opts->keepscript, "keepscript", false, err)) {
+ argt |= EX_KEEPSCRIPT;
+ } else if (ERROR_SET(err)) {
+ goto err;
+ }
+
+ bool force = api_object_to_bool(opts->force, "force", true, err);
if (ERROR_SET(err)) {
goto err;
}
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 693d2083e6..f4909b0801 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -19,6 +19,7 @@
#include "nvim/ascii.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
+#include "nvim/charset.h"
#include "nvim/context.h"
#include "nvim/decoration.h"
#include "nvim/edit.h"
@@ -30,7 +31,9 @@
#include "nvim/file_search.h"
#include "nvim/fileio.h"
#include "nvim/getchar.h"
+#include "nvim/globals.h"
#include "nvim/highlight.h"
+#include "nvim/highlight_defs.h"
#include "nvim/lua/executor.h"
#include "nvim/mark.h"
#include "nvim/memline.h"
@@ -121,7 +124,9 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err)
/// Set a highlight group.
///
-/// @param ns_id number of namespace for this highlight
+/// @param ns_id number of namespace for this highlight. Use value 0
+/// to set a highlight group in the global (`:highlight`)
+/// namespace.
/// @param name highlight group name, like ErrorMsg
/// @param val highlight definition map, like |nvim_get_hl_by_name|.
/// in addition the following keys are also recognized:
@@ -135,9 +140,8 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err)
/// same as attributes of gui color
/// @param[out] err Error details, if any
///
-/// TODO: ns_id = 0, should modify :highlight namespace
-/// TODO val should take update vs reset flag
-void nvim_set_hl(Integer ns_id, String name, Dictionary val, Error *err)
+// TODO(bfredl): val should take update vs reset flag
+void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err)
FUNC_API_SINCE(7)
{
int hl_id = syn_check_group(name.data, (int)name.size);
@@ -145,7 +149,7 @@ void nvim_set_hl(Integer ns_id, String name, Dictionary val, Error *err)
HlAttrs attrs = dict2hlattrs(val, true, &link_id, err);
if (!ERROR_SET(err)) {
- ns_hl_def((NS)ns_id, hl_id, attrs, link_id);
+ ns_hl_def((NS)ns_id, hl_id, attrs, link_id, val);
}
}
@@ -187,21 +191,23 @@ static void on_redraw_event(void **argv)
/// On execution error: does not fail, but updates v:errmsg.
///
/// To input sequences like <C-o> use |nvim_replace_termcodes()| (typically
-/// with escape_csi=true) to replace |keycodes|, then pass the result to
+/// with escape_ks=false) to replace |keycodes|, then pass the result to
/// nvim_feedkeys().
///
/// Example:
/// <pre>
/// :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true)
-/// :call nvim_feedkeys(key, 'n', v:true)
+/// :call nvim_feedkeys(key, 'n', v:false)
/// </pre>
///
/// @param keys to be typed
/// @param mode behavior flags, see |feedkeys()|
-/// @param escape_csi If true, escape K_SPECIAL/CSI bytes in `keys`
+/// @param escape_ks If true, escape K_SPECIAL bytes in `keys`
+/// This should be false if you already used
+/// |nvim_replace_termcodes()|, and true otherwise.
/// @see feedkeys()
-/// @see vim_strsave_escape_csi
-void nvim_feedkeys(String keys, String mode, Boolean escape_csi)
+/// @see vim_strsave_escape_ks
+void nvim_feedkeys(String keys, String mode, Boolean escape_ks)
FUNC_API_SINCE(1)
{
bool remap = true;
@@ -232,10 +238,10 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi)
}
char *keys_esc;
- if (escape_csi) {
- // Need to escape K_SPECIAL and CSI before putting the string in the
+ if (escape_ks) {
+ // Need to escape K_SPECIAL before putting the string in the
// typeahead buffer.
- keys_esc = (char *)vim_strsave_escape_csi((char_u *)keys.data);
+ keys_esc = (char *)vim_strsave_escape_ks((char_u *)keys.data);
} else {
keys_esc = keys.data;
}
@@ -245,7 +251,7 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi)
typebuf_was_filled = true;
}
- if (escape_csi) {
+ if (escape_ks) {
xfree(keys_esc);
}
@@ -383,7 +389,7 @@ error:
/// @param str String to be converted.
/// @param from_part Legacy Vim parameter. Usually true.
/// @param do_lt Also translate <lt>. Ignored if `special` is false.
-/// @param special Replace |keycodes|, e.g. <CR> becomes a "\n" char.
+/// @param special Replace |keycodes|, e.g. <CR> becomes a "\r" char.
/// @see replace_termcodes
/// @see cpoptions
String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, Boolean special)
@@ -540,20 +546,19 @@ void nvim_set_current_dir(String dir, Error *err)
return;
}
- char string[MAXPATHL];
+ char_u string[MAXPATHL];
memcpy(string, dir.data, dir.size);
string[dir.size] = NUL;
try_start();
- if (vim_chdir((char_u *)string)) {
+ if (!changedir_func(string, kCdScopeGlobal)) {
if (!try_end(err)) {
api_set_error(err, kErrorTypeException, "Failed to change directory");
}
return;
}
- post_chdir(kCdScopeGlobal, true);
try_end(err);
}
@@ -596,7 +601,19 @@ void nvim_del_current_line(Error *err)
Object nvim_get_var(String name, Error *err)
FUNC_API_SINCE(1)
{
- return dict_get_value(&globvardict, name, err);
+ dictitem_T *di = tv_dict_find(&globvardict, name.data, (ptrdiff_t)name.size);
+ if (di == NULL) { // try to autoload script
+ if (!script_autoload(name.data, name.size, false) || aborting()) {
+ api_set_error(err, kErrorTypeValidation, "Key not found: %s", name.data);
+ return (Object)OBJECT_INIT;
+ }
+ di = tv_dict_find(&globvardict, name.data, (ptrdiff_t)name.size);
+ }
+ if (di == NULL) {
+ api_set_error(err, kErrorTypeValidation, "Key not found: %s", name.data);
+ return (Object)OBJECT_INIT;
+ }
+ return vim_to_object(&di->di_tv);
}
/// Sets a global (g:) variable.
@@ -1163,7 +1180,7 @@ static void term_close(void *data)
/// Send data to channel `id`. For a job, it writes it to the
/// stdin of the process. For the stdio channel |channel-stdio|,
/// it writes to Nvim's stdout. For an internal terminal instance
-/// (|nvim_open_term()|) it writes directly to terimal output.
+/// (|nvim_open_term()|) it writes directly to terminal output.
/// See |channel-bytes| for more information.
///
/// This function writes raw data, not RPC messages. If the channel
@@ -2234,7 +2251,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
Dictionary result = ARRAY_DICT_INIT;
int maxwidth;
- char fillchar = 0;
+ int fillchar = 0;
Window window = 0;
bool use_tabline = false;
bool highlights = false;
@@ -2249,12 +2266,12 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
}
if (HAS_KEY(opts->fillchar)) {
- if (opts->fillchar.type != kObjectTypeString || opts->fillchar.data.string.size > 1) {
- api_set_error(err, kErrorTypeValidation, "fillchar must be an ASCII character");
+ if (opts->fillchar.type != kObjectTypeString || opts->fillchar.data.string.size == 0
+ || char2cells(fillchar = utf_ptr2char((char_u *)opts->fillchar.data.string.data)) != 1
+ || (size_t)utf_char2len(fillchar) != opts->fillchar.data.string.size) {
+ api_set_error(err, kErrorTypeValidation, "fillchar must be a single-width character");
return result;
}
-
- fillchar = opts->fillchar.data.string.data[0];
}
if (HAS_KEY(opts->highlights)) {
@@ -2281,11 +2298,16 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
fillchar = ' ';
} else {
wp = find_window_by_handle(window, err);
+
+ if (wp == NULL) {
+ api_set_error(err, kErrorTypeException, "unknown winid %d", window);
+ return result;
+ }
ewp = wp;
if (fillchar == 0) {
int attr;
- fillchar = (char)fillchar_status(&attr, wp);
+ fillchar = fillchar_status(&attr, wp);
}
}
@@ -2313,7 +2335,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
sizeof(buf),
(char_u *)str.data,
false,
- (char_u)fillchar,
+ fillchar,
maxwidth,
hltab_ptr,
NULL);
@@ -2394,11 +2416,12 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
/// - mods: (string) Command modifiers, if any |<mods>|
/// @param opts Optional command attributes. See |command-attributes| for more details. To use
/// boolean attributes (such as |:command-bang| or |:command-bar|) set the value to
-/// "true". When using a Lua function for {command} you can also provide a "desc"
-/// key that will be displayed when listing commands. In addition to the string
-/// options listed in |:command-complete|, the "complete" key also accepts a Lua
-/// function which works like the "customlist" completion mode
-/// |:command-complete-customlist|.
+/// "true". In addition to the string options listed in |:command-complete|, the
+/// "complete" key also accepts a Lua function which works like the "customlist"
+/// completion mode |:command-completion-customlist|. Additional parameters:
+/// - desc: (string) Used for listing the command when a Lua function is used for
+/// {command}.
+/// - force: (boolean, default true) Override any previous definition.
/// @param[out] err Error details, if any.
void nvim_add_user_command(String name, Object command, Dict(user_command) *opts, Error *err)
FUNC_API_SINCE(9)
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index 907306da7b..fc7823a070 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -395,7 +395,7 @@ void nvim_win_hide(Window window, Error *err)
TryState tstate;
try_enter(&tstate);
if (tabpage == curtab) {
- win_close(win, false);
+ win_close(win, false, false);
} else {
win_close_othertab(win, false, tabpage);
}
@@ -455,17 +455,12 @@ Object nvim_win_call(Window window, LuaRef fun, Error *err)
}
tabpage_T *tabpage = win_find_tabpage(win);
- win_T *save_curwin;
- tabpage_T *save_curtab;
-
try_start();
Object res = OBJECT_INIT;
- if (switch_win_noblock(&save_curwin, &save_curtab, win, tabpage, true) ==
- OK) {
+ WIN_EXECUTE(win, tabpage, {
Array args = ARRAY_DICT_INIT;
res = nlua_call_ref(fun, NULL, args, true, err);
- }
- restore_win_noblock(save_curwin, save_curtab, true);
+ });
try_end(err);
return res;
}
diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c
index 463bd5e0e6..dfba18b11d 100644
--- a/src/nvim/autocmd.c
+++ b/src/nvim/autocmd.c
@@ -285,7 +285,7 @@ void aubuflocal_remove(buf_T *buf)
}
// Add an autocmd group name.
-// Return its ID. Returns AUGROUP_ERROR (< 0) for error.
+// Return its ID.
static int au_new_group(char_u *name)
{
int i = au_find_group(name);
@@ -379,10 +379,7 @@ void do_augroup(char_u *arg, int del_group)
} else if (STRICMP(arg, "end") == 0) { // ":aug end": back to group 0
current_augroup = AUGROUP_DEFAULT;
} else if (*arg) { // ":aug xxx": switch to group xxx
- int i = au_new_group(arg);
- if (i != AUGROUP_ERROR) {
- current_augroup = i;
- }
+ current_augroup = au_new_group(arg);
} else { // ":aug": list the group names
msg_start();
for (int i = 0; i < augroups.ga_len; i++) {
@@ -1069,8 +1066,6 @@ void ex_doautoall(exarg_T *eap)
do_modelines(0);
}
}
-
- check_cursor(); // just in case lines got deleted
}
/// Check *argp for <nomodeline>. When it is present return false, otherwise
@@ -1160,7 +1155,10 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf)
// Prevent chdir() call in win_enter_ext(), through do_autochdir()
int save_acd = p_acd;
p_acd = false;
+ // no redrawing and don't set the window title
+ RedrawingDisabled++;
win_enter(aucmd_win, false);
+ RedrawingDisabled--;
p_acd = save_acd;
unblock_autocmds();
curwin = aucmd_win;
@@ -1168,6 +1166,10 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf)
curbuf = buf;
aco->new_curwin_handle = curwin->handle;
set_bufref(&aco->new_curbuf, curbuf);
+
+ // disable the Visual area, the position may be invalid in another buffer
+ aco->save_VIsual_active = VIsual_active;
+ VIsual_active = false;
}
/// Cleanup after executing autocommands for a (hidden) buffer.
@@ -1264,6 +1266,12 @@ win_found:
check_cursor();
}
}
+
+ check_cursor(); // just in case lines got deleted
+ VIsual_active = aco->save_VIsual_active;
+ if (VIsual_active) {
+ check_pos(curbuf, &VIsual);
+ }
}
/// Execute autocommands for "event" and file name "fname".
diff --git a/src/nvim/autocmd.h b/src/nvim/autocmd.h
index ac12e2acf3..63c5abd4f8 100644
--- a/src/nvim/autocmd.h
+++ b/src/nvim/autocmd.h
@@ -14,6 +14,7 @@ typedef struct {
handle_T save_prevwin_handle; ///< ID of saved prevwin
bufref_T new_curbuf; ///< new curbuf
char_u *globaldir; ///< saved value of globaldir
+ bool save_VIsual_active; ///< saved VIsual_active
} aco_save_T;
typedef struct AutoCmd {
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index abd22fba26..38b045b31c 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -846,7 +846,7 @@ void goto_buffer(exarg_T *eap, int start, int dir, int count)
enter_cleanup(&cs);
// Quitting means closing the split window, nothing else.
- win_close(curwin, true);
+ win_close(curwin, true, false);
swap_exists_action = SEA_NONE;
swap_exists_did_quit = true;
@@ -1237,7 +1237,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
while (buf == curbuf
&& !(curwin->w_closing || curwin->w_buffer->b_locked > 0)
&& (!ONE_WINDOW || first_tabpage->tp_next != NULL)) {
- if (win_close(curwin, false) == FAIL) {
+ if (win_close(curwin, false, false) == FAIL) {
break;
}
}
@@ -1454,7 +1454,10 @@ void set_curbuf(buf_T *buf, int action)
}
if (bufref_valid(&prevbufref) && !aborting()) {
win_T *previouswin = curwin;
- if (prevbuf == curbuf) {
+ // Do not sync when in Insert mode and the buffer is open in
+ // another window, might be a timer doing something in another
+ // window.
+ if (prevbuf == curbuf && ((State & INSERT) == 0 || curbuf->b_nwindows <= 1)) {
u_sync(false);
}
close_buffer(prevbuf == curwin->w_buffer ? curwin : NULL,
@@ -1494,6 +1497,11 @@ void set_curbuf(buf_T *buf, int action)
*/
void enter_buffer(buf_T *buf)
{
+ // Get the buffer in the current window.
+ curwin->w_buffer = buf;
+ curbuf = buf;
+ curbuf->b_nwindows++;
+
// Copy buffer and window local option values. Not for a help buffer.
buf_copy_options(buf, BCO_ENTER | BCO_NOHELP);
if (!buf->b_help) {
@@ -1504,11 +1512,6 @@ void enter_buffer(buf_T *buf)
}
foldUpdateAll(curwin); // update folds (later).
- // Get the buffer in the current window.
- curwin->w_buffer = buf;
- curbuf = buf;
- curbuf->b_nwindows++;
-
if (curwin->w_p_diff) {
diff_buf_add(curbuf);
}
@@ -1896,10 +1899,8 @@ void free_buf_options(buf_T *buf, int free_p_ff)
clear_string_option(&buf->b_p_flp);
clear_string_option(&buf->b_p_isk);
clear_string_option(&buf->b_p_vsts);
- xfree(buf->b_p_vsts_nopaste);
- buf->b_p_vsts_nopaste = NULL;
- xfree(buf->b_p_vsts_array);
- buf->b_p_vsts_array = NULL;
+ XFREE_CLEAR(buf->b_p_vsts_nopaste);
+ XFREE_CLEAR(buf->b_p_vsts_array);
clear_string_option(&buf->b_p_vts);
XFREE_CLEAR(buf->b_p_vts_array);
clear_string_option(&buf->b_p_keymap);
@@ -3326,7 +3327,7 @@ void maketitle(void)
len = (int)STRLEN(buf_p);
if (len > 100) {
len -= 100;
- len += (*mb_tail_off)(buf_p, buf_p + len) + 1;
+ len += mb_tail_off(buf_p, buf_p + len) + 1;
buf_p += len;
}
STRCPY(icon_str, buf_p);
@@ -3418,7 +3419,7 @@ typedef enum {
///
/// @return The final width of the statusline
int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use_sandbox,
- char_u fillchar, int maxwidth, stl_hlrec_t **hltab, StlClickRecord **tabtab)
+ int fillchar, int maxwidth, stl_hlrec_t **hltab, StlClickRecord **tabtab)
{
static size_t stl_items_len = 20; // Initial value, grows as needed.
static stl_item_t *stl_items = NULL;
@@ -3461,9 +3462,6 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use
if (fillchar == 0) {
fillchar = ' ';
- } else if (utf_char2len(fillchar) > 1) {
- // Can't handle a multi-byte fill character yet.
- fillchar = '-';
}
// The cursor in windows other than the current one isn't always
@@ -3661,7 +3659,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use
out_p = out_p - n + 1;
// Fill up space left over by half a double-wide char.
while (++group_len < stl_items[stl_groupitems[groupdepth]].minwid) {
- *out_p++ = fillchar;
+ MB_CHAR2BYTES(fillchar, out_p);
}
// }
@@ -3684,14 +3682,14 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use
if (min_group_width < 0) {
min_group_width = 0 - min_group_width;
while (group_len++ < min_group_width && out_p < out_end_p) {
- *out_p++ = fillchar;
+ MB_CHAR2BYTES(fillchar, out_p);
}
// If the group is right-aligned, shift everything to the right and
// prepend with filler characters.
} else {
// { Move the group to the right
- memmove(t + min_group_width - group_len, t, (size_t)(out_p - t));
- group_len = min_group_width - group_len;
+ group_len = (min_group_width - group_len) * utf_char2len(fillchar);
+ memmove(t + group_len, t, (size_t)(out_p - t));
if (out_p + group_len >= (out_end_p + 1)) {
group_len = (long)(out_end_p - out_p);
}
@@ -3705,7 +3703,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use
// Prepend the fill characters
for (; group_len > 0; group_len--) {
- *t++ = fillchar;
+ MB_CHAR2BYTES(fillchar, t);
}
}
}
@@ -4001,14 +3999,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use
case STL_VIRTCOL:
case STL_VIRTCOL_ALT: {
- // In list mode virtcol needs to be recomputed
- colnr_T virtcol = wp->w_virtcol;
- if (wp->w_p_list && wp->w_p_lcs_chars.tab1 == NUL) {
- wp->w_p_list = false;
- getvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL);
- wp->w_p_list = true;
- }
- virtcol++;
+ colnr_T virtcol = wp->w_virtcol + 1;
// Don't display %V if it's the same as %c.
if (opt == STL_VIRTCOL_ALT
&& (virtcol == (colnr_T)(!(State & INSERT) && empty_line
@@ -4237,7 +4228,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use
if (l + 1 == minwid && fillchar == '-' && ascii_isdigit(*t)) {
*out_p++ = ' ';
} else {
- *out_p++ = fillchar;
+ MB_CHAR2BYTES(fillchar, out_p);
}
}
minwid = 0;
@@ -4248,20 +4239,21 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use
}
// { Copy the string text into the output buffer
- while (*t && out_p < out_end_p) {
- *out_p++ = *t++;
+ for (; *t && out_p < out_end_p; t++) {
// Change a space by fillchar, unless fillchar is '-' and a
// digit follows.
- if (fillable && out_p[-1] == ' '
- && (!ascii_isdigit(*t) || fillchar != '-')) {
- out_p[-1] = fillchar;
+ if (fillable && *t == ' '
+ && (!ascii_isdigit(*(t + 1)) || fillchar != '-')) {
+ MB_CHAR2BYTES(fillchar, out_p);
+ } else {
+ *out_p++ = *t;
}
}
// }
// For left-aligned items, fill any remaining space with the fillchar
for (; l < minwid && out_p < out_end_p; l++) {
- *out_p++ = fillchar;
+ MB_CHAR2BYTES(fillchar, out_p);
}
// Otherwise if the item is a number, copy that to the output buffer.
@@ -4351,7 +4343,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use
// Only free the string buffer if we allocated it.
// Note: This is not needed if `str` is pointing at `tmp`
if (opt == STL_VIM_EXPR) {
- xfree(str);
+ XFREE_CLEAR(str);
}
if (num >= 0 || (!itemisflag && str && *str)) {
@@ -4454,7 +4446,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use
// Fill up for half a double-wide character.
while (++width < maxwidth) {
- *trunc_p++ = fillchar;
+ MB_CHAR2BYTES(fillchar, trunc_p);
*trunc_p = NUL;
}
// }
@@ -4505,13 +4497,13 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use
standard_spaces * (num_separators - 1);
for (int i = 0; i < num_separators; i++) {
- int dislocation = (i == (num_separators - 1))
- ? final_spaces : standard_spaces;
+ int dislocation = (i == (num_separators - 1)) ? final_spaces : standard_spaces;
+ dislocation *= utf_char2len(fillchar);
char_u *start = stl_items[stl_separator_locations[i]].start;
char_u *seploc = start + dislocation;
STRMOVE(seploc, start);
- for (char_u *s = start; s < seploc; s++) {
- *s = fillchar;
+ for (char_u *s = start; s < seploc; ) {
+ MB_CHAR2BYTES(fillchar, s);
}
for (int item_idx = stl_separator_locations[i] + 1;
@@ -4830,7 +4822,7 @@ void do_arg_all(int count, int forceit, int keep_tabs)
&& (first_tabpage->tp_next == NULL || !had_tab)) {
use_firstwin = true;
} else {
- win_close(wp, !buf_hide(buf) && !bufIsChanged(buf));
+ win_close(wp, !buf_hide(buf) && !bufIsChanged(buf), false);
// check if autocommands removed the next window
if (!win_valid(wpnext)) {
// start all over...
@@ -5021,7 +5013,7 @@ void ex_buffer_all(exarg_T *eap)
&& !ONE_WINDOW
&& !(wp->w_closing
|| wp->w_buffer->b_locked > 0)) {
- win_close(wp, false);
+ win_close(wp, false, false);
wpnext = firstwin; // just in case an autocommand does
// something strange with windows
tpnext = first_tabpage; // start all over...
@@ -5102,7 +5094,7 @@ void ex_buffer_all(exarg_T *eap)
enter_cleanup(&cs);
// User selected Quit at ATTENTION prompt; close this window.
- win_close(curwin, true);
+ win_close(curwin, true, false);
open_wins--;
swap_exists_action = SEA_NONE;
swap_exists_did_quit = true;
@@ -5144,7 +5136,7 @@ void ex_buffer_all(exarg_T *eap)
// BufWrite Autocommands made the window invalid, start over
wp = lastwin;
} else if (r) {
- win_close(wp, !buf_hide(wp->w_buffer));
+ win_close(wp, !buf_hide(wp->w_buffer), false);
open_wins--;
wp = lastwin;
} else {
@@ -5463,30 +5455,43 @@ bool find_win_for_buf(buf_T *buf, win_T **wp, tabpage_T **tp)
return false;
}
-int buf_signcols(buf_T *buf)
+static int buf_signcols_inner(buf_T *buf, int maximum)
{
- if (!buf->b_signcols_valid) {
- sign_entry_T *sign; // a sign in the sign list
- int signcols = 0;
- int linesum = 0;
- linenr_T curline = 0;
-
- FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (sign->se_lnum > curline) {
- if (linesum > signcols) {
- signcols = linesum;
+ sign_entry_T *sign; // a sign in the sign list
+ int signcols = 0;
+ int linesum = 0;
+ linenr_T curline = 0;
+
+ FOR_ALL_SIGNS_IN_BUF(buf, sign) {
+ if (sign->se_lnum > curline) {
+ if (linesum > signcols) {
+ signcols = linesum;
+ if (signcols >= maximum) {
+ return maximum;
}
- curline = sign->se_lnum;
- linesum = 0;
- }
- if (sign->se_has_text_or_icon) {
- linesum++;
}
+ curline = sign->se_lnum;
+ linesum = 0;
+ }
+ if (sign->se_has_text_or_icon) {
+ linesum++;
}
- if (linesum > signcols) {
- signcols = linesum;
+ }
+
+ if (linesum > signcols) {
+ signcols = linesum;
+ if (signcols >= maximum) {
+ return maximum;
}
+ }
+
+ return signcols;
+}
+int buf_signcols(buf_T *buf, int maximum)
+{
+ if (!buf->b_signcols_valid) {
+ int signcols = buf_signcols_inner(buf, maximum);
// Check if we need to redraw
if (signcols != buf->b_signcols) {
buf->b_signcols = signcols;
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 63a550c017..7b17c5b506 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -204,6 +204,10 @@ typedef struct {
#define w_p_nu w_onebuf_opt.wo_nu // 'number'
int wo_rnu;
#define w_p_rnu w_onebuf_opt.wo_rnu // 'relativenumber'
+ char_u *wo_ve;
+#define w_p_ve w_onebuf_opt.wo_ve // 'virtualedit'
+ unsigned wo_ve_flags;
+#define w_ve_flags w_onebuf_opt.wo_ve_flags // flags for 'virtualedit'
long wo_nuw;
#define w_p_nuw w_onebuf_opt.wo_nuw // 'numberwidth'
int wo_wfh;
@@ -866,8 +870,7 @@ struct file_buffer {
int b_mapped_ctrl_c; // modes where CTRL-C is mapped
MarkTree b_marktree[1];
- Map(uint64_t, ExtmarkItem) b_extmark_index[1];
- Map(uint64_t, ExtmarkNs) b_extmark_ns[1]; // extmark namespaces
+ Map(uint32_t, uint32_t) b_extmark_ns[1]; // extmark namespaces
size_t b_virt_line_blocks; // number of virt_line blocks
// array of channel_id:s which have asked to receive updates for this
diff --git a/src/nvim/change.c b/src/nvim/change.c
index 1dbbfff024..6ac759d5e0 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -789,7 +789,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine)
// fixpos is true, we don't want to end up positioned at the NUL,
// unless "restart_edit" is set or 'virtualedit' contains "onemore".
if (col > 0 && fixpos && restart_edit == 0
- && (ve_flags & VE_ONEMORE) == 0) {
+ && (get_ve_flags() & VE_ONEMORE) == 0) {
curwin->w_cursor.col--;
curwin->w_cursor.coladd = 0;
curwin->w_cursor.col -= utf_head_off(oldp, oldp + curwin->w_cursor.col);
@@ -952,11 +952,13 @@ int copy_indent(int size, char_u *src)
///
/// "second_line_indent": indent for after ^^D in Insert mode or if flag
/// OPENLINE_COM_LIST
+/// "did_do_comment" is set to true when intentionally putting the comment
+/// leader in fromt of the new line.
///
/// @param dir FORWARD or BACKWARD
///
/// @return true on success, false on failure
-int open_line(int dir, int flags, int second_line_indent)
+int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
{
char_u *next_line = NULL; // copy of the next line
char_u *p_extra = NULL; // what goes to next line
@@ -969,6 +971,7 @@ int open_line(int dir, int flags, int second_line_indent)
bool retval = false; // return value
int extra_len = 0; // length of p_extra string
int lead_len; // length of comment leader
+ int comment_start = 0; // start index of the comment leader
char_u *lead_flags; // position in 'comments' for comment leader
char_u *leader = NULL; // copy of comment leader
char_u *allocated = NULL; // allocated memory
@@ -977,6 +980,7 @@ int open_line(int dir, int flags, int second_line_indent)
pos_T *pos;
bool do_si = (!p_paste && curbuf->b_p_si && !curbuf->b_p_cin
&& *curbuf->b_p_inde == NUL);
+ bool do_cindent;
bool no_si = false; // reset did_si afterwards
int first_char = NUL; // init for GCC
int vreplace_mode;
@@ -1189,11 +1193,30 @@ int open_line(int dir, int flags, int second_line_indent)
did_ai = true;
}
+ // May do indenting after opening a new line.
+ do_cindent = !p_paste && (curbuf->b_p_cin || *curbuf->b_p_inde != NUL)
+ && in_cinkeys(dir == FORWARD ? KEY_OPEN_FORW : KEY_OPEN_BACK,
+ ' ', linewhite(curwin->w_cursor.lnum));
+
// Find out if the current line starts with a comment leader.
// This may then be inserted in front of the new line.
end_comment_pending = NUL;
if (flags & OPENLINE_DO_COM) {
lead_len = get_leader_len(saved_line, &lead_flags, dir == BACKWARD, true);
+ if (lead_len == 0 && do_cindent && dir == FORWARD) {
+ // Check for a line comment after code.
+ comment_start = check_linecomment(saved_line);
+ if (comment_start != MAXCOL) {
+ lead_len = get_leader_len(saved_line + comment_start,
+ &lead_flags, false, true);
+ if (lead_len != 0) {
+ lead_len += comment_start;
+ if (did_do_comment != NULL) {
+ *did_do_comment = true;
+ }
+ }
+ }
+ }
} else {
lead_len = 0;
}
@@ -1349,6 +1372,13 @@ int open_line(int dir, int flags, int second_line_indent)
STRLCPY(leader, saved_line, lead_len + 1);
+ // TODO(vim): handle multi-byte and double width chars
+ for (int li = 0; li < comment_start; li++) {
+ if (!ascii_iswhite(leader[li])) {
+ leader[li] = ' ';
+ }
+ }
+
// Replace leader with lead_repl, right or left adjusted
if (lead_repl != NULL) {
int c = 0;
@@ -1758,13 +1788,7 @@ int open_line(int dir, int flags, int second_line_indent)
ai_col = (colnr_T)getwhitecols_curline();
}
// May do indenting after opening a new line.
- if (!p_paste
- && (curbuf->b_p_cin
- || *curbuf->b_p_inde != NUL
- )
- && in_cinkeys(dir == FORWARD
- ? KEY_OPEN_FORW
- : KEY_OPEN_BACK, ' ', linewhite(curwin->w_cursor.lnum))) {
+ if (do_cindent) {
do_c_expr_indent();
ai_col = (colnr_T)getwhitecols_curline();
}
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
index cd5134fe5f..d79c0acc4a 100644
--- a/src/nvim/channel.c
+++ b/src/nvim/channel.c
@@ -613,7 +613,6 @@ static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, size_
} else {
if (chan->term) {
terminal_receive(chan->term, ptr, count);
- terminal_flush_output(chan->term);
}
rbuffer_consumed(buf, count);
diff --git a/src/nvim/channel.h b/src/nvim/channel.h
index 81b75e2d31..50d6b3600a 100644
--- a/src/nvim/channel.h
+++ b/src/nvim/channel.h
@@ -96,6 +96,8 @@ struct Channel {
EXTERN PMap(uint64_t) channels INIT(= MAP_INIT);
+EXTERN Callback on_print INIT(= CALLBACK_INIT);
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "channel.h.generated.h"
#endif
diff --git a/src/nvim/charset.c b/src/nvim/charset.c
index 599d662993..583a040ed1 100644
--- a/src/nvim/charset.c
+++ b/src/nvim/charset.c
@@ -217,9 +217,7 @@ int buf_init_chartab(buf_T *buf, int global)
}
} else if (i == 1) {
// (re)set printable
- // For double-byte we keep the cell width, so
- // that we can detect it from the first byte.
- if (((c < ' ') || (c > '~'))) {
+ if (c < ' ' || c > '~') {
if (tilde) {
g_chartab[c] = (uint8_t)((g_chartab[c] & ~CT_CELL_MASK)
+ ((dy_flags & DY_UHEX) ? 4 : 2));
@@ -539,7 +537,7 @@ char_u *transchar_buf(const buf_T *buf, int c)
c = K_SECOND(c);
}
- if ((!chartab_initialized && (((c >= ' ') && (c <= '~'))))
+ if ((!chartab_initialized && (c >= ' ' && c <= '~'))
|| ((c <= 0xFF) && vim_isprintc_strict(c))) {
// printable character
transchar_charbuf[i] = (char_u)c;
diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c
index 6e2c6232d7..55f55a46b2 100644
--- a/src/nvim/cursor.c
+++ b/src/nvim/cursor.c
@@ -15,6 +15,7 @@
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/move.h"
+#include "nvim/option.h"
#include "nvim/plines.h"
#include "nvim/screen.h"
#include "nvim/state.h"
@@ -110,7 +111,7 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a
|| (State & TERM_FOCUS)
|| restart_edit != NUL
|| (VIsual_active && *p_sel != 'o')
- || ((ve_flags & VE_ONEMORE) && wcol < MAXCOL);
+ || ((get_ve_flags() & VE_ONEMORE) && wcol < MAXCOL);
line = ml_get_buf(curbuf, pos->lnum, false);
if (wcol >= MAXCOL) {
@@ -366,6 +367,7 @@ void check_cursor_col_win(win_T *win)
colnr_T len;
colnr_T oldcol = win->w_cursor.col;
colnr_T oldcoladd = win->w_cursor.col + win->w_cursor.coladd;
+ unsigned int cur_ve_flags = get_ve_flags();
len = (colnr_T)STRLEN(ml_get_buf(win->w_buffer, win->w_cursor.lnum, false));
if (len == 0) {
@@ -377,7 +379,7 @@ void check_cursor_col_win(win_T *win)
* - 'virtualedit' is set */
if ((State & INSERT) || restart_edit
|| (VIsual_active && *p_sel != 'o')
- || (ve_flags & VE_ONEMORE)
+ || (cur_ve_flags & VE_ONEMORE)
|| virtual_active()) {
win->w_cursor.col = len;
} else {
@@ -394,7 +396,7 @@ void check_cursor_col_win(win_T *win)
// line.
if (oldcol == MAXCOL) {
win->w_cursor.coladd = 0;
- } else if (ve_flags == VE_ALL) {
+ } else if (cur_ve_flags == VE_ALL) {
if (oldcoladd > win->w_cursor.col) {
win->w_cursor.coladd = oldcoladd - win->w_cursor.col;
diff --git a/src/nvim/debugger.c b/src/nvim/debugger.c
index b6e35f3047..75ebf0084e 100644
--- a/src/nvim/debugger.c
+++ b/src/nvim/debugger.c
@@ -268,7 +268,7 @@ void do_debug(char_u *cmd)
DOCMD_VERBOSE|DOCMD_EXCRESET);
debug_break_level = n;
}
- lines_left = (int)(Rows - 1);
+ lines_left = Rows - 1;
}
xfree(cmdline);
@@ -277,7 +277,7 @@ void do_debug(char_u *cmd)
redraw_all_later(NOT_VALID);
need_wait_return = false;
msg_scroll = save_msg_scroll;
- lines_left = (int)(Rows - 1);
+ lines_left = Rows - 1;
State = save_State;
debug_mode = false;
did_emsg = save_did_emsg;
diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c
index c0f3c32f93..935b233752 100644
--- a/src/nvim/decoration.c
+++ b/src/nvim/decoration.c
@@ -13,8 +13,6 @@
# include "decoration.c.generated.h"
#endif
-static PMap(uint64_t) hl_decors;
-
/// Add highlighting to a buffer, bounded by two cursor positions,
/// with an offset.
///
@@ -33,9 +31,9 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start
{
colnr_T hl_start = 0;
colnr_T hl_end = 0;
- Decoration *decor = decor_hl(hl_id);
+ Decoration decor = DECORATION_INIT;
+ decor.hl_id = hl_id;
- decor->priority = DECOR_PRIORITY_BASE;
// TODO(bfredl): if decoration had blocky mode, we could avoid this loop
for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum++) {
int end_off = 0;
@@ -59,40 +57,23 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start
hl_start = pos_start.col + offset;
hl_end = pos_end.col + offset;
}
- (void)extmark_set(buf, (uint64_t)src_id, NULL,
- (int)lnum-1, hl_start, (int)lnum-1+end_off, hl_end,
- decor, true, false, kExtmarkNoUndo);
- }
-}
-
-Decoration *decor_hl(int hl_id)
-{
- assert(hl_id > 0);
- Decoration **dp = (Decoration **)pmap_ref(uint64_t)(&hl_decors,
- (uint64_t)hl_id, true);
- if (*dp) {
- return *dp;
+ extmark_set(buf, (uint32_t)src_id, NULL,
+ (int)lnum-1, hl_start, (int)lnum-1+end_off, hl_end,
+ &decor, true, false, kExtmarkNoUndo);
}
-
- Decoration *decor = xcalloc(1, sizeof(*decor));
- decor->hl_id = hl_id;
- decor->shared = true;
- decor->priority = DECOR_PRIORITY_BASE;
- *dp = decor;
- return decor;
}
void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor)
{
- if (decor->hl_id && row2 >= row1) {
+ if ((!decor || decor->hl_id) && row2 >= row1) {
redraw_buf_range_later(buf, row1+1, row2+1);
}
- if (kv_size(decor->virt_text)) {
+ if (decor && kv_size(decor->virt_text)) {
redraw_buf_line_later(buf, row1+1);
}
- if (kv_size(decor->virt_lines)) {
+ if (decor && kv_size(decor->virt_lines)) {
redraw_buf_line_later(buf, MIN(buf->b_ml.ml_line_count,
row1+1+(decor->virt_lines_above?0:1)));
}
@@ -100,17 +81,17 @@ void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor)
void decor_remove(buf_T *buf, int row, int row2, Decoration *decor)
{
- if (kv_size(decor->virt_lines)) {
+ decor_redraw(buf, row, row2, decor);
+ if (decor && kv_size(decor->virt_lines)) {
assert(buf->b_virt_line_blocks > 0);
buf->b_virt_line_blocks--;
}
- decor_redraw(buf, row, row2, decor);
decor_free(decor);
}
void decor_free(Decoration *decor)
{
- if (decor && !decor->shared) {
+ if (decor) {
clear_virttext(&decor->virt_text);
for (size_t i = 0; i < kv_size(decor->virt_lines); i++) {
clear_virttext(&kv_A(decor->virt_lines, i).line);
@@ -134,17 +115,16 @@ Decoration *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id)
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, row, 0, itr);
while (true) {
- mtmark_t mark = marktree_itr_current(itr);
- if (mark.row < 0 || mark.row > row) {
+ mtkey_t mark = marktree_itr_current(itr);
+ if (mark.pos.row < 0 || mark.pos.row > row) {
break;
- } else if (marktree_decor_level(mark.id) < kDecorLevelVisible) {
+ } else if (marktree_decor_level(mark) < kDecorLevelVisible) {
goto next_mark;
}
- ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index,
- mark.id, false);
- if (item && (ns_id == 0 || ns_id == item->ns_id)
- && item->decor && kv_size(item->decor->virt_text)) {
- return item->decor;
+ Decoration *decor = mark.decor_full;
+ if ((ns_id == 0 || ns_id == mark.ns)
+ && decor && kv_size(decor->virt_text)) {
+ return decor;
}
next_mark:
marktree_itr_next(buf->b_marktree, itr);
@@ -163,7 +143,20 @@ bool decor_redraw_reset(buf_T *buf, DecorState *state)
}
}
kv_size(state->active) = 0;
- return map_size(buf->b_extmark_index);
+ return buf->b_marktree->n_keys;
+}
+
+Decoration get_decor(mtkey_t mark)
+{
+ if (mark.decor_full) {
+ return *mark.decor_full;
+ } else {
+ Decoration fake = DECORATION_INIT;
+ fake.hl_id = mark.hl_id;
+ fake.priority = mark.priority;
+ fake.hl_eol = (mark.flags & MT_FLAG_HL_EOL);
+ return fake;
+ }
}
@@ -176,42 +169,35 @@ bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state)
}
marktree_itr_rewind(buf->b_marktree, state->itr);
while (true) {
- mtmark_t mark = marktree_itr_current(state->itr);
- if (mark.row < 0) { // || mark.row > end_row
+ mtkey_t mark = marktree_itr_current(state->itr);
+ if (mark.pos.row < 0) { // || mark.row > end_row
break;
}
- if ((mark.row < top_row && mark.id&MARKTREE_END_FLAG)
- || marktree_decor_level(mark.id) < kDecorLevelVisible) {
+ if ((mark.pos.row < top_row && mt_end(mark))
+ || marktree_decor_level(mark) < kDecorLevelVisible) {
goto next_mark;
}
- uint64_t start_id = mark.id & ~MARKTREE_END_FLAG;
- ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index,
- start_id, false);
- if (!item || !item->decor) {
- goto next_mark;
- }
- Decoration *decor = item->decor;
+ Decoration decor = get_decor(mark);
- mtpos_t altpos = marktree_lookup(buf->b_marktree,
- mark.id^MARKTREE_END_FLAG, NULL);
+ mtpos_t altpos = marktree_get_altpos(buf->b_marktree, mark, NULL);
- if ((!(mark.id&MARKTREE_END_FLAG) && altpos.row < top_row
- && !kv_size(decor->virt_text))
- || ((mark.id&MARKTREE_END_FLAG) && altpos.row >= top_row)) {
+ if ((!mt_end(mark) && altpos.row < top_row
+ && !kv_size(decor.virt_text))
+ || (mt_end(mark) && altpos.row >= top_row)) {
goto next_mark;
}
- if (mark.id&MARKTREE_END_FLAG) {
- decor_add(state, altpos.row, altpos.col, mark.row, mark.col,
- decor, false);
+ if (mt_end(mark)) {
+ decor_add(state, altpos.row, altpos.col, mark.pos.row, mark.pos.col,
+ &decor, false);
} else {
if (altpos.row == -1) {
- altpos.row = mark.row;
- altpos.col = mark.col;
+ altpos.row = mark.pos.row;
+ altpos.col = mark.pos.col;
}
- decor_add(state, mark.row, mark.col, altpos.row, altpos.col,
- decor, false);
+ decor_add(state, mark.pos.row, mark.pos.col, altpos.row, altpos.col,
+ &decor, false);
}
next_mark:
@@ -266,43 +252,36 @@ int decor_redraw_col(buf_T *buf, int col, int win_col, bool hidden, DecorState *
while (true) {
// TODO(bfredl): check duplicate entry in "intersection"
// branch
- mtmark_t mark = marktree_itr_current(state->itr);
- if (mark.row < 0 || mark.row > state->row) {
+ mtkey_t mark = marktree_itr_current(state->itr);
+ if (mark.pos.row < 0 || mark.pos.row > state->row) {
break;
- } else if (mark.row == state->row && mark.col > col) {
- state->col_until = mark.col-1;
+ } else if (mark.pos.row == state->row && mark.pos.col > col) {
+ state->col_until = mark.pos.col-1;
break;
}
- if ((mark.id&MARKTREE_END_FLAG)
- || marktree_decor_level(mark.id) < kDecorLevelVisible) {
+ if (mt_end(mark)
+ || marktree_decor_level(mark) < kDecorLevelVisible) {
goto next_mark;
}
- ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index,
- mark.id, false);
- if (!item || !item->decor) {
- goto next_mark;
- }
- Decoration *decor = item->decor;
+ Decoration decor = get_decor(mark);
- mtpos_t endpos = marktree_lookup(buf->b_marktree,
- mark.id|MARKTREE_END_FLAG, NULL);
+ mtpos_t endpos = marktree_get_altpos(buf->b_marktree, mark, NULL);
if (endpos.row == -1) {
- endpos.row = mark.row;
- endpos.col = mark.col;
+ endpos = mark.pos;
}
- if (endpos.row < mark.row
- || (endpos.row == mark.row && endpos.col <= mark.col)) {
- if (!kv_size(decor->virt_text)) {
+ if (endpos.row < mark.pos.row
+ || (endpos.row == mark.pos.row && endpos.col <= mark.pos.col)) {
+ if (!kv_size(decor.virt_text)) {
goto next_mark;
}
}
- decor_add(state, mark.row, mark.col, endpos.row, endpos.col,
- decor, false);
+ decor_add(state, mark.pos.row, mark.pos.col, endpos.row, endpos.col,
+ &decor, false);
next_mark:
marktree_itr_next(buf->b_marktree, state->itr);
@@ -452,18 +431,18 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines)
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, row, 0, itr);
while (true) {
- mtmark_t mark = marktree_itr_current(itr);
- if (mark.row < 0 || mark.row >= end_row) {
+ mtkey_t mark = marktree_itr_current(itr);
+ if (mark.pos.row < 0 || mark.pos.row >= end_row) {
break;
- } else if (marktree_decor_level(mark.id) < kDecorLevelVirtLine) {
+ } else if (marktree_decor_level(mark) < kDecorLevelVirtLine) {
goto next_mark;
}
- bool above = mark.row > (int)(lnum - 2);
- ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark.id, false);
- if (item && item->decor && item->decor->virt_lines_above == above) {
- virt_lines += (int)kv_size(item->decor->virt_lines);
+ bool above = mark.pos.row > (int)(lnum - 2);
+ Decoration *decor = mark.decor_full;
+ if (decor && decor->virt_lines_above == above) {
+ virt_lines += (int)kv_size(decor->virt_lines);
if (lines) {
- kv_splice(*lines, item->decor->virt_lines);
+ kv_splice(*lines, decor->virt_lines);
}
}
next_mark:
diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h
index 611b4223da..02472d09e4 100644
--- a/src/nvim/decoration.h
+++ b/src/nvim/decoration.h
@@ -17,6 +17,8 @@ typedef enum {
kVTRightAlign,
} VirtTextPos;
+EXTERN const char *const virt_text_pos_str[] INIT(= { "eol", "overlay", "win_col", "right_align" });
+
typedef enum {
kHlModeUnknown,
kHlModeReplace,
@@ -24,6 +26,8 @@ typedef enum {
kHlModeBlend,
} HlMode;
+EXTERN const char *const hl_mode_str[] INIT(= { "", "replace", "combine", "blend" });
+
typedef kvec_t(VirtTextChunk) VirtText;
#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE)
@@ -42,7 +46,6 @@ struct Decoration {
// TODO(bfredl): at some point turn this into FLAGS
bool virt_text_hide;
bool hl_eol;
- bool shared; // shared decoration, don't free
bool virt_lines_above;
// TODO(bfredl): style, signs, etc
DecorPriority priority;
@@ -50,7 +53,7 @@ struct Decoration {
int virt_text_width; // width of virt_text
};
#define DECORATION_INIT { KV_INITIAL_VALUE, KV_INITIAL_VALUE, 0, kVTEndOfLine, kHlModeUnknown, \
- false, false, false, false, DECOR_PRIORITY_BASE, 0, 0 }
+ false, false, false, DECOR_PRIORITY_BASE, 0, 0 }
typedef struct {
int start_row;
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index 4f4da7c2a9..233753839b 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -82,6 +82,14 @@ typedef struct {
garray_T dout_ga; // used for internal diff
} diffout_T;
+// used for recording hunks from xdiff
+typedef struct {
+ linenr_T lnum_orig;
+ long count_orig;
+ linenr_T lnum_new;
+ long count_new;
+} diffhunk_T;
+
// two diff inputs and one result
typedef struct {
diffin_T dio_orig; // original file input
@@ -782,9 +790,14 @@ static int diff_write(buf_T *buf, diffin_T *din)
// Always use 'fileformat' set to "unix".
char_u *save_ff = buf->b_p_ff;
buf->b_p_ff = vim_strsave((char_u *)FF_UNIX);
+ const bool save_lockmarks = cmdmod.lockmarks;
+ // Writing the buffer is an implementation detail of performing the diff,
+ // so it shouldn't update the '[ and '] marks.
+ cmdmod.lockmarks = true;
int r = buf_write(buf, din->din_fname, NULL,
(linenr_T)1, buf->b_ml.ml_line_count,
NULL, false, false, false, true);
+ cmdmod.lockmarks = save_lockmarks;
free_string_option(buf->b_p_ff);
buf->b_p_ff = save_ff;
return r;
@@ -852,7 +865,7 @@ static void diff_try_update(diffio_T *dio, int idx_orig, exarg_T *eap)
}
// Read the diff output and add each entry to the diff list.
- diff_read(idx_orig, idx_new, &dio->dio_diff);
+ diff_read(idx_orig, idx_new, dio);
clear_diffin(&dio->dio_new);
clear_diffout(&dio->dio_diff);
@@ -1078,7 +1091,7 @@ static int diff_file_internal(diffio_T *diffio)
emit_cfg.ctxlen = 0; // don't need any diff_context here
emit_cb.priv = &diffio->dio_diff;
- emit_cb.out_line = xdiff_out;
+ emit_cfg.hunk_func = xdiff_out;
if (xdl_diff(&diffio->dio_orig.din_mmfile,
&diffio->dio_new.din_mmfile,
&param, &emit_cfg, &emit_cb) < 0) {
@@ -1519,20 +1532,20 @@ void ex_diffoff(exarg_T *eap)
/// @param idx_orig idx of original file
/// @param idx_new idx of new file
/// @dout diff output
-static void diff_read(int idx_orig, int idx_new, diffout_T *dout)
+static void diff_read(int idx_orig, int idx_new, diffio_T *dio)
{
FILE *fd = NULL;
int line_idx = 0;
diff_T *dprev = NULL;
diff_T *dp = curtab->tp_first_diff;
diff_T *dn, *dpl;
+ diffout_T *dout = &dio->dio_diff;
char_u linebuf[LBUFLEN]; // only need to hold the diff line
char_u *line;
long off;
int i;
- linenr_T lnum_orig, lnum_new;
- long count_orig, count_new;
int notset = true; // block "*dp" not set yet
+ diffhunk_T *hunk;
enum {
DIFF_ED,
DIFF_UNIFIED,
@@ -1549,70 +1562,79 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout)
}
}
+ if (!dio->dio_internal) {
+ hunk = xmalloc(sizeof(*hunk));
+ }
+
for (;;) {
- if (fd == NULL) {
+ if (dio->dio_internal) {
if (line_idx >= dout->dout_ga.ga_len) {
break; // did last line
}
- line = ((char_u **)dout->dout_ga.ga_data)[line_idx++];
+ hunk = ((diffhunk_T **)dout->dout_ga.ga_data)[line_idx++];
} else {
- if (vim_fgets(linebuf, LBUFLEN, fd)) {
- break; // end of file
- }
- line = linebuf;
- }
-
- if (diffstyle == DIFF_NONE) {
- // Determine diff style.
- // ed like diff looks like this:
- // {first}[,{last}]c{first}[,{last}]
- // {first}a{first}[,{last}]
- // {first}[,{last}]d{first}
- //
- // unified diff looks like this:
- // --- file1 2018-03-20 13:23:35.783153140 +0100
- // +++ file2 2018-03-20 13:23:41.183156066 +0100
- // @@ -1,3 +1,5 @@
- if (isdigit(*line)) {
- diffstyle = DIFF_ED;
- } else if ((STRNCMP(line, "@@ ", 3) == 0)) {
- diffstyle = DIFF_UNIFIED;
- } else if ((STRNCMP(line, "--- ", 4) == 0) // -V501
- && (vim_fgets(linebuf, LBUFLEN, fd) == 0) // -V501
- && (STRNCMP(line, "+++ ", 4) == 0)
- && (vim_fgets(linebuf, LBUFLEN, fd) == 0) // -V501
- && (STRNCMP(line, "@@ ", 3) == 0)) {
- diffstyle = DIFF_UNIFIED;
+ if (fd == NULL) {
+ if (line_idx >= dout->dout_ga.ga_len) {
+ break; // did last line
+ }
+ line = ((char_u **)dout->dout_ga.ga_data)[line_idx++];
} else {
- // Format not recognized yet, skip over this line. Cygwin diff
- // may put a warning at the start of the file.
- continue;
+ if (vim_fgets(linebuf, LBUFLEN, fd)) {
+ break; // end of file
+ }
+ line = linebuf;
}
- }
- if (diffstyle == DIFF_ED) {
- if (!isdigit(*line)) {
- continue; // not the start of a diff block
- }
- if (parse_diff_ed(line, &lnum_orig, &count_orig,
- &lnum_new, &count_new) == FAIL) {
- continue;
- }
- } else {
- assert(diffstyle == DIFF_UNIFIED);
- if (STRNCMP(line, "@@ ", 3) != 0) {
- continue; // not the start of a diff block
+ if (diffstyle == DIFF_NONE) {
+ // Determine diff style.
+ // ed like diff looks like this:
+ // {first}[,{last}]c{first}[,{last}]
+ // {first}a{first}[,{last}]
+ // {first}[,{last}]d{first}
+ //
+ // unified diff looks like this:
+ // --- file1 2018-03-20 13:23:35.783153140 +0100
+ // +++ file2 2018-03-20 13:23:41.183156066 +0100
+ // @@ -1,3 +1,5 @@
+ if (isdigit(*line)) {
+ diffstyle = DIFF_ED;
+ } else if ((STRNCMP(line, "@@ ", 3) == 0)) {
+ diffstyle = DIFF_UNIFIED;
+ } else if ((STRNCMP(line, "--- ", 4) == 0) // -V501
+ && (vim_fgets(linebuf, LBUFLEN, fd) == 0) // -V501
+ && (STRNCMP(line, "+++ ", 4) == 0)
+ && (vim_fgets(linebuf, LBUFLEN, fd) == 0) // -V501
+ && (STRNCMP(line, "@@ ", 3) == 0)) {
+ diffstyle = DIFF_UNIFIED;
+ } else {
+ // Format not recognized yet, skip over this line. Cygwin diff
+ // may put a warning at the start of the file.
+ continue;
+ }
}
- if (parse_diff_unified(line, &lnum_orig, &count_orig,
- &lnum_new, &count_new) == FAIL) {
- continue;
+
+ if (diffstyle == DIFF_ED) {
+ if (!isdigit(*line)) {
+ continue; // not the start of a diff block
+ }
+ if (parse_diff_ed(line, hunk) == FAIL) {
+ continue;
+ }
+ } else {
+ assert(diffstyle == DIFF_UNIFIED);
+ if (STRNCMP(line, "@@ ", 3) != 0) {
+ continue; // not the start of a diff block
+ }
+ if (parse_diff_unified(line, hunk) == FAIL) {
+ continue;
+ }
}
}
// Go over blocks before the change, for which orig and new are equal.
// Copy blocks from orig to new.
while (dp != NULL
- && lnum_orig > dp->df_lnum[idx_orig] + dp->df_count[idx_orig]) {
+ && hunk->lnum_orig > dp->df_lnum[idx_orig] + dp->df_count[idx_orig]) {
if (notset) {
diff_copy_entry(dprev, dp, idx_orig, idx_new);
}
@@ -1622,19 +1644,19 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout)
}
if ((dp != NULL)
- && (lnum_orig <= dp->df_lnum[idx_orig] + dp->df_count[idx_orig])
- && (lnum_orig + count_orig >= dp->df_lnum[idx_orig])) {
+ && (hunk->lnum_orig <= dp->df_lnum[idx_orig] + dp->df_count[idx_orig])
+ && (hunk->lnum_orig + hunk->count_orig >= dp->df_lnum[idx_orig])) {
// New block overlaps with existing block(s).
// First find last block that overlaps.
for (dpl = dp; dpl->df_next != NULL; dpl = dpl->df_next) {
- if (lnum_orig + count_orig < dpl->df_next->df_lnum[idx_orig]) {
+ if (hunk->lnum_orig + hunk->count_orig < dpl->df_next->df_lnum[idx_orig]) {
break;
}
}
// If the newly found block starts before the old one, set the
// start back a number of lines.
- off = dp->df_lnum[idx_orig] - lnum_orig;
+ off = dp->df_lnum[idx_orig] - hunk->lnum_orig;
if (off > 0) {
for (i = idx_orig; i < idx_new; ++i) {
@@ -1642,15 +1664,15 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout)
dp->df_lnum[i] -= off;
}
}
- dp->df_lnum[idx_new] = lnum_new;
- dp->df_count[idx_new] = count_new;
+ dp->df_lnum[idx_new] = hunk->lnum_new;
+ dp->df_count[idx_new] = hunk->count_new;
} else if (notset) {
// new block inside existing one, adjust new block
- dp->df_lnum[idx_new] = lnum_new + off;
- dp->df_count[idx_new] = count_new - off;
+ dp->df_lnum[idx_new] = hunk->lnum_new + off;
+ dp->df_count[idx_new] = hunk->count_new - off;
} else {
// second overlap of new block with existing block
- dp->df_count[idx_new] += count_new - count_orig
+ dp->df_count[idx_new] += hunk->count_new - hunk->count_orig
+ dpl->df_lnum[idx_orig] +
dpl->df_count[idx_orig]
- (dp->df_lnum[idx_orig] +
@@ -1659,7 +1681,7 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout)
// Adjust the size of the block to include all the lines to the
// end of the existing block or the new diff, whatever ends last.
- off = (lnum_orig + count_orig)
+ off = (hunk->lnum_orig + hunk->count_orig)
- (dpl->df_lnum[idx_orig] + dpl->df_count[idx_orig]);
if (off < 0) {
@@ -1691,10 +1713,10 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout)
// Allocate a new diffblock.
dp = diff_alloc_new(curtab, dprev, dp);
- dp->df_lnum[idx_orig] = lnum_orig;
- dp->df_count[idx_orig] = count_orig;
- dp->df_lnum[idx_new] = lnum_new;
- dp->df_count[idx_new] = count_new;
+ dp->df_lnum[idx_orig] = hunk->lnum_orig;
+ dp->df_count[idx_orig] = hunk->count_orig;
+ dp->df_lnum[idx_new] = hunk->lnum_new;
+ dp->df_count[idx_new] = hunk->count_new;
// Set values for other buffers, these must be equal to the
// original buffer, otherwise there would have been a change
@@ -1718,6 +1740,10 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout)
notset = true;
}
+ if (!dio->dio_internal) {
+ xfree(hunk);
+ }
+
if (fd != NULL) {
fclose(fd);
}
@@ -3026,8 +3052,7 @@ linenr_T diff_lnum_win(linenr_T lnum, win_T *wp)
/// Handle an ED style diff line.
/// Return FAIL if the line does not contain diff info.
///
-static int parse_diff_ed(char_u *line, linenr_T *lnum_orig, long *count_orig, linenr_T *lnum_new,
- long *count_new)
+static int parse_diff_ed(char_u *line, diffhunk_T *hunk)
{
char_u *p;
long f1, l1, f2, l2;
@@ -3061,18 +3086,18 @@ static int parse_diff_ed(char_u *line, linenr_T *lnum_orig, long *count_orig, li
}
if (difftype == 'a') {
- *lnum_orig = f1 + 1;
- *count_orig = 0;
+ hunk->lnum_orig = f1 + 1;
+ hunk->count_orig = 0;
} else {
- *lnum_orig = f1;
- *count_orig = l1 - f1 + 1;
+ hunk->lnum_orig = f1;
+ hunk->count_orig = l1 - f1 + 1;
}
if (difftype == 'd') {
- *lnum_new = f2 + 1;
- *count_new = 0;
+ hunk->lnum_new = f2 + 1;
+ hunk->count_new = 0;
} else {
- *lnum_new = f2;
- *count_new = l2 - f2 + 1;
+ hunk->lnum_new = f2;
+ hunk->count_new = l2 - f2 + 1;
}
return OK;
}
@@ -3081,8 +3106,7 @@ static int parse_diff_ed(char_u *line, linenr_T *lnum_orig, long *count_orig, li
/// Parses unified diff with zero(!) context lines.
/// Return FAIL if there is no diff information in "line".
///
-static int parse_diff_unified(char_u *line, linenr_T *lnum_orig, long *count_orig,
- linenr_T *lnum_new, long *count_new)
+static int parse_diff_unified(char_u *line, diffhunk_T *hunk)
{
char_u *p;
long oldline, oldcount, newline, newcount;
@@ -3120,10 +3144,10 @@ static int parse_diff_unified(char_u *line, linenr_T *lnum_orig, long *count_ori
newline = 1;
}
- *lnum_orig = oldline;
- *count_orig = oldcount;
- *lnum_new = newline;
- *count_new = newcount;
+ hunk->lnum_orig = oldline;
+ hunk->count_orig = oldcount;
+ hunk->lnum_new = newline;
+ hunk->count_new = newcount;
return OK;
}
@@ -3135,25 +3159,17 @@ static int parse_diff_unified(char_u *line, linenr_T *lnum_orig, long *count_ori
/// Callback function for the xdl_diff() function.
/// Stores the diff output in a grow array.
///
-static int xdiff_out(void *priv, mmbuffer_t *mb, int nbuf)
+static int xdiff_out(long start_a, long count_a, long start_b, long count_b,
+ void *priv)
{
diffout_T *dout = (diffout_T *)priv;
- char_u *p;
-
- // The header line always comes by itself, text lines in at least two
- // parts. We drop the text part.
- if (nbuf > 1) {
- return 0;
- }
-
- // sanity check
- if (STRNCMP(mb[0].ptr, "@@ ", 3) != 0) {
- return 0;
- }
+ diffhunk_T *p = xmalloc(sizeof(*p));
ga_grow(&dout->dout_ga, 1);
-
- p = vim_strnsave((char_u *)mb[0].ptr, mb[0].size);
- ((char_u **)dout->dout_ga.ga_data)[dout->dout_ga.ga_len++] = p;
+ p->lnum_orig = start_a + 1;
+ p->count_orig = count_a;
+ p->lnum_new = start_b + 1;
+ p->count_new = count_b;
+ ((diffhunk_T **)dout->dout_ga.ga_data)[dout->dout_ga.ga_len++] = p;
return 0;
}
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 5eef4350b7..00ffa7cba1 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -260,7 +260,7 @@ static colnr_T Insstart_blank_vcol; // vcol for first inserted blank
static bool update_Insstart_orig = true; // set Insstart_orig to Insstart
static char_u *last_insert = NULL; // the text of the previous insert,
- // K_SPECIAL and CSI are escaped
+ // K_SPECIAL is escaped
static int last_insert_skip; // nr of chars in front of previous insert
static int new_insert_skip; // nr of chars in front of current insert
static int did_restart_edit; // "restart_edit" when calling edit()
@@ -663,8 +663,12 @@ static int insert_execute(VimState *state, int key)
InsertState *const s = (InsertState *)state;
if (stop_insert_mode) {
// Insert mode ended, possibly from a callback.
+ if (key != K_IGNORE && key != K_NOP) {
+ vungetc(key);
+ }
s->count = 0;
s->nomove = true;
+ ins_compl_prep(ESC);
return 0;
}
@@ -909,7 +913,7 @@ static int insert_handle_key(InsertState *s)
ins_ctrl_o();
// don't move the cursor left when 'virtualedit' has "onemore".
- if (ve_flags & VE_ONEMORE) {
+ if (get_ve_flags() & VE_ONEMORE) {
ins_at_eol = false;
s->nomove = true;
}
@@ -1082,11 +1086,15 @@ static int insert_handle_key(InsertState *s)
map_execute_lua();
check_pum:
+ // nvim_select_popupmenu_item() can be called from the handling of
+ // K_EVENT, K_COMMAND, or K_LUA.
// TODO(bfredl): Not entirely sure this indirection is necessary
// but doing like this ensures using nvim_select_popupmenu_item is
// equivalent to selecting the item with a typed key.
if (pum_want.active) {
if (pum_visible()) {
+ // Set this to NULL so that ins_complete() will update the message.
+ edit_submode_extra = NULL;
insert_do_complete(s);
if (pum_want.finish) {
// accept the item and stop completion
@@ -3620,7 +3628,7 @@ static bool ins_compl_prep(int c)
// Ignore end of Select mode mapping and mouse scroll buttons.
if (c == K_SELECT || c == K_MOUSEDOWN || c == K_MOUSEUP
|| c == K_MOUSELEFT || c == K_MOUSERIGHT || c == K_EVENT
- || c == K_COMMAND) {
+ || c == K_COMMAND || c == K_LUA) {
return retval;
}
@@ -4986,7 +4994,7 @@ void ins_compl_check_keys(int frequency, int in_compl_func)
*/
static int ins_compl_key2dir(int c)
{
- if (c == K_EVENT || c == K_COMMAND) {
+ if (c == K_EVENT || c == K_COMMAND || c == K_LUA) {
return pum_want.item < pum_selected_item ? BACKWARD : FORWARD;
}
if (c == Ctrl_P || c == Ctrl_L
@@ -5016,7 +5024,7 @@ static int ins_compl_key2count(int c)
{
int h;
- if (c == K_EVENT || c == K_COMMAND) {
+ if (c == K_EVENT || c == K_COMMAND || c == K_LUA) {
int offset = pum_want.item - pum_selected_item;
return abs(offset);
}
@@ -5050,6 +5058,7 @@ static bool ins_compl_use_match(int c)
return false;
case K_EVENT:
case K_COMMAND:
+ case K_LUA:
return pum_want.active && pum_want.insert;
}
return true;
@@ -5628,8 +5637,12 @@ int get_literal(void)
i = 0;
for (;;) {
nc = plain_vgetc();
- if (!(State & CMDLINE)
- && MB_BYTE2LEN_CHECK(nc) == 1) {
+ if ((mod_mask & ~MOD_MASK_SHIFT) != 0) {
+ // A character with non-Shift modifiers should not be a valid
+ // character for i_CTRL-V_digit.
+ break;
+ }
+ if (!(State & CMDLINE) && MB_BYTE2LEN_CHECK(nc) == 1) {
add_to_showcmd(nc);
}
if (nc == 'x' || nc == 'X') {
@@ -5695,6 +5708,8 @@ int get_literal(void)
--no_mapping;
if (nc) {
vungetc(nc);
+ // A character typed with i_CTRL-V_digit cannot have modifiers.
+ mod_mask = 0;
}
got_int = false; // CTRL-C typed after CTRL-V is not an interrupt
return cc;
@@ -6006,6 +6021,7 @@ static void internal_format(int textwidth, int second_indent, int flags, int for
char_u *saved_text = NULL;
colnr_T col;
colnr_T end_col;
+ bool did_do_comment = false;
virtcol = get_nolist_virtcol()
+ char2cells(c != NUL ? c : gchar_cursor());
@@ -6121,8 +6137,7 @@ static void internal_format(int textwidth, int second_indent, int flags, int for
if (curwin->w_cursor.col <= (colnr_T)wantcol) {
break;
}
- } else if ((cc >= 0x100 || !utf_allow_break_before(cc))
- && fo_multibyte) {
+ } else if ((cc >= 0x100 || !utf_allow_break_before(cc)) && fo_multibyte) {
int ncc;
bool allow_break;
@@ -6279,11 +6294,18 @@ static void internal_format(int textwidth, int second_indent, int flags, int for
+ (fo_white_par ? OPENLINE_KEEPTRAIL : 0)
+ (do_comments ? OPENLINE_DO_COM : 0)
+ ((flags & INSCHAR_COM_LIST) ? OPENLINE_COM_LIST : 0),
- ((flags & INSCHAR_COM_LIST) ? second_indent : old_indent));
+ ((flags & INSCHAR_COM_LIST) ? second_indent : old_indent),
+ &did_do_comment);
if (!(flags & INSCHAR_COM_LIST)) {
old_indent = 0;
}
+ // If a comment leader was inserted, may also do this on a following
+ // line.
+ if (did_do_comment) {
+ no_leader = false;
+ }
+
replace_offset = 0;
if (first_line) {
if (!(flags & INSCHAR_COM_LIST)) {
@@ -6814,7 +6836,7 @@ void free_last_insert(void)
/// Add character "c" to buffer "s"
///
-/// Escapes the special meaning of K_SPECIAL and CSI, handles multi-byte
+/// Escapes the special meaning of K_SPECIAL, handles multi-byte
/// characters.
///
/// @param[in] c Character to add.
@@ -6828,7 +6850,7 @@ char_u *add_char2buf(int c, char_u *s)
const int len = utf_char2bytes(c, temp);
for (int i = 0; i < len; i++) {
c = temp[i];
- // Need to escape K_SPECIAL and CSI like in the typeahead buffer.
+ // Need to escape K_SPECIAL like in the typeahead buffer.
if (c == K_SPECIAL) {
*s++ = K_SPECIAL;
*s++ = KS_SPECIAL;
@@ -6902,8 +6924,7 @@ int oneright(void)
// move "l" bytes right, but don't end up on the NUL, unless 'virtualedit'
// contains "onemore".
- if (ptr[l] == NUL
- && (ve_flags & VE_ONEMORE) == 0) {
+ if (ptr[l] == NUL && (get_ve_flags() & VE_ONEMORE) == 0) {
return FAIL;
}
curwin->w_cursor.col += l;
@@ -7110,9 +7131,7 @@ int stuff_inserted(int c, long count, int no_esc)
stuffReadbuff((const char *)ptr);
// A trailing "0" is inserted as "<C-V>048", "^" as "<C-V>^".
if (last) {
- stuffReadbuff((last == '0'
- ? "\026\060\064\070"
- : "\026^"));
+ stuffReadbuff(last == '0' ? "\026\060\064\070" : "\026^");
}
} while (--count > 0);
@@ -8025,7 +8044,7 @@ static bool ins_esc(long *count, int cmdchar, bool nomove)
&& !VIsual_active
))
&& !revins_on) {
- if (curwin->w_cursor.coladd > 0 || ve_flags == VE_ALL) {
+ if (curwin->w_cursor.coladd > 0 || get_ve_flags() == VE_ALL) {
oneleft();
if (restart_edit != NUL) {
curwin->w_cursor.coladd++;
@@ -8278,6 +8297,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
int in_indent;
int oldState;
int cpc[MAX_MCO]; // composing characters
+ bool call_fix_indent = false;
// can't delete anything in an empty file
// can't backup past first character in buffer
@@ -8421,6 +8441,8 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
beginline(BL_WHITE);
if (curwin->w_cursor.col < save_col) {
mincol = curwin->w_cursor.col;
+ // should now fix the indent to match with the previous line
+ call_fix_indent = true;
}
curwin->w_cursor.col = save_col;
}
@@ -8555,6 +8577,11 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
if (curwin->w_cursor.col <= 1) {
did_ai = false;
}
+
+ if (call_fix_indent) {
+ fix_indent();
+ }
+
// It's a little strange to put backspaces into the redo
// buffer, but it makes auto-indent a lot easier to deal
// with.
@@ -9169,7 +9196,7 @@ static bool ins_eol(int c)
AppendToRedobuff(NL_STR);
bool i = open_line(FORWARD,
has_format_option(FO_RET_COMS) ? OPENLINE_DO_COM : 0,
- old_indent);
+ old_indent, NULL);
old_indent = 0;
can_cindent = true;
// When inserting a line the cursor line must never be in a closed fold.
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 6dbdc09c3b..07f0799bc4 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -69,6 +69,7 @@ static char *e_nowhitespace
static char *e_invalwindow = N_("E957: Invalid window number");
static char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s");
static char *e_write2 = N_("E80: Error while writing: %s");
+static char *e_string_list_or_blob_required = N_("E1098: String, List or Blob required");
// TODO(ZyX-I): move to eval/executor
static char *e_letwrong = N_("E734: Wrong variable type for %s=");
@@ -112,9 +113,11 @@ typedef struct {
int fi_semicolon; // TRUE if ending in '; var]'
int fi_varcount; // nr of variables in the list
listwatch_T fi_lw; // keep an eye on the item used.
- list_T *fi_list; // list being used
+ list_T *fi_list; // list being used
int fi_bi; // index of blob
blob_T *fi_blob; // blob being used
+ char_u *fi_string; // copy of string being used
+ int fi_byte_idx; // byte index in fi_string
} forinfo_T;
// values for vv_flags:
@@ -763,6 +766,15 @@ static int eval1_emsg(char_u **arg, typval_T *rettv, bool evaluate)
return ret;
}
+/// @return whether a typval is a valid expression to pass to eval_expr_typval()
+/// or eval_expr_to_bool(). An empty string returns false;
+bool eval_expr_valid_arg(const typval_T *const tv)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_CONST
+{
+ return tv->v_type != VAR_UNKNOWN
+ && (tv->v_type != VAR_STRING || (tv->vval.v_string != NULL && *tv->vval.v_string != NUL));
+}
+
int eval_expr_typval(const typval_T *expr, typval_T *argv, int argc, typval_T *rettv)
FUNC_ATTR_NONNULL_ARG(1, 2, 4)
{
@@ -2641,8 +2653,15 @@ void *eval_for_line(const char_u *arg, bool *errp, char_u **nextcmdp, int skip)
fi->fi_blob = btv.vval.v_blob;
}
tv_clear(&tv);
+ } else if (tv.v_type == VAR_STRING) {
+ fi->fi_byte_idx = 0;
+ fi->fi_string = tv.vval.v_string;
+ tv.vval.v_string = NULL;
+ if (fi->fi_string == NULL) {
+ fi->fi_string = vim_strsave((char_u *)"");
+ }
} else {
- emsg(_(e_listblobreq));
+ emsg(_(e_string_list_or_blob_required));
tv_clear(&tv);
}
}
@@ -2679,6 +2698,22 @@ bool next_for_item(void *fi_void, char_u *arg)
fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK;
}
+ if (fi->fi_string != NULL) {
+ const int len = utfc_ptr2len(fi->fi_string + fi->fi_byte_idx);
+ if (len == 0) {
+ return false;
+ }
+ typval_T tv;
+ tv.v_type = VAR_STRING;
+ tv.v_lock = VAR_FIXED;
+ tv.vval.v_string = vim_strnsave(fi->fi_string + fi->fi_byte_idx, len);
+ fi->fi_byte_idx += len;
+ const int result
+ = ex_let_vars(arg, &tv, true, fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK;
+ xfree(tv.vval.v_string);
+ return result;
+ }
+
listitem_T *item = fi->fi_lw.lw_item;
if (item == NULL) {
return false;
@@ -2698,12 +2733,16 @@ void free_for_info(void *fi_void)
{
forinfo_T *fi = (forinfo_T *)fi_void;
- if (fi != NULL && fi->fi_list != NULL) {
+ if (fi == NULL) {
+ return;
+ }
+ if (fi->fi_list != NULL) {
tv_list_watch_remove(fi->fi_list, &fi->fi_lw);
tv_list_unref(fi->fi_list);
- }
- if (fi != NULL && fi->fi_blob != NULL) {
+ } else if (fi->fi_blob != NULL) {
tv_blob_unref(fi->fi_blob);
+ } else {
+ xfree(fi->fi_string);
}
xfree(fi);
}
@@ -3218,9 +3257,8 @@ char_u *get_user_var_name(expand_T *xp, int idx)
// b: variables
// In cmdwin, the alternative buffer should be used.
- hashtab_T *ht = (cmdwin_type != 0 && get_cmdline_type() == NUL)
- ? &prevwin->w_buffer->b_vars->dv_hashtab
- : &curbuf->b_vars->dv_hashtab;
+ hashtab_T *ht
+ = is_in_cmdwin() ? &prevwin->w_buffer->b_vars->dv_hashtab : &curbuf->b_vars->dv_hashtab;
if (bdone < ht->ht_used) {
if (bdone++ == 0) {
hi = ht->ht_array;
@@ -3235,9 +3273,7 @@ char_u *get_user_var_name(expand_T *xp, int idx)
// w: variables
// In cmdwin, the alternative window should be used.
- ht = (cmdwin_type != 0 && get_cmdline_type() == NUL)
- ? &prevwin->w_vars->dv_hashtab
- : &curwin->w_vars->dv_hashtab;
+ ht = is_in_cmdwin() ? &prevwin->w_vars->dv_hashtab : &curwin->w_vars->dv_hashtab;
if (wdone < ht->ht_used) {
if (wdone++ == 0) {
hi = ht->ht_array;
@@ -4402,7 +4438,7 @@ static int eval_lambda(char_u **const arg, typval_T *const rettv, const bool eva
rettv->v_type = VAR_UNKNOWN;
int ret = get_lambda_tv(arg, rettv, evaluate);
- if (ret == NOTDONE) {
+ if (ret != OK) {
return FAIL;
} else if (**arg != '(') {
if (verbose) {
@@ -6468,6 +6504,10 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
if (argvars[0].v_type == VAR_DICT) {
vimvars[VV_KEY].vv_type = VAR_STRING;
+ const VarLockStatus prev_lock = d->dv_lock;
+ if (map && d->dv_lock == VAR_UNLOCKED) {
+ d->dv_lock = VAR_LOCKED;
+ }
ht = &d->dv_hashtab;
hash_lock(ht);
todo = (int)ht->ht_used;
@@ -6498,6 +6538,7 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
}
}
hash_unlock(ht);
+ d->dv_lock = prev_lock;
} else if (argvars[0].v_type == VAR_BLOB) {
vimvars[VV_KEY].vv_type = VAR_NUMBER;
@@ -6530,6 +6571,10 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
assert(argvars[0].v_type == VAR_LIST);
vimvars[VV_KEY].vv_type = VAR_NUMBER;
+ const VarLockStatus prev_lock = tv_list_locked(l);
+ if (map && tv_list_locked(l) == VAR_UNLOCKED) {
+ tv_list_set_lock(l, VAR_LOCKED);
+ }
for (listitem_T *li = tv_list_first(l); li != NULL;) {
if (map
&& var_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg,
@@ -6548,6 +6593,7 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
}
idx++;
}
+ tv_list_set_lock(l, prev_lock);
}
restore_vimvar(VV_KEY, &save_key);
@@ -6956,10 +7002,9 @@ win_T *find_tabwin(typval_T *wvp, typval_T *tvp)
/// @param off 1 for gettabwinvar()
void getwinvar(typval_T *argvars, typval_T *rettv, int off)
{
- win_T *win, *oldcurwin;
+ win_T *win;
dictitem_T *v;
tabpage_T *tp = NULL;
- tabpage_T *oldtabpage = NULL;
bool done = false;
if (off == 1) {
@@ -6979,8 +7024,8 @@ void getwinvar(typval_T *argvars, typval_T *rettv, int off)
// otherwise the window is not valid. Only do this when needed,
// autocommands get blocked.
bool need_switch_win = tp != curtab || win != curwin;
- if (!need_switch_win
- || switch_win(&oldcurwin, &oldtabpage, win, tp, true) == OK) {
+ switchwin_T switchwin;
+ if (!need_switch_win || switch_win(&switchwin, win, tp, true) == OK) {
if (*varname == '&') {
if (varname[1] == NUL) {
// get all window-local options in a dict
@@ -7008,7 +7053,7 @@ void getwinvar(typval_T *argvars, typval_T *rettv, int off)
if (need_switch_win) {
// restore previous notion of curwin
- restore_win(oldcurwin, oldtabpage, true);
+ restore_win(&switchwin, true);
}
}
emsg_off--;
@@ -7510,11 +7555,9 @@ void setwinvar(typval_T *argvars, typval_T *rettv, int off)
typval_T *varp = &argvars[off + 2];
if (win != NULL && varname != NULL && varp != NULL) {
- win_T *save_curwin;
- tabpage_T *save_curtab;
bool need_switch_win = tp != curtab || win != curwin;
- if (!need_switch_win
- || switch_win(&save_curwin, &save_curtab, win, tp, true) == OK) {
+ switchwin_T switchwin;
+ if (!need_switch_win || switch_win(&switchwin, win, tp, true) == OK) {
if (*varname == '&') {
long numval;
bool error = false;
@@ -7536,7 +7579,7 @@ void setwinvar(typval_T *argvars, typval_T *rettv, int off)
}
}
if (need_switch_win) {
- restore_win(save_curwin, save_curtab, true);
+ restore_win(&switchwin, true);
}
}
}
@@ -8147,6 +8190,66 @@ char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl)
return ret;
}
+/// Convert the specified byte index of line 'lnum' in buffer 'buf' to a
+/// character index. Works only for loaded buffers. Returns -1 on failure.
+/// The index of the first byte and the first character is zero.
+int buf_byteidx_to_charidx(buf_T *buf, int lnum, int byteidx)
+{
+ if (buf == NULL || buf->b_ml.ml_mfp == NULL) {
+ return -1;
+ }
+
+ if (lnum > buf->b_ml.ml_line_count) {
+ lnum = buf->b_ml.ml_line_count;
+ }
+
+ char_u *str = ml_get_buf(buf, lnum, false);
+
+ if (*str == NUL) {
+ return 0;
+ }
+
+ // count the number of characters
+ char_u *t = str;
+ int count;
+ for (count = 0; *t != NUL && t <= str + byteidx; count++) {
+ t += utfc_ptr2len(t);
+ }
+
+ // In insert mode, when the cursor is at the end of a non-empty line,
+ // byteidx points to the NUL character immediately past the end of the
+ // string. In this case, add one to the character count.
+ if (*t == NUL && byteidx != 0 && t == str + byteidx) {
+ count++;
+ }
+
+ return count - 1;
+}
+
+/// Convert the specified character index of line 'lnum' in buffer 'buf' to a
+/// byte index. Works only for loaded buffers. Returns -1 on failure.
+/// The index of the first byte and the first character is zero.
+int buf_charidx_to_byteidx(buf_T *buf, int lnum, int charidx)
+{
+ if (buf == NULL || buf->b_ml.ml_mfp == NULL) {
+ return -1;
+ }
+
+ if (lnum > buf->b_ml.ml_line_count) {
+ lnum = buf->b_ml.ml_line_count;
+ }
+
+ char_u *str = ml_get_buf(buf, lnum, false);
+
+ // Convert the character offset to a byte offset
+ char_u *t = str;
+ while (*t != NUL && --charidx > 0) {
+ t += utfc_ptr2len(t);
+ }
+
+ return t - str;
+}
+
/// Translate a VimL object into a position
///
/// Accepts VAR_LIST and VAR_STRING objects. Does not give an error for invalid
@@ -8155,9 +8258,11 @@ char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl)
/// @param[in] tv Object to translate.
/// @param[in] dollar_lnum True when "$" is last line.
/// @param[out] ret_fnum Set to fnum for marks.
+/// @param[in] charcol True to return character column.
///
/// @return Pointer to position or NULL in case of error (e.g. invalid type).
-pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret_fnum)
+pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret_fnum,
+ const bool charcol)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
static pos_T pos;
@@ -8187,7 +8292,11 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
if (error) {
return NULL;
}
- len = (long)STRLEN(ml_get(pos.lnum));
+ if (charcol) {
+ len = mb_charlen(ml_get(pos.lnum));
+ } else {
+ len = STRLEN(ml_get(pos.lnum));
+ }
// We accept "$" for the column number: last column.
li = tv_list_find(l, 1L);
@@ -8218,19 +8327,31 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
return NULL;
}
if (name[0] == '.') { // Cursor.
- return &curwin->w_cursor;
+ pos = curwin->w_cursor;
+ if (charcol) {
+ pos.col = buf_byteidx_to_charidx(curbuf, pos.lnum, pos.col);
+ }
+ return &pos;
}
if (name[0] == 'v' && name[1] == NUL) { // Visual start.
if (VIsual_active) {
- return &VIsual;
+ pos = VIsual;
+ } else {
+ pos = curwin->w_cursor;
+ }
+ if (charcol) {
+ pos.col = buf_byteidx_to_charidx(curbuf, pos.lnum, pos.col);
}
- return &curwin->w_cursor;
+ return &pos;
}
if (name[0] == '\'') { // Mark.
pp = getmark_buf_fnum(curbuf, (uint8_t)name[1], false, ret_fnum);
if (pp == NULL || pp == (pos_T *)-1 || pp->lnum <= 0) {
return NULL;
}
+ if (charcol) {
+ pp->col = buf_byteidx_to_charidx(curbuf, pp->lnum, pp->col);
+ }
return pp;
}
@@ -8256,22 +8377,24 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
pos.col = 0;
} else {
pos.lnum = curwin->w_cursor.lnum;
- pos.col = (colnr_T)STRLEN(get_cursor_line_ptr());
+ if (charcol) {
+ pos.col = (colnr_T)mb_charlen(get_cursor_line_ptr());
+ } else {
+ pos.col = (colnr_T)STRLEN(get_cursor_line_ptr());
+ }
}
return &pos;
}
return NULL;
}
-/*
- * Convert list in "arg" into a position and optional file number.
- * When "fnump" is NULL there is no file number, only 3 items.
- * Note that the column is passed on as-is, the caller may want to decrement
- * it to use 1 for the first column.
- * Return FAIL when conversion is not possible, doesn't check the position for
- * validity.
- */
-int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp)
+/// Convert list in "arg" into a position and optional file number.
+/// When "fnump" is NULL there is no file number, only 3 items.
+/// Note that the column is passed on as-is, the caller may want to decrement
+/// it to use 1 for the first column.
+/// Return FAIL when conversion is not possible, doesn't check the position for
+/// validity.
+int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, bool charcol)
{
list_T *l;
long i = 0;
@@ -8307,6 +8430,15 @@ int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp)
if (n < 0) {
return FAIL;
}
+ // If character position is specified, then convert to byte position
+ if (charcol) {
+ // Get the text for the specified line in a loaded buffer
+ buf_T *buf = buflist_findnr(fnump == NULL ? curbuf->b_fnum : *fnump);
+ if (buf == NULL || buf->b_ml.ml_mfp == NULL) {
+ return FAIL;
+ }
+ n = buf_charidx_to_byteidx(buf, posp->lnum, n) + 1;
+ }
posp->col = n;
n = tv_list_find_nr(l, i, NULL); // off
@@ -8946,7 +9078,7 @@ static bool tv_is_luafunc(typval_T *tv)
const char *skip_luafunc_name(const char *p)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.' || *p == '\'') {
+ while (ASCII_ISALNUM(*p) || *p == '_' || *p == '-' || *p == '.' || *p == '\'') {
p++;
}
return p;
@@ -10515,12 +10647,13 @@ int modify_fname(char_u *src, bool tilde_file, size_t *usedlen, char_u **fnamep,
char_u *s, *p, *pbuf;
char_u dirname[MAXPATHL];
int c;
- int has_fullname = 0;
+ bool has_fullname = false;
+ bool has_homerelative = false;
repeat:
// ":p" - full path/file_name
if (src[*usedlen] == ':' && src[*usedlen + 1] == 'p') {
- has_fullname = 1;
+ has_fullname = true;
valid |= VALID_PATH;
*usedlen += 2;
@@ -10589,8 +10722,8 @@ repeat:
}
pbuf = NULL;
// Need full path first (use expand_env() to remove a "~/")
- if (!has_fullname) {
- if (c == '.' && **fnamep == '~') {
+ if (!has_fullname && !has_homerelative) {
+ if (**fnamep == '~') {
p = pbuf = expand_env_save(*fnamep);
} else {
p = pbuf = (char_u *)FullName_save((char *)*fnamep, FALSE);
@@ -10599,18 +10732,33 @@ repeat:
p = *fnamep;
}
- has_fullname = 0;
+ has_fullname = false;
if (p != NULL) {
if (c == '.') {
os_dirname(dirname, MAXPATHL);
- s = path_shorten_fname(p, dirname);
- if (s != NULL) {
- *fnamep = s;
- if (pbuf != NULL) {
- xfree(*bufp); // free any allocated file name
- *bufp = pbuf;
- pbuf = NULL;
+ if (has_homerelative) {
+ s = vim_strsave(dirname);
+ home_replace(NULL, s, dirname, MAXPATHL, true);
+ xfree(s);
+ }
+ size_t namelen = STRLEN(dirname);
+
+ // Do not call shorten_fname() here since it removes the prefix
+ // even though the path does not have a prefix.
+ if (fnamencmp(p, dirname, namelen) == 0) {
+ p += namelen;
+ if (vim_ispathsep(*p)) {
+ while (*p && vim_ispathsep(*p)) {
+ p++;
+ }
+ *fnamep = p;
+ if (pbuf != NULL) {
+ // free any allocated file name
+ xfree(*bufp);
+ *bufp = pbuf;
+ pbuf = NULL;
+ }
}
}
} else {
@@ -10621,6 +10769,7 @@ repeat:
*fnamep = s;
xfree(*bufp);
*bufp = s;
+ has_homerelative = true;
}
}
xfree(pbuf);
@@ -10997,10 +11146,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments, boo
bool eval_has_provider(const char *feat)
{
if (!strequal(feat, "clipboard")
- && !strequal(feat, "python")
&& !strequal(feat, "python3")
- && !strequal(feat, "python_compiled")
- && !strequal(feat, "python_dynamic")
&& !strequal(feat, "python3_compiled")
&& !strequal(feat, "python3_dynamic")
&& !strequal(feat, "perl")
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index e445a08227..05e91a658f 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -71,6 +71,7 @@ return {
chanclose={args={1, 2}},
chansend={args=2},
char2nr={args={1, 2}, base=1},
+ charcol={args=1, base=1},
charidx={args={2, 3}, base=1},
chdir={args=1, base=1},
cindent={args=1, base=1},
@@ -144,6 +145,7 @@ return {
getchangelist={args={0, 1}, base=1},
getchar={args={0, 1}},
getcharmod={},
+ getcharpos={args=1, base=1},
getcharsearch={},
getcharstr={args={0, 1}},
getcmdline={},
@@ -151,7 +153,8 @@ return {
getcmdtype={},
getcmdwintype={},
getcompletion={args={2, 3}, base=1},
- getcurpos={},
+ getcurpos={args={0, 1}, base=1},
+ getcursorcharpos={args={0, 1}, base=1},
getcwd={args={0, 2}, base=1},
getenv={args=1, base=1},
getfontname={args={0, 1}},
@@ -246,6 +249,8 @@ return {
matcharg={args=1, base=1},
matchdelete={args={1, 2}, base=1},
matchend={args={2, 4}, base=1},
+ matchfuzzy={args={2, 3}, base=1},
+ matchfuzzypos={args={2, 3}, base=1},
matchlist={args={2, 4}, base=1},
matchstr={args={2, 4}, base=1},
matchstrpos={args={2,4}, base=1},
@@ -259,7 +264,7 @@ return {
nextnonblank={args=1, base=1},
nr2char={args={1, 2}, base=1},
['or']={args=2, base=1},
- pathshorten={args=1, base=1},
+ pathshorten={args={1, 2}, base=1},
pow={args=2, base=1},
prevnonblank={args=1, base=1},
printf={args=varargs(1), base=2},
@@ -270,12 +275,14 @@ return {
pum_getpos={},
pumvisible={},
py3eval={args=1, base=1},
- pyeval={args=1, base=1},
- pyxeval={args=1, base=1},
+ pyeval={args=1, base=1, func="f_py3eval"},
+ pyxeval={args=1, base=1, func="f_py3eval"},
perleval={args=1, base=1},
+ rand={args={0, 1}, base=1},
range={args={1, 3}, base=1},
readdir={args={1, 2}, base=1},
readfile={args={1, 3}, base=1},
+ reduce={args={2, 3}, base=1},
reg_executing={},
reg_recording={},
reg_recorded={},
@@ -300,19 +307,21 @@ return {
screenpos={args=3, base=1},
screenrow={},
screenstring={args=2, base=1},
- search={args={1, 4}, base=1},
+ search={args={1, 5}, base=1},
searchcount={args={0, 1}, base=1},
searchdecl={args={1, 3}, base=1},
searchpair={args={3, 7}},
searchpairpos={args={3, 7}},
- searchpos={args={1, 4}, base=1},
+ searchpos={args={1, 5}, base=1},
serverlist={},
serverstart={args={0, 1}},
serverstop={args=1},
setbufline={args=3, base=3},
setbufvar={args=3, base=3},
+ setcharpos={args=2, base=2},
setcharsearch={args=1, base=1},
setcmdpos={args=1, base=1},
+ setcursorcharpos={args={1, 3}, base=1},
setenv={args=2, base=2},
setfperm={args=2, base=1},
setline={args=2, base=2},
@@ -348,6 +357,7 @@ return {
spellsuggest={args={1, 3}, base=1},
split={args={1, 3}, base=1},
sqrt={args=1, base=1, func="float_op_wrapper", data="&sqrt"},
+ srand={args={0, 1}, base=1},
stdpath={args=1},
str2float={args=1, base=1},
str2list={args={1, 2}, base=1},
@@ -413,6 +423,8 @@ return {
win_gotoid={args=1, base=1},
win_id2tabwin={args=1, base=1},
win_id2win={args=1, base=1},
+ win_move_separator={args=2, base=1},
+ win_move_statusline={args=2, base=1},
win_screenpos={args=1, base=1},
win_splitmove={args={2, 3}, base=1},
winbufnr={args=1, base=1},
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 7701688b49..7772d9ffc2 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -22,6 +22,7 @@
#include "nvim/eval/encode.h"
#include "nvim/eval/executor.h"
#include "nvim/eval/funcs.h"
+#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
@@ -97,9 +98,9 @@ PRAGMA_DIAG_POP
#endif
-static char *e_listarg = N_("E686: Argument of %s must be a List");
static char *e_listblobarg = N_("E899: Argument of %s must be a List or Blob");
static char *e_invalwindow = N_("E957: Invalid window number");
+static char *e_reduceempty = N_("E998: Reduce of an empty %s with no initial value");
/// Dummy va_list for passing to vim_snprintf
///
@@ -1019,6 +1020,49 @@ static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = utf_ptr2char((const char_u *)tv_get_string(&argvars[0]));
}
+/// Get the current cursor column and store it in 'rettv'. If 'charcol' is true,
+/// returns the character index of the column. Otherwise, returns the byte index
+/// of the column.
+static void get_col(typval_T *argvars, typval_T *rettv, bool charcol)
+{
+ colnr_T col = 0;
+ pos_T *fp;
+ int fnum = curbuf->b_fnum;
+
+ fp = var2fpos(&argvars[0], false, &fnum, charcol);
+ if (fp != NULL && fnum == curbuf->b_fnum) {
+ if (fp->col == MAXCOL) {
+ // '> can be MAXCOL, get the length of the line then
+ if (fp->lnum <= curbuf->b_ml.ml_line_count) {
+ col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1;
+ } else {
+ col = MAXCOL;
+ }
+ } else {
+ col = fp->col + 1;
+ // col(".") when the cursor is on the NUL at the end of the line
+ // because of "coladd" can be seen as an extra column.
+ if (virtual_active() && fp == &curwin->w_cursor) {
+ char_u *p = get_cursor_pos_ptr();
+ if (curwin->w_cursor.coladd >=
+ (colnr_T)win_chartabsize(curwin, p, curwin->w_virtcol - curwin->w_cursor.coladd)) {
+ int l;
+ if (*p != NUL && p[(l = utfc_ptr2len(p))] == NUL) {
+ col += l;
+ }
+ }
+ }
+ }
+ }
+ rettv->vval.v_number = col;
+}
+
+/// "charcol()" function
+static void f_charcol(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ get_col(argvars, rettv, true);
+}
+
// "charidx()" function
static void f_charidx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
@@ -1147,45 +1191,10 @@ static void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
-/*
- * "col(string)" function
- */
+/// "col(string)" function
static void f_col(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- colnr_T col = 0;
- pos_T *fp;
- int fnum = curbuf->b_fnum;
-
- fp = var2fpos(&argvars[0], FALSE, &fnum);
- if (fp != NULL && fnum == curbuf->b_fnum) {
- if (fp->col == MAXCOL) {
- // '> can be MAXCOL, get the length of the line then
- if (fp->lnum <= curbuf->b_ml.ml_line_count) {
- col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1;
- } else {
- col = MAXCOL;
- }
- } else {
- col = fp->col + 1;
- // col(".") when the cursor is on the NUL at the end of the line
- // because of "coladd" can be seen as an extra column.
- if (virtual_active() && fp == &curwin->w_cursor) {
- char_u *p = get_cursor_pos_ptr();
-
- if (curwin->w_cursor.coladd
- >= (colnr_T)win_chartabsize(curwin, p,
- (curwin->w_virtcol
- - curwin->w_cursor.coladd))) {
- int l;
-
- if (*p != NUL && p[(l = utfc_ptr2len(p))] == NUL) {
- col += l;
- }
- }
- }
- }
- }
- rettv->vval.v_number = col;
+ get_col(argvars, rettv, false);
}
/*
@@ -1548,24 +1557,21 @@ static void f_ctxsize(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = ctx_size();
}
-/// "cursor(lnum, col)" function, or
-/// "cursor(list)"
-///
-/// Moves the cursor to the specified line and column.
-///
-/// @returns 0 when the position could be set, -1 otherwise.
-static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+/// Set the cursor position.
+/// If 'charcol' is true, then use the column number as a character offet.
+/// Otherwise use the column number as a byte offset.
+static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol)
{
long line, col;
long coladd = 0;
bool set_curswant = true;
rettv->vval.v_number = -1;
- if (argvars[1].v_type == VAR_UNKNOWN) {
+ if (argvars[0].v_type == VAR_LIST) {
pos_T pos;
colnr_T curswant = -1;
- if (list2fpos(argvars, &pos, NULL, &curswant) == FAIL) {
+ if (list2fpos(argvars, &pos, NULL, &curswant, charcol) == FAIL) {
emsg(_(e_invarg));
return;
}
@@ -1577,16 +1583,22 @@ static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr)
curwin->w_curswant = curswant - 1;
set_curswant = false;
}
- } else {
+ } else if ((argvars[0].v_type == VAR_NUMBER || argvars[0].v_type == VAR_STRING)
+ && (argvars[1].v_type == VAR_NUMBER || argvars[1].v_type == VAR_STRING)) {
line = tv_get_lnum(argvars);
col = (long)tv_get_number_chk(&argvars[1], NULL);
+ if (charcol) {
+ col = buf_charidx_to_byteidx(curbuf, line, col) + 1;
+ }
if (argvars[2].v_type != VAR_UNKNOWN) {
coladd = (long)tv_get_number_chk(&argvars[2], NULL);
}
+ } else {
+ emsg(_(e_invarg));
+ return;
}
- if (line < 0 || col < 0
- || coladd < 0) {
- return; // type error; errmsg already given
+ if (line < 0 || col < 0 || coladd < 0) {
+ return; // type error; errmsg already given
}
if (line > 0) {
curwin->w_cursor.lnum = line;
@@ -1605,6 +1617,16 @@ static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = 0;
}
+/// "cursor(lnum, col)" function, or
+/// "cursor(list)"
+///
+/// Moves the cursor to the specified line and column.
+/// Returns 0 when the position could be set, -1 otherwise.
+static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ set_cursorpos(argvars, rettv, false);
+}
+
// "debugbreak()" function
static void f_debugbreak(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
@@ -2176,25 +2198,12 @@ static void f_win_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
tabpage_T *tp;
win_T *wp = win_id2wp_tp(argvars, &tp);
- win_T *save_curwin;
- tabpage_T *save_curtab;
// Return an empty string if something fails.
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL;
if (wp != NULL && tp != NULL) {
- pos_T curpos = wp->w_cursor;
- if (switch_win_noblock(&save_curwin, &save_curtab, wp, tp, true) ==
- OK) {
- check_cursor();
- execute_common(argvars, rettv, fptr, 1);
- }
- restore_win_noblock(save_curwin, save_curtab, true);
-
- // Update the status line if the cursor moved.
- if (win_valid(wp) && !equalpos(curpos, wp->w_cursor)) {
- wp->w_redr_status = true;
- }
+ WIN_EXECUTE(wp, tp, execute_common(argvars, rettv, fptr, 1));
}
}
@@ -3180,7 +3189,7 @@ static void getchar_common(typval_T *argvars, typval_T *rettv)
if (argvars[0].v_type == VAR_UNKNOWN) {
// getchar(): blocking wait.
// TODO(bfredl): deduplicate shared logic with state_enter ?
- if (!(char_avail() || using_script() || input_available())) {
+ if (!char_avail()) {
(void)os_inchar(NULL, 0, -1, 0, main_loop.events);
if (!multiqueue_empty(main_loop.events)) {
state_handle_k_event();
@@ -3300,6 +3309,67 @@ static void f_getcharmod(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = mod_mask;
}
+static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos, bool charcol)
+{
+ pos_T *fp = NULL;
+ pos_T pos;
+ win_T *wp = curwin;
+ int fnum = -1;
+
+ if (getcurpos) {
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp != NULL) {
+ fp = &wp->w_cursor;
+ }
+ } else {
+ fp = &curwin->w_cursor;
+ }
+ if (fp != NULL && charcol) {
+ pos = *fp;
+ pos.col = buf_byteidx_to_charidx(wp->w_buffer, pos.lnum, pos.col);
+ fp = &pos;
+ }
+ } else {
+ fp = var2fpos(&argvars[0], true, &fnum, charcol);
+ }
+
+ list_T *const l = tv_list_alloc_ret(rettv, 4 + getcurpos);
+ tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0);
+ tv_list_append_number(l, ((fp != NULL) ? (varnumber_T)fp->lnum : (varnumber_T)0));
+ tv_list_append_number(l, ((fp != NULL)
+ ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1)
+ : (varnumber_T)0));
+ tv_list_append_number(l, (fp != NULL) ? (varnumber_T)fp->coladd : (varnumber_T)0);
+ if (getcurpos) {
+ const int save_set_curswant = curwin->w_set_curswant;
+ const colnr_T save_curswant = curwin->w_curswant;
+ const colnr_T save_virtcol = curwin->w_virtcol;
+
+ if (wp == curwin) {
+ update_curswant();
+ }
+ tv_list_append_number(l, (wp == NULL) ? 0 : ((wp->w_curswant == MAXCOL)
+ ? (varnumber_T)MAXCOL
+ : (varnumber_T)wp->w_curswant + 1));
+
+ // Do not change "curswant", as it is unexpected that a get
+ // function has a side effect.
+ if (wp == curwin && save_set_curswant) {
+ curwin->w_set_curswant = save_set_curswant;
+ curwin->w_curswant = save_curswant;
+ curwin->w_virtcol = save_virtcol;
+ curwin->w_valid &= ~VALID_VIRTCOL;
+ }
+ }
+}
+
+/// "getcharpos()" function
+static void f_getcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ getpos_both(argvars, rettv, false, true);
+}
+
/*
* "getcharsearch()" function
*/
@@ -3795,7 +3865,7 @@ static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
// "getmousepos()" function
-void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+static void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
dict_T *d;
win_T *wp;
@@ -3855,61 +3925,21 @@ static void f_getpid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = os_get_pid();
}
-static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos)
+/// "getcurpos(string)" function
+static void f_getcurpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- pos_T *fp;
- int fnum = -1;
-
- if (getcurpos) {
- fp = &curwin->w_cursor;
- } else {
- fp = var2fpos(&argvars[0], true, &fnum);
- }
-
- list_T *const l = tv_list_alloc_ret(rettv, 4 + (!!getcurpos));
- tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0);
- tv_list_append_number(l, ((fp != NULL)
- ? (varnumber_T)fp->lnum
- : (varnumber_T)0));
- tv_list_append_number(l, ((fp != NULL)
- ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1)
- : (varnumber_T)0));
- tv_list_append_number(l, (fp != NULL) ? (varnumber_T)fp->coladd : (varnumber_T)0);
- if (getcurpos) {
- const int save_set_curswant = curwin->w_set_curswant;
- const colnr_T save_curswant = curwin->w_curswant;
- const colnr_T save_virtcol = curwin->w_virtcol;
-
- update_curswant();
- tv_list_append_number(l, (curwin->w_curswant == MAXCOL
- ? (varnumber_T)MAXCOL
- : (varnumber_T)curwin->w_curswant + 1));
-
- // Do not change "curswant", as it is unexpected that a get
- // function has a side effect.
- if (save_set_curswant) {
- curwin->w_set_curswant = save_set_curswant;
- curwin->w_curswant = save_curswant;
- curwin->w_virtcol = save_virtcol;
- curwin->w_valid &= ~VALID_VIRTCOL;
- }
- }
+ getpos_both(argvars, rettv, true, false);
}
-/*
- * "getcurpos(string)" function
- */
-static void f_getcurpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+static void f_getcursorcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- getpos_both(argvars, rettv, true);
+ getpos_both(argvars, rettv, true, true);
}
-/*
- * "getpos(string)" function
- */
+/// "getpos(string)" function
static void f_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- getpos_both(argvars, rettv, false);
+ getpos_both(argvars, rettv, false, false);
}
/// "getqflist()" functions
@@ -4031,8 +4061,6 @@ static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)
*/
static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- win_T *oldcurwin;
- tabpage_T *oldtabpage;
bool done = false;
rettv->v_type = VAR_STRING;
@@ -4046,7 +4074,8 @@ static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
win_T *const window = tp == curtab || tp->tp_firstwin == NULL
? firstwin
: tp->tp_firstwin;
- if (switch_win(&oldcurwin, &oldtabpage, window, tp, true) == OK) {
+ switchwin_T switchwin;
+ if (switch_win(&switchwin, window, tp, true) == OK) {
// look up the variable
// Let gettabvar({nr}, "") return the "t:" dictionary.
const dictitem_T *const v = find_var_in_ht(&tp->tp_vars->dv_hashtab, 't',
@@ -4059,7 +4088,7 @@ static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
// restore previous notion of curwin
- restore_win(oldcurwin, oldtabpage, true);
+ restore_win(&switchwin, true);
}
if (!done && argvars[2].v_type != VAR_UNKNOWN) {
@@ -4433,6 +4462,12 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)
#if defined(BSD) && !defined(__APPLE__)
"bsd",
#endif
+#ifdef __linux__
+ "linux",
+#endif
+#ifdef SUN_SYSTEM
+ "sun",
+#endif
#ifdef UNIX
"unix",
#endif
@@ -5881,22 +5916,20 @@ static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (argvars[1].v_type != VAR_UNKNOWN) {
tabpage_T *tp;
- win_T *save_curwin;
- tabpage_T *save_curtab;
// use window specified in the second argument
win_T *wp = win_id2wp_tp(&argvars[1], &tp);
if (wp != NULL && tp != NULL) {
- if (switch_win_noblock(&save_curwin, &save_curtab, wp, tp, true)
- == OK) {
+ switchwin_T switchwin;
+ if (switch_win_noblock(&switchwin, wp, tp, true) == OK) {
check_cursor();
- fp = var2fpos(&argvars[0], true, &fnum);
+ fp = var2fpos(&argvars[0], true, &fnum, false);
}
- restore_win_noblock(save_curwin, save_curtab, true);
+ restore_win_noblock(&switchwin, true);
}
} else {
// use current window
- fp = var2fpos(&argvars[0], true, &fnum);
+ fp = var2fpos(&argvars[0], true, &fnum, false);
}
if (fp != NULL) {
@@ -6813,12 +6846,23 @@ static void f_or(typval_T *argvars, typval_T *rettv, FunPtr fptr)
*/
static void f_pathshorten(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
+ int trim_len = 1;
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ trim_len = (int)tv_get_number(&argvars[1]);
+ if (trim_len < 1) {
+ trim_len = 1;
+ }
+ }
+
rettv->v_type = VAR_STRING;
- const char *const s = tv_get_string_chk(&argvars[0]);
- if (!s) {
- return;
+ const char_u *p = (char_u *)tv_get_string_chk(&argvars[0]);
+ if (p == NULL) {
+ rettv->vval.v_string = NULL;
+ } else {
+ rettv->vval.v_string = vim_strsave(p);
+ shorten_dir_len(rettv->vval.v_string, trim_len);
}
- rettv->vval.v_string = shorten_dir((char_u *)xstrdup(s));
}
/*
@@ -6927,7 +6971,7 @@ static void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, FunPtr fpt
}
/// "prompt_getprompt({buffer})" function
-void f_prompt_getprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+static void f_prompt_getprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr)
FUNC_ATTR_NONNULL_ALL
{
// return an empty string by default, e.g. it's not a prompt buffer
@@ -6982,36 +7026,169 @@ static void f_pumvisible(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
-/*
- * "pyeval()" function
- */
-static void f_pyeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+/// "py3eval()" and "pyxeval()" functions (always python3)
+static void f_py3eval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- script_host_eval("python", argvars, rettv);
+ script_host_eval("python3", argvars, rettv);
}
-/*
- * "py3eval()" function
- */
-static void f_py3eval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+static void init_srand(uint32_t *const x)
+ FUNC_ATTR_NONNULL_ALL
{
- script_host_eval("python3", argvars, rettv);
+#ifndef MSWIN
+ static int dev_urandom_state = NOTDONE; // FAIL or OK once tried
+
+ if (dev_urandom_state != FAIL) {
+ const int fd = os_open("/dev/urandom", O_RDONLY, 0);
+ struct {
+ union {
+ uint32_t number;
+ char bytes[sizeof(uint32_t)];
+ } contents;
+ } buf;
+
+ // Attempt reading /dev/urandom.
+ if (fd == -1) {
+ dev_urandom_state = FAIL;
+ } else {
+ buf.contents.number = 0;
+ if (read(fd, buf.contents.bytes, sizeof(uint32_t)) != sizeof(uint32_t)) {
+ dev_urandom_state = FAIL;
+ } else {
+ dev_urandom_state = OK;
+ *x = buf.contents.number;
+ }
+ os_close(fd);
+ }
+ }
+ if (dev_urandom_state != OK) {
+ // Reading /dev/urandom doesn't work, fall back to time().
+#endif
+ *x = time(NULL);
+#ifndef MSWIN
+ }
+#endif
+}
+
+static inline uint32_t splitmix32(uint32_t *const x)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE
+{
+ uint32_t z = (*x += 0x9e3779b9);
+ z = (z ^ (z >> 16)) * 0x85ebca6b;
+ z = (z ^ (z >> 13)) * 0xc2b2ae35;
+ return z ^ (z >> 16);
+}
+
+static inline uint32_t shuffle_xoshiro128starstar(uint32_t *const x, uint32_t *const y,
+ uint32_t *const z, uint32_t *const w)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE
+{
+#define ROTL(x, k) ((x << k) | (x >> (32 - k)))
+ const uint32_t result = ROTL(*y * 5, 7) * 9;
+ const uint32_t t = *y << 9;
+ *z ^= *x;
+ *w ^= *y;
+ *y ^= *z;
+ *x ^= *w;
+ *z ^= t;
+ *w = ROTL(*w, 11);
+#undef ROTL
+ return result;
+}
+
+/// "rand()" function
+static void f_rand(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ uint32_t result;
+
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ static uint32_t gx, gy, gz, gw;
+ static bool initialized = false;
+
+ // When no argument is given use the global seed list.
+ if (!initialized) {
+ // Initialize the global seed list.
+ uint32_t x;
+ init_srand(&x);
+
+ gx = splitmix32(&x);
+ gy = splitmix32(&x);
+ gz = splitmix32(&x);
+ gw = splitmix32(&x);
+ initialized = true;
+ }
+
+ result = shuffle_xoshiro128starstar(&gx, &gy, &gz, &gw);
+ } else if (argvars[0].v_type == VAR_LIST) {
+ list_T *const l = argvars[0].vval.v_list;
+ if (tv_list_len(l) != 4) {
+ goto theend;
+ }
+
+ typval_T *const tvx = TV_LIST_ITEM_TV(tv_list_find(l, 0L));
+ typval_T *const tvy = TV_LIST_ITEM_TV(tv_list_find(l, 1L));
+ typval_T *const tvz = TV_LIST_ITEM_TV(tv_list_find(l, 2L));
+ typval_T *const tvw = TV_LIST_ITEM_TV(tv_list_find(l, 3L));
+ if (tvx->v_type != VAR_NUMBER) {
+ goto theend;
+ }
+ if (tvy->v_type != VAR_NUMBER) {
+ goto theend;
+ }
+ if (tvz->v_type != VAR_NUMBER) {
+ goto theend;
+ }
+ if (tvw->v_type != VAR_NUMBER) {
+ goto theend;
+ }
+ uint32_t x = tvx->vval.v_number;
+ uint32_t y = tvy->vval.v_number;
+ uint32_t z = tvz->vval.v_number;
+ uint32_t w = tvw->vval.v_number;
+
+ result = shuffle_xoshiro128starstar(&x, &y, &z, &w);
+
+ tvx->vval.v_number = (varnumber_T)x;
+ tvy->vval.v_number = (varnumber_T)y;
+ tvz->vval.v_number = (varnumber_T)z;
+ tvw->vval.v_number = (varnumber_T)w;
+ } else {
+ goto theend;
+ }
+
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = (varnumber_T)result;
+ return;
+
+theend:
+ semsg(_(e_invarg2), tv_get_string(&argvars[0]));
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = -1;
}
-// "pyxeval()" function
-static void f_pyxeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+/// "srand()" function
+static void f_srand(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- init_pyxversion();
- if (p_pyx == 2) {
- f_pyeval(argvars, rettv, NULL);
+ uint32_t x = 0;
+
+ tv_list_alloc_ret(rettv, 4);
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ init_srand(&x);
} else {
- f_py3eval(argvars, rettv, NULL);
+ bool error = false;
+ x = tv_get_number_chk(&argvars[0], &error);
+ if (error) {
+ return;
+ }
}
+
+ tv_list_append_number(rettv->vval.v_list, (varnumber_T)splitmix32(&x));
+ tv_list_append_number(rettv->vval.v_list, (varnumber_T)splitmix32(&x));
+ tv_list_append_number(rettv->vval.v_list, (varnumber_T)splitmix32(&x));
+ tv_list_append_number(rettv->vval.v_list, (varnumber_T)splitmix32(&x));
}
-///
/// "perleval()" function
-///
static void f_perleval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
script_host_eval("perl", argvars, rettv);
@@ -7878,6 +8055,102 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+/// "reduce(list, { accumlator, element -> value } [, initial])" function
+static void f_reduce(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) {
+ emsg(_(e_listblobreq));
+ return;
+ }
+
+ const char_u *func_name;
+ partial_T *partial = NULL;
+ if (argvars[1].v_type == VAR_FUNC) {
+ func_name = argvars[1].vval.v_string;
+ } else if (argvars[1].v_type == VAR_PARTIAL) {
+ partial = argvars[1].vval.v_partial;
+ func_name = partial_name(partial);
+ } else {
+ func_name = (const char_u *)tv_get_string(&argvars[1]);
+ }
+ if (*func_name == NUL) {
+ return; // type error or empty name
+ }
+
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.evaluate = true;
+ funcexe.partial = partial;
+
+ typval_T initial;
+ typval_T argv[3];
+ if (argvars[0].v_type == VAR_LIST) {
+ list_T *const l = argvars[0].vval.v_list;
+ const listitem_T *li;
+
+ if (argvars[2].v_type == VAR_UNKNOWN) {
+ if (tv_list_len(l) == 0) {
+ semsg(_(e_reduceempty), "List");
+ return;
+ }
+ const listitem_T *const first = tv_list_first(l);
+ initial = *TV_LIST_ITEM_TV(first);
+ li = TV_LIST_ITEM_NEXT(l, first);
+ } else {
+ initial = argvars[2];
+ li = tv_list_first(l);
+ }
+
+ tv_copy(&initial, rettv);
+
+ if (l != NULL) {
+ const VarLockStatus prev_locked = tv_list_locked(l);
+ const int called_emsg_start = called_emsg;
+
+ tv_list_set_lock(l, VAR_FIXED); // disallow the list changing here
+ for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
+ argv[0] = *rettv;
+ argv[1] = *TV_LIST_ITEM_TV(li);
+ rettv->v_type = VAR_UNKNOWN;
+ const int r = call_func(func_name, -1, rettv, 2, argv, &funcexe);
+ tv_clear(&argv[0]);
+ if (r == FAIL || called_emsg != called_emsg_start) {
+ break;
+ }
+ }
+ tv_list_set_lock(l, prev_locked);
+ }
+ } else {
+ const blob_T *const b = argvars[0].vval.v_blob;
+ int i;
+
+ if (argvars[2].v_type == VAR_UNKNOWN) {
+ if (tv_blob_len(b) == 0) {
+ semsg(_(e_reduceempty), "Blob");
+ return;
+ }
+ initial.v_type = VAR_NUMBER;
+ initial.vval.v_number = tv_blob_get(b, 0);
+ i = 1;
+ } else if (argvars[2].v_type != VAR_NUMBER) {
+ emsg(_(e_number_exp));
+ return;
+ } else {
+ initial = argvars[2];
+ i = 0;
+ }
+
+ tv_copy(&initial, rettv);
+ for (; i < tv_blob_len(b); i++) {
+ argv[0] = *rettv;
+ argv[1].v_type = VAR_NUMBER;
+ argv[1].vval.v_number = tv_blob_get(b, i);
+ if (call_func(func_name, -1, rettv, 2, argv, &funcexe) == FAIL) {
+ return;
+ }
+ }
+ }
+}
+
#define SP_NOMOVE 0x01 ///< don't move cursor
#define SP_REPEAT 0x02 ///< repeat to find outer pair
#define SP_RETCOUNT 0x04 ///< return matchcount
@@ -7964,6 +8237,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
int options = SEARCH_KEEP;
int subpatnum;
searchit_arg_T sia;
+ bool use_skip = false;
const char *const pat = tv_get_string(&argvars[0]);
dir = get_search_arg(&argvars[1], flagsp); // May set p_ws.
@@ -7981,7 +8255,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
options |= SEARCH_COL;
}
- // Optional arguments: line number to stop searching and timeout.
+ // Optional arguments: line number to stop searching, timeout and skip.
if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) {
lnum_stop = tv_get_number_chk(&argvars[2], NULL);
if (lnum_stop < 0) {
@@ -7992,6 +8266,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
if (time_limit < 0) {
goto theend;
}
+ use_skip = eval_expr_valid_arg(&argvars[4]);
}
}
@@ -8011,11 +8286,46 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
}
pos = save_cursor = curwin->w_cursor;
+ pos_T firstpos = { 0 };
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, &sia);
+
+ // Repeat until {skip} returns false.
+ for (;;) {
+ subpatnum
+ = searchit(curwin, curbuf, &pos, NULL, dir, (char_u *)pat, 1, options, RE_SEARCH, &sia);
+ // finding the first match again means there is no match where {skip}
+ // evaluates to zero.
+ if (firstpos.lnum != 0 && equalpos(pos, firstpos)) {
+ subpatnum = FAIL;
+ }
+
+ if (subpatnum == FAIL || !use_skip) {
+ // didn't find it or no skip argument
+ break;
+ }
+ firstpos = pos;
+
+ // If the skip expression matches, ignore this match.
+ {
+ const pos_T save_pos = curwin->w_cursor;
+
+ curwin->w_cursor = pos;
+ bool err = false;
+ const bool do_skip = eval_expr_to_bool(&argvars[4], &err);
+ curwin->w_cursor = save_pos;
+ if (err) {
+ // Evaluating {skip} caused an error, break here.
+ subpatnum = FAIL;
+ break;
+ }
+ if (!do_skip) {
+ break;
+ }
+ }
+ }
+
if (subpatnum != FAIL) {
if (flags & SP_SUBPAT) {
retval = subpatnum;
@@ -8475,13 +8785,9 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos)
|| argvars[4].v_type == VAR_UNKNOWN) {
skip = NULL;
} else {
+ // Type is checked later.
skip = &argvars[4];
- if (skip->v_type != VAR_FUNC
- && skip->v_type != VAR_PARTIAL
- && skip->v_type != VAR_STRING) {
- semsg(_(e_invarg2), tv_get_string(&argvars[4]));
- goto theend; // Type error.
- }
+
if (argvars[5].v_type != VAR_UNKNOWN) {
lnum_stop = tv_get_number_chk(&argvars[5], NULL);
if (lnum_stop < 0) {
@@ -8592,10 +8898,7 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir
}
if (skip != NULL) {
- // Empty string means to not use the skip expression.
- if (skip->v_type == VAR_STRING || skip->v_type == VAR_FUNC) {
- use_skip = skip->vval.v_string != NULL && *skip->vval.v_string != NUL;
- }
+ use_skip = eval_expr_valid_arg(skip);
}
save_cursor = curwin->w_cursor;
@@ -8868,6 +9171,49 @@ static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+/// Set the cursor or mark position.
+/// If 'charpos' is TRUE, then use the column number as a character offet.
+/// Otherwise use the column number as a byte offset.
+static void set_position(typval_T *argvars, typval_T *rettv, bool charpos)
+{
+ pos_T pos;
+ int fnum;
+ colnr_T curswant = -1;
+
+ rettv->vval.v_number = -1;
+ const char *const name = tv_get_string_chk(argvars);
+ if (name != NULL) {
+ if (list2fpos(&argvars[1], &pos, &fnum, &curswant, charpos) == OK) {
+ if (pos.col != MAXCOL && --pos.col < 0) {
+ pos.col = 0;
+ }
+ if (name[0] == '.' && name[1] == NUL) {
+ // set cursor; "fnum" is ignored
+ curwin->w_cursor = pos;
+ if (curswant >= 0) {
+ curwin->w_curswant = curswant - 1;
+ curwin->w_set_curswant = false;
+ }
+ check_cursor();
+ rettv->vval.v_number = 0;
+ } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) {
+ // set mark
+ if (setmark_pos((uint8_t)name[1], &pos, fnum) == OK) {
+ rettv->vval.v_number = 0;
+ }
+ } else {
+ emsg(_(e_invarg));
+ }
+ }
+ }
+}
+
+/// "setcharpos()" function
+static void f_setcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ set_position(argvars, rettv, true);
+}
+
static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
dict_T *d;
@@ -8910,6 +9256,12 @@ static void f_setcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+/// "setcursorcharpos" function
+static void f_setcursorcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ set_cursorpos(argvars, rettv, true);
+}
+
/// "setenv()" function
static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
@@ -9166,41 +9518,10 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
-/*
- * "setpos()" function
- */
+/// "setpos()" function
static void f_setpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- pos_T pos;
- int fnum;
- colnr_T curswant = -1;
-
- rettv->vval.v_number = -1;
- const char *const name = tv_get_string_chk(argvars);
- if (name != NULL) {
- if (list2fpos(&argvars[1], &pos, &fnum, &curswant) == OK) {
- if (pos.col != MAXCOL && --pos.col < 0) {
- pos.col = 0;
- }
- if (name[0] == '.' && name[1] == NUL) {
- // set cursor; "fnum" is ignored
- curwin->w_cursor = pos;
- if (curswant >= 0) {
- curwin->w_curswant = curswant - 1;
- curwin->w_set_curswant = false;
- }
- check_cursor();
- rettv->vval.v_number = 0;
- } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) {
- // set mark
- if (setmark_pos((uint8_t)name[1], &pos, fnum) == OK) {
- rettv->vval.v_number = 0;
- }
- } else {
- emsg(_(e_invarg));
- }
- }
- }
+ set_position(argvars, rettv, false);
}
/*
@@ -10215,6 +10536,10 @@ static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (!tv_dict_get_callback(opts, S_LEN("on_stdin"), &on_stdin.cb)) {
return;
}
+ if (!tv_dict_get_callback(opts, S_LEN("on_print"), &on_print)) {
+ return;
+ }
+
on_stdin.buffered = tv_dict_get_number(opts, "stdin_buffered");
if (on_stdin.buffered && on_stdin.cb.type == kCallbackNone) {
on_stdin.self = opts;
@@ -10669,7 +10994,7 @@ static void f_stridx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/*
* "string()" function
*/
-void f_string(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+static void f_string(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
rettv->v_type = VAR_STRING;
rettv->vval.v_string = (char_u *)encode_tv2string(&argvars[0], NULL);
@@ -11879,7 +12204,7 @@ static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr)
pos_T *fp;
int fnum = curbuf->b_fnum;
- fp = var2fpos(&argvars[0], FALSE, &fnum);
+ fp = var2fpos(&argvars[0], false, &fnum, false);
if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count
&& fnum == curbuf->b_fnum) {
// Limit the column to a valid value, getvvcol() doesn't check.
@@ -11985,6 +12310,42 @@ static void f_win_id2win(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = win_id2win(argvars);
}
+/// "win_move_separator()" function
+static void f_win_move_separator(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *wp;
+ int offset;
+
+ rettv->vval.v_number = false;
+
+ wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp == NULL || wp->w_floating) {
+ return;
+ }
+
+ offset = (int)tv_get_number(&argvars[1]);
+ win_drag_vsep_line(wp, offset);
+ rettv->vval.v_number = true;
+}
+
+/// "win_move_statusline()" function
+static void f_win_move_statusline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *wp;
+ int offset;
+
+ rettv->vval.v_number = false;
+
+ wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp == NULL || wp->w_floating) {
+ return;
+ }
+
+ offset = (int)tv_get_number(&argvars[1]);
+ win_drag_status_line(wp, offset);
+ rettv->vval.v_number = true;
+}
+
/// "winbufnr(nr)" function
static void f_winbufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index 42ac1839e6..6f8b032d41 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -3091,7 +3091,7 @@ linenr_T tv_get_lnum(const typval_T *const tv)
linenr_T lnum = (linenr_T)tv_get_number_chk(tv, NULL);
if (lnum == 0) { // No valid number, try using same function as line() does.
int fnum;
- pos_T *const fp = var2fpos(tv, true, &fnum);
+ pos_T *const fp = var2fpos(tv, true, &fnum, false);
if (fp != NULL) {
lnum = fp->lnum;
}
@@ -3205,8 +3205,9 @@ const char *tv_get_string_buf_chk(const typval_T *const tv, char *const buf)
case VAR_BLOB:
case VAR_UNKNOWN:
emsg(_(str_errors[tv->v_type]));
- return false;
+ return NULL;
}
+ abort();
return NULL;
}
diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h
index d1275d6512..ad01c01499 100644
--- a/src/nvim/eval/typval.h
+++ b/src/nvim/eval/typval.h
@@ -81,7 +81,8 @@ typedef struct {
} data;
CallbackType type;
} Callback;
-#define CALLBACK_NONE ((Callback){ .type = kCallbackNone })
+#define CALLBACK_INIT { .type = kCallbackNone }
+#define CALLBACK_NONE ((Callback)CALLBACK_INIT)
/// Structure holding dictionary watcher
typedef struct dict_watcher {
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 95390b1a70..49bf9972b1 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -814,7 +814,11 @@ void ex_retab(exarg_T *eap)
// len is actual number of white characters used
len = num_spaces + num_tabs;
old_len = (long)STRLEN(ptr);
- long new_len = old_len - col + start_col + len + 1;
+ const long new_len = old_len - col + start_col + len + 1;
+ if (new_len <= 0 || new_len >= MAXCOL) {
+ emsg(_(e_resulting_text_too_long));
+ break;
+ }
new_line = xmalloc(new_len);
if (start_col > 0) {
@@ -847,6 +851,10 @@ void ex_retab(exarg_T *eap)
break;
}
vcol += win_chartabsize(curwin, ptr + col, (colnr_T)vcol);
+ if (vcol >= MAXCOL) {
+ emsg(_(e_resulting_text_too_long));
+ break;
+ }
col += utfc_ptr2len(ptr + col);
}
if (new_line == NULL) { // out of memory
@@ -980,8 +988,10 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
foldMoveRange(win, &win->w_folds, line1, line2, dest);
}
}
- curbuf->b_op_start.lnum = dest - num_lines + 1;
- curbuf->b_op_end.lnum = dest;
+ if (!cmdmod.lockmarks) {
+ curbuf->b_op_start.lnum = dest - num_lines + 1;
+ curbuf->b_op_end.lnum = dest;
+ }
line_off = -num_lines;
byte_off = -extent_byte;
} else {
@@ -991,10 +1001,14 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
foldMoveRange(win, &win->w_folds, dest + 1, line1 - 1, line2);
}
}
- curbuf->b_op_start.lnum = dest + 1;
- curbuf->b_op_end.lnum = dest + num_lines;
+ if (!cmdmod.lockmarks) {
+ curbuf->b_op_start.lnum = dest + 1;
+ curbuf->b_op_end.lnum = dest + num_lines;
+ }
+ }
+ if (!cmdmod.lockmarks) {
+ curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
}
- 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, kExtmarkNOOP);
@@ -1057,9 +1071,11 @@ void ex_copy(linenr_T line1, linenr_T line2, linenr_T n)
char_u *p;
count = line2 - line1 + 1;
- curbuf->b_op_start.lnum = n + 1;
- curbuf->b_op_end.lnum = n + count;
- curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
+ if (!cmdmod.lockmarks) {
+ curbuf->b_op_start.lnum = n + 1;
+ curbuf->b_op_end.lnum = n + count;
+ curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
+ }
/*
* there are three situations:
@@ -1099,6 +1115,9 @@ void ex_copy(linenr_T line1, linenr_T line2, linenr_T n)
}
appended_lines_mark(n, count);
+ if (VIsual_active) {
+ check_pos(curbuf, &VIsual);
+ }
msgmore((long)count);
}
@@ -1269,12 +1288,18 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char_u *cmd,
char_u *cmd_buf;
buf_T *old_curbuf = curbuf;
int shell_flags = 0;
+ const pos_T orig_start = curbuf->b_op_start;
+ const pos_T orig_end = curbuf->b_op_end;
const int stmp = p_stmp;
if (*cmd == NUL) { // no filter command
return;
}
+ const bool save_lockmarks = cmdmod.lockmarks;
+ // Temporarily disable lockmarks since that's needed to propagate changed
+ // regions of the buffer for foldUpdate(), linecount, etc.
+ cmdmod.lockmarks = false;
cursor_save = curwin->w_cursor;
linecount = line2 - line1 + 1;
@@ -1349,7 +1374,7 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char_u *cmd,
ui_cursor_goto(Rows - 1, 0);
if (do_out) {
- if (u_save((line2), (linenr_T)(line2 + 1)) == FAIL) {
+ if (u_save(line2, (linenr_T)(line2 + 1)) == FAIL) {
xfree(cmd_buf);
goto error;
}
@@ -1455,10 +1480,15 @@ error:
filterend:
+ cmdmod.lockmarks = save_lockmarks;
if (curbuf != old_curbuf) {
no_wait_return--;
emsg(_("E135: *Filter* Autocommands must not change current buffer"));
+ } else if (cmdmod.lockmarks) {
+ curbuf->b_op_start = orig_start;
+ curbuf->b_op_end = orig_end;
}
+
if (itmp != NULL) {
os_remove((char *)itmp);
}
@@ -3031,14 +3061,15 @@ void ex_append(exarg_T *eap)
// eap->line2 pointed to the end of the buffer and nothing was appended)
// "end" is set to lnum when something has been appended, otherwise
// it is the same as "start" -- Acevedo
- curbuf->b_op_start.lnum = (eap->line2 < curbuf->b_ml.ml_line_count) ?
- eap->line2 + 1 : curbuf->b_ml.ml_line_count;
- if (eap->cmdidx != CMD_append) {
- --curbuf->b_op_start.lnum;
+ if (!cmdmod.lockmarks) {
+ curbuf->b_op_start.lnum
+ = (eap->line2 < curbuf->b_ml.ml_line_count) ? eap->line2 + 1 : curbuf->b_ml.ml_line_count;
+ if (eap->cmdidx != CMD_append) {
+ curbuf->b_op_start.lnum--;
+ }
+ curbuf->b_op_end.lnum = (eap->line2 < lnum) ? lnum : curbuf->b_op_start.lnum;
+ curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
}
- curbuf->b_op_end.lnum = (eap->line2 < lnum)
- ? lnum : curbuf->b_op_start.lnum;
- curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
curwin->w_cursor.lnum = lnum;
check_cursor_lnum();
beginline(BL_SOL | BL_FIX);
@@ -3629,8 +3660,14 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
// We do it here once to avoid it to be replaced over and over again.
// But don't do it when it starts with "\=", then it's an expression.
assert(sub != NULL);
+
+ bool sub_needs_free = false;
if (!(sub[0] == '\\' && sub[1] == '=')) {
+ char_u *source = sub;
sub = regtilde(sub, p_magic);
+ // When previewing, the new pattern allocated by regtilde() needs to be freed
+ // in this function because it will not be used or freed by regtilde() later.
+ sub_needs_free = preview && sub != source;
}
// Check for a match on each line.
@@ -4351,10 +4388,12 @@ skip:
}
if (sub_nsubs > start_nsubs) {
- // Set the '[ and '] marks.
- curbuf->b_op_start.lnum = eap->line1;
- curbuf->b_op_end.lnum = line2;
- curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
+ if (!cmdmod.lockmarks) {
+ // Set the '[ and '] marks.
+ curbuf->b_op_start.lnum = eap->line1;
+ curbuf->b_op_end.lnum = line2;
+ curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
+ }
if (!global_busy) {
// when interactive leave cursor on the match
@@ -4425,6 +4464,10 @@ skip:
kv_destroy(preview_lines.subresults);
+ if (sub_needs_free) {
+ xfree(sub);
+ }
+
return preview_buf;
#undef ADJUST_SUB_FIRSTLNUM
#undef PUSH_PREVIEW_LINES
@@ -5068,8 +5111,7 @@ int find_help_tags(const char_u *arg, int *num_matches, char_u ***matches, bool
&& ((arg[1] != NUL && arg[2] == NUL)
|| (vim_strchr((char_u *)"%_z@", arg[1]) != NULL
&& arg[2] != NUL))) {
- STRCPY(d, "/\\\\");
- STRCPY(d + 3, arg + 1);
+ vim_snprintf((char *)d, IOSIZE, "/\\\\%s", arg + 1);
// Check for "/\\_$", should be "/\\_\$"
if (d[3] == '_' && d[4] == '$') {
STRCPY(d + 4, "\\$");
@@ -5822,7 +5864,7 @@ void ex_helpclose(exarg_T *eap)
{
FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
if (bt_help(win->w_buffer)) {
- win_close(win, false);
+ win_close(win, false, eap->forceit);
return;
}
}
@@ -6107,12 +6149,14 @@ char_u *skip_vimgrep_pat(char_u *p, char_u **s, int *flags)
p++;
// Find the flags
- while (*p == 'g' || *p == 'j') {
+ while (*p == 'g' || *p == 'j' || *p == 'f') {
if (flags != NULL) {
if (*p == 'g') {
*flags |= VGR_GLOBAL;
- } else {
+ } else if (*p == 'j') {
*flags |= VGR_NOJUMP;
+ } else {
+ *flags |= VGR_FUZZY;
}
}
p++;
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index c388373ac1..c2d40c8bb7 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -2097,19 +2097,19 @@ module.cmds = {
command='python',
flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN),
addr_type='ADDR_LINES',
- func='ex_python',
+ func='ex_python3',
},
{
command='pydo',
flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN),
addr_type='ADDR_LINES',
- func='ex_pydo',
+ func='ex_pydo3',
},
{
command='pyfile',
flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN),
addr_type='ADDR_LINES',
- func='ex_pyfile',
+ func='ex_py3file',
},
{
command='py3',
@@ -2139,25 +2139,25 @@ module.cmds = {
command='pyx',
flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN),
addr_type='ADDR_LINES',
- func='ex_pyx',
+ func='ex_python3',
},
{
command='pyxdo',
flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN),
addr_type='ADDR_LINES',
- func='ex_pyxdo',
+ func='ex_pydo3',
},
{
command='pythonx',
flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN),
addr_type='ADDR_LINES',
- func='ex_pyx',
+ func='ex_python3',
},
{
command='pyxfile',
flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN),
addr_type='ADDR_LINES',
- func='ex_pyxfile',
+ func='ex_py3file',
},
{
command='quit',
@@ -2413,7 +2413,7 @@ module.cmds = {
},
{
command='set',
- flags=bit.bor(TRLBAR, EXTRA, CMDWIN, SBOXOK),
+ flags=bit.bor(BANG, TRLBAR, EXTRA, CMDWIN, SBOXOK),
addr_type='ADDR_NONE',
func='ex_set',
},
@@ -2425,13 +2425,13 @@ module.cmds = {
},
{
command='setglobal',
- flags=bit.bor(TRLBAR, EXTRA, CMDWIN, SBOXOK),
+ flags=bit.bor(BANG, TRLBAR, EXTRA, CMDWIN, SBOXOK),
addr_type='ADDR_NONE',
func='ex_set',
},
{
command='setlocal',
- flags=bit.bor(TRLBAR, EXTRA, CMDWIN, SBOXOK),
+ flags=bit.bor(BANG, TRLBAR, EXTRA, CMDWIN, SBOXOK),
addr_type='ADDR_NONE',
func='ex_set',
},
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index 2e8d39ec30..090422088a 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -12,6 +12,7 @@
#include <string.h>
#include "nvim/ascii.h"
+#include "nvim/globals.h"
#include "nvim/vim.h"
#ifdef HAVE_LOCALE_H
# include <locale.h>
@@ -135,21 +136,6 @@ void ex_profile(exarg_T *eap)
}
}
-void ex_python(exarg_T *eap)
-{
- script_host_execute("python", eap);
-}
-
-void ex_pyfile(exarg_T *eap)
-{
- script_host_execute_file("python", eap);
-}
-
-void ex_pydo(exarg_T *eap)
-{
- script_host_do_range("python", eap);
-}
-
void ex_ruby(exarg_T *eap)
{
script_host_execute("ruby", eap);
@@ -1613,7 +1599,7 @@ void ex_compiler(exarg_T *eap)
if (old_cur_comp != NULL) {
old_cur_comp = vim_strsave(old_cur_comp);
}
- do_cmdline_cmd("command -nargs=* CompilerSet setlocal <args>");
+ do_cmdline_cmd("command -nargs=* -keepscript CompilerSet setlocal <args>");
}
do_unlet(S_LEN("g:current_compiler"), true);
do_unlet(S_LEN("b:current_compiler"), true);
@@ -1660,126 +1646,6 @@ void ex_options(exarg_T *eap)
cmd_source((char_u *)SYS_OPTWIN_FILE, NULL);
}
-// Detect Python 3 or 2, and initialize 'pyxversion'.
-void init_pyxversion(void)
-{
- if (p_pyx == 0) {
- if (eval_has_provider("python3")) {
- p_pyx = 3;
- } else if (eval_has_provider("python")) {
- p_pyx = 2;
- }
- }
-}
-
-// Does a file contain one of the following strings at the beginning of any
-// line?
-// "#!(any string)python2" => returns 2
-// "#!(any string)python3" => returns 3
-// "# requires python 2.x" => returns 2
-// "# requires python 3.x" => returns 3
-// otherwise return 0.
-static int requires_py_version(char_u *filename)
-{
- FILE *file;
- int requires_py_version = 0;
- int i, lines;
-
- lines = (int)p_mls;
- if (lines < 0) {
- lines = 5;
- }
-
- file = os_fopen((char *)filename, "r");
- if (file != NULL) {
- for (i = 0; i < lines; i++) {
- if (vim_fgets(IObuff, IOSIZE, file)) {
- break;
- }
- if (i == 0 && IObuff[0] == '#' && IObuff[1] == '!') {
- // Check shebang.
- if (strstr((char *)IObuff + 2, "python2") != NULL) {
- requires_py_version = 2;
- break;
- }
- if (strstr((char *)IObuff + 2, "python3") != NULL) {
- requires_py_version = 3;
- break;
- }
- }
- IObuff[21] = '\0';
- if (STRCMP("# requires python 2.x", IObuff) == 0) {
- requires_py_version = 2;
- break;
- }
- if (STRCMP("# requires python 3.x", IObuff) == 0) {
- requires_py_version = 3;
- break;
- }
- }
- fclose(file);
- }
- return requires_py_version;
-}
-
-
-// Source a python file using the requested python version.
-static void source_pyx_file(exarg_T *eap, char_u *fname)
-{
- exarg_T ex;
- long int v = requires_py_version(fname);
-
- init_pyxversion();
- if (v == 0) {
- // user didn't choose a preference, 'pyx' is used
- v = p_pyx;
- }
-
- // now source, if required python version is not supported show
- // unobtrusive message.
- if (eap == NULL) {
- memset(&ex, 0, sizeof(ex));
- } else {
- ex = *eap;
- }
- ex.arg = fname;
- ex.cmd = (char_u *)(v == 2 ? "pyfile" : "pyfile3");
-
- if (v == 2) {
- ex_pyfile(&ex);
- } else {
- ex_py3file(&ex);
- }
-}
-
-// ":pyxfile {fname}"
-void ex_pyxfile(exarg_T *eap)
-{
- source_pyx_file(eap, eap->arg);
-}
-
-// ":pyx"
-void ex_pyx(exarg_T *eap)
-{
- init_pyxversion();
- if (p_pyx == 2) {
- ex_python(eap);
- } else {
- ex_python3(eap);
- }
-}
-
-// ":pyxdo"
-void ex_pyxdo(exarg_T *eap)
-{
- init_pyxversion();
- if (p_pyx == 2) {
- ex_pydo(eap);
- } else {
- ex_pydo3(eap);
- }
-}
-
/// ":source [{fname}]"
void ex_source(exarg_T *eap)
{
@@ -2323,9 +2189,13 @@ void ex_scriptnames(exarg_T *eap)
for (int i = 1; i <= script_items.ga_len && !got_int; i++) {
if (SCRIPT_ITEM(i).sn_name != NULL) {
- home_replace(NULL, SCRIPT_ITEM(i).sn_name,
- NameBuff, MAXPATHL, true);
- smsg("%3d: %s", i, NameBuff);
+ home_replace(NULL, SCRIPT_ITEM(i).sn_name, NameBuff, MAXPATHL, true);
+ vim_snprintf((char *)IObuff, IOSIZE, "%3d: %s", i, NameBuff);
+ if (!message_filtered(IObuff)) {
+ msg_putchar('\n');
+ msg_outtrans(IObuff);
+ line_breakcheck();
+ }
}
}
}
diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h
index e5eab61f9e..eaf5f627b6 100644
--- a/src/nvim/ex_cmds_defs.h
+++ b/src/nvim/ex_cmds_defs.h
@@ -61,6 +61,7 @@
// current buffer is locked
#define EX_MODIFY 0x100000 // forbidden in non-'modifiable' buffer
#define EX_FLAGS 0x200000 // allow flags after count in argument
+#define EX_KEEPSCRIPT 0x4000000 // keep sctx of where command was invoked
#define EX_FILES (EX_XFILE | EX_EXTRA) // multiple extra files allowed
#define EX_FILE1 (EX_FILES | EX_NOSPC) // 1 file, defaults to current file
#define EX_WORD1 (EX_EXTRA | EX_NOSPC) // one extra word allowed
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 0d82406a0a..4dba0b97ed 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -78,6 +78,10 @@
#include "nvim/vim.h"
#include "nvim/window.h"
+static char *e_no_such_user_defined_command_str = N_("E184: No such user-defined command: %s");
+static char *e_no_such_user_defined_command_in_current_buffer_str
+ = N_("E1237: No such user-defined command in current buffer: %s");
+
static int quitmore = 0;
static bool ex_pressedreturn = false;
@@ -1758,7 +1762,9 @@ static char_u *do_one_cmd(char_u **cmdlinep, int flags, cstack_T *cstack, LineGe
ea.regname = *ea.arg++;
// for '=' register: accept the rest of the line as an expression
if (ea.arg[-1] == '=' && ea.arg[0] != NUL) {
- set_expr_line(vim_strsave(ea.arg));
+ if (!ea.skip) {
+ set_expr_line(vim_strsave(ea.arg));
+ }
ea.arg += STRLEN(ea.arg);
}
ea.arg = skipwhite(ea.arg);
@@ -2697,10 +2703,8 @@ static char_u *find_ucmd(exarg_T *eap, char_u *p, int *full, expand_T *xp, int *
bool amb_local = false; // Found ambiguous buffer-local command,
// only full match global is accepted.
- /*
- * Look for buffer-local user commands first, then global ones.
- */
- gap = &curbuf->b_ucmds;
+ // Look for buffer-local user commands first, then global ones.
+ gap = is_in_cmdwin() ? &prevwin->w_buffer->b_ucmds : &curbuf->b_ucmds;
for (;;) {
for (j = 0; j < gap->ga_len; j++) {
uc = USER_CMD_GA(gap, j);
@@ -2891,13 +2895,17 @@ void f_fullcommand(typval_T *argvars, typval_T *rettv, FunPtr fptr)
exarg_T ea;
char_u *name = argvars[0].vval.v_string;
- while (name[0] != NUL && name[0] == ':') {
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ if (name == NULL) {
+ return;
+ }
+
+ while (*name == ':') {
name++;
}
name = skip_range(name, NULL);
- rettv->v_type = VAR_STRING;
-
ea.cmd = (*name == '2' || *name == '3') ? name + 1 : name;
ea.cmdidx = (cmdidx_T)0;
char_u *p = find_command(&ea, NULL);
@@ -2906,7 +2914,7 @@ void f_fullcommand(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
rettv->vval.v_string = vim_strsave(IS_USER_CMDIDX(ea.cmdidx)
- ? get_user_commands(NULL, ea.useridx)
+ ? get_user_command_name(ea.useridx, ea.cmdidx)
: cmdnames[ea.cmdidx].cmd_name);
}
@@ -4370,7 +4378,7 @@ static char_u *replace_makeprg(exarg_T *eap, char_u *p, char_u **cmdlinep)
++i;
}
len = (int)STRLEN(p);
- new_cmdline = xmalloc(STRLEN(program) + i * (len - 2) + 1);
+ new_cmdline = xmalloc(STRLEN(program) + (size_t)i * (len - 2) + 1);
ptr = new_cmdline;
while ((pos = (char_u *)strstr((char *)program, "$*")) != NULL) {
i = (int)(pos - program);
@@ -5151,7 +5159,7 @@ static int check_more(int message, bool forceit)
char_u *get_command_name(expand_T *xp, int idx)
{
if (idx >= CMD_SIZE) {
- return get_user_command_name(idx);
+ return expand_user_command_name(idx);
}
return cmdnames[idx].cmd_name;
}
@@ -5343,9 +5351,7 @@ static void uc_list(char_u *name, size_t name_len)
uint32_t a;
// 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;
+ garray_T *gap = is_in_cmdwin() ? &prevwin->w_buffer->b_ucmds : &curbuf->b_ucmds;
for (;;) {
for (i = 0; i < gap->ga_len; i++) {
cmd = USER_CMD_GA(gap, i);
@@ -5517,6 +5523,8 @@ static int uc_scan_attr(char_u *attr, size_t len, uint32_t *argt, long *def, int
*flags |= UC_BUFFER;
} else if (STRNICMP(attr, "register", len) == 0) {
*argt |= EX_REGSTR;
+ } else if (STRNICMP(attr, "keepscript", len) == 0) {
+ *argt |= EX_KEEPSCRIPT;
} else if (STRNICMP(attr, "bar", len) == 0) {
*argt |= EX_TRLBAR;
} else {
@@ -5731,26 +5739,36 @@ static void ex_delcommand(exarg_T *eap)
{
int i = 0;
ucmd_T *cmd = NULL;
- int cmp = -1;
+ int res = -1;
garray_T *gap;
+ const char_u *arg = eap->arg;
+ bool buffer_only = false;
+
+ if (STRNCMP(arg, "-buffer", 7) == 0 && ascii_iswhite(arg[7])) {
+ buffer_only = true;
+ arg = skipwhite(arg + 7);
+ }
gap = &curbuf->b_ucmds;
for (;;) {
for (i = 0; i < gap->ga_len; i++) {
cmd = USER_CMD_GA(gap, i);
- cmp = STRCMP(eap->arg, cmd->uc_name);
- if (cmp <= 0) {
+ res = STRCMP(arg, cmd->uc_name);
+ if (res <= 0) {
break;
}
}
- if (gap == &ucmds || cmp == 0) {
+ if (gap == &ucmds || res == 0 || buffer_only) {
break;
}
gap = &ucmds;
}
- if (cmp != 0) {
- semsg(_("E184: No such user-defined command: %s"), eap->arg);
+ if (res != 0) {
+ semsg(_(buffer_only
+ ? e_no_such_user_defined_command_in_current_buffer_str
+ : e_no_such_user_defined_command_str),
+ arg);
return;
}
@@ -6028,7 +6046,7 @@ static size_t uc_check_code(char_u *code, size_t len, char_u *buf, ucmd_T *cmd,
break;
}
- case ct_MODS: {
+ case ct_MODS:
result = quote ? 2 : 0;
if (buf != NULL) {
if (quote) {
@@ -6044,7 +6062,6 @@ static size_t uc_check_code(char_u *code, size_t len, char_u *buf, ucmd_T *cmd,
*buf = '"';
}
break;
- }
case ct_REGISTER:
result = eap->regname ? 1 : 0;
@@ -6205,7 +6222,6 @@ static void do_ucmd(exarg_T *eap)
// K_SPECIAL has been put in the buffer as K_SPECIAL
// KS_SPECIAL KE_FILLER, like for mappings, but
// do_cmdline() doesn't handle that, so convert it back.
- // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI.
len = ksp - p;
if (len > 0) {
memmove(q, p, len);
@@ -6258,15 +6274,19 @@ static void do_ucmd(exarg_T *eap)
buf = xmalloc(totlen + 1);
}
- current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
+ if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0) {
+ current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
+ }
(void)do_cmdline(buf, eap->getline, eap->cookie,
DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
- current_sctx = save_current_sctx;
+ if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0) {
+ current_sctx = save_current_sctx;
+ }
xfree(buf);
xfree(split_buf);
}
-static char_u *get_user_command_name(int idx)
+static char_u *expand_user_command_name(int idx)
{
return get_user_commands(NULL, idx - CMD_SIZE);
}
@@ -6285,9 +6305,7 @@ char_u *get_user_commands(expand_T *xp FUNC_ATTR_UNUSED, int idx)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
// In cmdwin, the alternative buffer should be used.
- const buf_T *const buf = (cmdwin_type != 0 && get_cmdline_type() == NUL)
- ? prevwin->w_buffer
- : curbuf;
+ const buf_T *const buf = is_in_cmdwin() ? prevwin->w_buffer : curbuf;
if (idx < buf->b_ucmds.ga_len) {
return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
@@ -6299,6 +6317,24 @@ char_u *get_user_commands(expand_T *xp FUNC_ATTR_UNUSED, int idx)
return NULL;
}
+// Get the name of user command "idx". "cmdidx" can be CMD_USER or
+// CMD_USER_BUF.
+// Returns NULL if the command is not found.
+static char_u *get_user_command_name(int idx, int cmdidx)
+{
+ if (cmdidx == CMD_USER && idx < ucmds.ga_len) {
+ return USER_CMD(idx)->uc_name;
+ }
+ if (cmdidx == CMD_USER_BUF) {
+ // In cmdwin, the alternative buffer should be used.
+ buf_T *buf = is_in_cmdwin() ? prevwin->w_buffer : curbuf;
+ if (idx < buf->b_ucmds.ga_len) {
+ return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
+ }
+ }
+ return NULL;
+}
+
/*
* Function given to ExpandGeneric() to obtain the list of user command
* attributes.
@@ -6307,7 +6343,7 @@ char_u *get_user_cmd_flags(expand_T *xp, int idx)
{
static char *user_cmd_flags[] = { "addr", "bang", "bar",
"buffer", "complete", "count",
- "nargs", "range", "register" };
+ "nargs", "range", "register", "keepscript" };
if (idx >= (int)ARRAY_SIZE(user_cmd_flags)) {
return NULL;
@@ -6583,7 +6619,7 @@ static void ex_quit(exarg_T *eap)
}
not_exiting();
// close window; may free buffer
- win_close(wp, !buf_hide(wp->w_buffer) || eap->forceit);
+ win_close(wp, !buf_hide(wp->w_buffer) || eap->forceit, eap->forceit);
}
}
@@ -6698,7 +6734,7 @@ void ex_win_close(int forceit, win_T *win, tabpage_T *tp)
// free buffer when not hiding it or when it's a scratch buffer
if (tp == NULL) {
- win_close(win, !need_hide && !buf_hide(buf));
+ win_close(win, !need_hide && !buf_hide(buf), forceit);
} else {
win_close_othertab(win, !need_hide && !buf_hide(buf), tp);
}
@@ -6862,7 +6898,7 @@ static void ex_hide(exarg_T *eap)
// ":hide" or ":hide | cmd": hide current window
if (!eap->skip) {
if (eap->addr_count == 0) {
- win_close(curwin, false); // don't free buffer
+ win_close(curwin, false, eap->forceit); // don't free buffer
} else {
int winnr = 0;
win_T *win = NULL;
@@ -6877,7 +6913,7 @@ static void ex_hide(exarg_T *eap)
if (win == NULL) {
win = lastwin;
}
- win_close(win, false);
+ win_close(win, false, eap->forceit);
}
}
}
@@ -6935,7 +6971,7 @@ static void ex_exit(exarg_T *eap)
}
not_exiting();
// Quit current window, may free the buffer.
- win_close(curwin, !buf_hide(curwin->w_buffer));
+ win_close(curwin, !buf_hide(curwin->w_buffer), eap->forceit);
}
}
@@ -7536,7 +7572,7 @@ void do_exedit(exarg_T *eap, win_T *old_curwin)
// Reset the error/interrupt/exception state here so that
// aborting() returns FALSE when closing a window.
enter_cleanup(&cs);
- win_close(curwin, !need_hide && !buf_hide(curbuf));
+ win_close(curwin, !need_hide && !buf_hide(curbuf), false);
// Restore the error/interrupt/exception state if not
// discarded by a new aborting error, interrupt, or
@@ -7799,14 +7835,11 @@ void post_chdir(CdScope scope, bool trigger_dirchanged)
/// @return true if the directory is successfully changed.
bool changedir_func(char_u *new_dir, CdScope scope)
{
- char_u *tofree;
- char_u *pdir = NULL;
- bool retval = false;
-
if (new_dir == NULL || allbuf_locked()) {
return false;
}
+ char_u *pdir = NULL;
// ":cd -": Change to previous directory
if (STRCMP(new_dir, "-") == 0) {
pdir = get_prevdir(scope);
@@ -7817,26 +7850,12 @@ bool changedir_func(char_u *new_dir, CdScope scope)
new_dir = pdir;
}
- // Free the previous directory
- tofree = get_prevdir(scope);
-
if (os_dirname(NameBuff, MAXPATHL) == OK) {
pdir = vim_strsave(NameBuff);
} else {
pdir = NULL;
}
- switch (scope) {
- case kCdScopeTabpage:
- curtab->tp_prevdir = pdir;
- break;
- case kCdScopeWindow:
- curwin->w_prevdir = pdir;
- break;
- default:
- prev_dir = pdir;
- }
-
// For UNIX ":cd" means: go to home directory.
// On other systems too if 'cdhome' is set.
#if defined(UNIX)
@@ -7849,17 +7868,30 @@ bool changedir_func(char_u *new_dir, CdScope scope)
new_dir = NameBuff;
}
- bool dir_differs = new_dir == NULL || pdir == NULL
- || pathcmp((char *)pdir, (char *)new_dir, -1) != 0;
- if (new_dir != NULL && (!dir_differs || vim_chdir(new_dir) == 0)) {
- post_chdir(scope, dir_differs);
- retval = true;
- } else {
+ bool dir_differs = pdir == NULL || pathcmp((char *)pdir, (char *)new_dir, -1) != 0;
+ if (dir_differs && vim_chdir(new_dir) != 0) {
emsg(_(e_failed));
+ xfree(pdir);
+ return false;
}
- xfree(tofree);
- return retval;
+ char_u **pp;
+ switch (scope) {
+ case kCdScopeTabpage:
+ pp = &curtab->tp_prevdir;
+ break;
+ case kCdScopeWindow:
+ pp = &curwin->w_prevdir;
+ break;
+ default:
+ pp = &prev_dir;
+ }
+ xfree(*pp);
+ *pp = pdir;
+
+ post_chdir(scope, dir_differs);
+
+ return true;
}
/// ":cd", ":tcd", ":lcd", ":chdir", "tchdir" and ":lchdir".
@@ -8107,6 +8139,7 @@ static void ex_put(exarg_T *eap)
eap->forceit = TRUE;
}
curwin->w_cursor.lnum = eap->line2;
+ check_cursor_col();
do_put(eap->regname, NULL, eap->forceit ? BACKWARD : FORWARD, 1,
PUT_LINE|PUT_CURSLINE);
}
@@ -8628,7 +8661,7 @@ static void ex_normal(exarg_T *eap)
return;
}
- // vgetc() expects a CSI and K_SPECIAL to have been escaped. Don't do
+ // vgetc() expects K_SPECIAL to have been escaped. Don't do
// this for the K_SPECIAL leading byte, otherwise special keys will not
// work.
{
@@ -8637,8 +8670,7 @@ static void ex_normal(exarg_T *eap)
// Count the number of characters to be escaped.
for (p = eap->arg; *p != NUL; p++) {
for (l = utfc_ptr2len(p) - 1; l > 0; l--) {
- if (*++p == K_SPECIAL // trailbyte K_SPECIAL or CSI
- ) {
+ if (*++p == K_SPECIAL) { // trailbyte K_SPECIAL
len += 2;
}
}
@@ -9547,23 +9579,34 @@ static void ex_filetype(exarg_T *eap)
}
}
-/// Set all :filetype options ON if user did not explicitly set any to OFF.
-void filetype_maybe_enable(void)
+/// Source ftplugin.vim and indent.vim to create the necessary FileType
+/// autocommands. We do this separately from filetype.vim so that these
+/// autocommands will always fire first (and thus can be overriden) while still
+/// allowing general filetype detection to be disabled in the user's init file.
+void filetype_plugin_enable(void)
{
- if (filetype_detect == kNone) {
- source_runtime(FILETYPE_FILE, true);
- filetype_detect = kTrue;
- }
if (filetype_plugin == kNone) {
- source_runtime(FTPLUGIN_FILE, true);
+ source_runtime(FTPLUGIN_FILE, DIP_ALL);
filetype_plugin = kTrue;
}
if (filetype_indent == kNone) {
- source_runtime(INDENT_FILE, true);
+ source_runtime(INDENT_FILE, DIP_ALL);
filetype_indent = kTrue;
}
}
+/// Enable filetype detection if the user did not explicitly disable it.
+void filetype_maybe_enable(void)
+{
+ if (filetype_detect == kNone) {
+ // Normally .vim files are sourced before .lua files when both are
+ // supported, but we reverse the order here because we want the Lua
+ // autocommand to be defined first so that it runs first
+ source_runtime(FILETYPE_FILE, DIP_ALL);
+ filetype_detect = kTrue;
+ }
+}
+
/// ":setfiletype [FALLBACK] {name}"
static void ex_setfiletype(exarg_T *eap)
{
@@ -9590,18 +9633,6 @@ static void ex_digraphs(exarg_T *eap)
}
}
-static void ex_set(exarg_T *eap)
-{
- int flags = 0;
-
- if (eap->cmdidx == CMD_setlocal) {
- flags = OPT_LOCAL;
- } else if (eap->cmdidx == CMD_setglobal) {
- flags = OPT_GLOBAL;
- }
- (void)do_set(eap->arg, flags);
-}
-
void set_no_hlsearch(bool flag)
{
no_hlsearch = flag;
@@ -9836,6 +9867,7 @@ Dictionary commands_array(buf_T *buf)
PUT(d, "bang", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_BANG)));
PUT(d, "bar", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_TRLBAR)));
PUT(d, "register", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_REGSTR)));
+ PUT(d, "keepscript", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_KEEPSCRIPT)));
switch (cmd->uc_argt & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) {
case 0:
diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c
index b1c59a607c..851828afcf 100644
--- a/src/nvim/ex_eval.c
+++ b/src/nvim/ex_eval.c
@@ -599,7 +599,7 @@ static void catch_exception(except_T *excp)
{
excp->caught = caught_stack;
caught_stack = excp;
- set_vim_var_string(VV_EXCEPTION, (char *)excp->value, -1);
+ set_vim_var_string(VV_EXCEPTION, excp->value, -1);
if (*excp->throw_name != NUL) {
if (excp->throw_lnum != 0) {
vim_snprintf((char *)IObuff, IOSIZE, _("%s, line %" PRId64),
@@ -650,7 +650,7 @@ static void finish_exception(except_T *excp)
}
caught_stack = caught_stack->caught;
if (caught_stack != NULL) {
- set_vim_var_string(VV_EXCEPTION, (char *)caught_stack->value, -1);
+ set_vim_var_string(VV_EXCEPTION, caught_stack->value, -1);
if (*caught_stack->throw_name != NUL) {
if (caught_stack->throw_lnum != 0) {
vim_snprintf((char *)IObuff, IOSIZE,
@@ -733,7 +733,7 @@ static void report_pending(int action, int pending, void *value)
vim_snprintf((char *)IObuff, IOSIZE,
mesg, _("Exception"));
mesg = (char *)concat_str(IObuff, (char_u *)": %s");
- s = (char *)((except_T *)value)->value;
+ s = ((except_T *)value)->value;
} else if ((pending & CSTP_ERROR) && (pending & CSTP_INTERRUPT)) {
s = _("Error and interrupt");
} else if (pending & CSTP_ERROR) {
@@ -1620,7 +1620,7 @@ void ex_endtry(exarg_T *eap)
// the finally clause. The latter case need not be tested since then
// anything pending has already been discarded.
bool skip = did_emsg || got_int || current_exception
- || !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE);
+ || !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE);
if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) {
eap->errmsg = get_end_emsg(cstack);
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 78b8e43e65..ed4475eb1a 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -311,7 +311,7 @@ static char_u *get_healthcheck_names(expand_T *xp, int idx)
healthchecks.last_gen = last_prompt_id;
}
return idx <
- (int)healthchecks.names.ga_len ? ((char_u **)(healthchecks.names.ga_data))[idx] : NULL;
+ healthchecks.names.ga_len ? ((char_u **)(healthchecks.names.ga_data))[idx] : NULL;
}
/// Transform healthcheck file path into it's name.
@@ -772,7 +772,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
ccline.cmdindent = (s->firstc > 0 ? s->indent : 0);
// alloc initial ccline.cmdbuff
- alloc_cmdbuff(exmode_active ? 250 : s->indent + 1);
+ alloc_cmdbuff(indent + 50);
ccline.cmdlen = ccline.cmdpos = 0;
ccline.cmdbuff[0] = NUL;
@@ -6563,7 +6563,7 @@ static int open_cmdwin(void)
set_bufref(&bufref, curbuf);
win_goto(old_curwin);
if (win_valid(wp) && wp != curwin) {
- win_close(wp, true);
+ win_close(wp, true, false);
}
// win_close() may have already wiped the buffer when 'bh' is
@@ -6587,6 +6587,13 @@ static int open_cmdwin(void)
return cmdwin_result;
}
+/// @return true if in the cmdwin, not editing the command line.
+bool is_in_cmdwin(void)
+ FUNC_ATTR_PURE
+{
+ return cmdwin_type != 0 && get_cmdline_type() == NUL;
+}
+
/// Get script string
///
/// Used for commands which accept either `:command script` or
diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c
index a37cad9f2d..ca07174543 100644
--- a/src/nvim/ex_session.c
+++ b/src/nvim/ex_session.c
@@ -338,14 +338,26 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr
// Edit the file. Skip this when ":next" already did it.
if (add_edit && (!did_next || wp->w_arg_idx_invalid)) {
- char *fname_esc =
- ses_escape_fname(ses_get_fname(wp->w_buffer, flagp), flagp);
- //
- // Load the file.
- //
- if (wp->w_buffer->b_ffname != NULL
- && (!bt_nofile(wp->w_buffer)
- || wp->w_buffer->terminal)) {
+ char *fname_esc = ses_escape_fname(ses_get_fname(wp->w_buffer, flagp), flagp);
+ if (bt_help(wp->w_buffer)) {
+ char *curtag = "";
+
+ // A help buffer needs some options to be set.
+ // First, create a new empty buffer with "buftype=help".
+ // Then ":help" will re-use both the buffer and the window and set
+ // the options, even when "options" is not in 'sessionoptions'.
+ if (0 < wp->w_tagstackidx && wp->w_tagstackidx <= wp->w_tagstacklen) {
+ curtag = (char *)wp->w_tagstack[wp->w_tagstackidx - 1].tagname;
+ }
+
+ if (put_line(fd, "enew | setl bt=help") == FAIL
+ || fprintf(fd, "help %s", curtag) < 0 || put_eol(fd) == FAIL) {
+ return FAIL;
+ }
+ } else if (wp->w_buffer->b_ffname != NULL
+ && (!bt_nofile(wp->w_buffer) || wp->w_buffer->terminal)) {
+ // Load the file.
+
// Editing a file in this buffer: use ":edit file".
// This may have side effects! (e.g., compressed or network file).
//
@@ -579,6 +591,24 @@ static int makeopens(FILE *fd, char_u *dirnow)
return FAIL;
}
+ // Put all buffers into the buffer list.
+ // Do it very early to preserve buffer order after loading session (which
+ // can be disrupted by prior `edit` or `tabedit` calls).
+ FOR_ALL_BUFFERS(buf) {
+ if (!(only_save_windows && buf->b_nwindows == 0)
+ && !(buf->b_help && !(ssop_flags & SSOP_HELP))
+ && buf->b_fname != NULL
+ && buf->b_p_bl) {
+ if (fprintf(fd, "badd +%" PRId64 " ",
+ buf->b_wininfo == NULL
+ ? (int64_t)1L
+ : (int64_t)buf->b_wininfo->wi_fpos.lnum) < 0
+ || ses_fname(fd, buf, &ssop_flags, true) == FAIL) {
+ return FAIL;
+ }
+ }
+ }
+
// the global argument list
if (ses_arglist(fd, "argglobal", &global_alist.al_ga,
!(ssop_flags & SSOP_CURDIR), &ssop_flags) == FAIL) {
@@ -614,7 +644,10 @@ static int makeopens(FILE *fd, char_u *dirnow)
// Similar to ses_win_rec() below, populate the tab pages first so
// later local options won't be copied to the new tabs.
FOR_ALL_TABS(tp) {
- if (tp->tp_next != NULL && put_line(fd, "tabnew") == FAIL) {
+ // Use `bufhidden=wipe` to remove empty "placeholder" buffers once
+ // they are not needed. This prevents creating extra buffers (see
+ // cause of Vim patch 8.1.0829)
+ if (tp->tp_next != NULL && put_line(fd, "tabnew +setlocal\\ bufhidden=wipe") == FAIL) {
return FAIL;
}
}
@@ -792,25 +825,6 @@ static int makeopens(FILE *fd, char_u *dirnow)
return FAIL;
}
- // Now put the remaining buffers into the buffer list.
- // This is near the end, so that when 'hidden' is set we don't create extra
- // buffers. If the buffer was already created with another command the
- // ":badd" will have no effect.
- FOR_ALL_BUFFERS(buf) {
- if (!(only_save_windows && buf->b_nwindows == 0)
- && !(buf->b_help && !(ssop_flags & SSOP_HELP))
- && buf->b_fname != NULL
- && buf->b_p_bl) {
- if (fprintf(fd, "badd +%" PRId64 " ",
- buf->b_wininfo == NULL
- ? (int64_t)1L
- : (int64_t)buf->b_wininfo->wi_fpos.lnum) < 0
- || ses_fname(fd, buf, &ssop_flags, true) == FAIL) {
- return FAIL;
- }
- }
- }
-
//
// Wipe out an empty unnamed buffer we started in.
//
diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c
index c4d8f75a21..48e57e20e1 100644
--- a/src/nvim/extmark.c
+++ b/src/nvim/extmark.c
@@ -48,28 +48,29 @@
# include "extmark.c.generated.h"
#endif
-static ExtmarkNs *buf_ns_ref(buf_T *buf, uint64_t ns_id, bool put)
+static uint32_t *buf_ns_ref(buf_T *buf, uint32_t ns_id, bool put)
{
- return map_ref(uint64_t, ExtmarkNs)(buf->b_extmark_ns, ns_id, put);
+ return map_ref(uint32_t, uint32_t)(buf->b_extmark_ns, ns_id, put);
}
/// Create or update an extmark
///
/// must not be used during iteration!
-/// @returns the internal mark id
-uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t *idp, int row, colnr_T col, int end_row,
- colnr_T end_col, Decoration *decor, bool right_gravity, bool end_right_gravity,
- ExtmarkOp op)
+void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col, int end_row,
+ colnr_T end_col, Decoration *decor, bool right_gravity, bool end_right_gravity,
+ ExtmarkOp op)
{
- ExtmarkNs *ns = buf_ns_ref(buf, ns_id, true);
- assert(ns != NULL);
- mtpos_t old_pos;
- uint64_t mark = 0;
- uint64_t id = idp ? *idp : 0;
+ uint32_t *ns = buf_ns_ref(buf, ns_id, true);
+ uint32_t id = idp ? *idp : 0;
+ bool decor_full = false;
uint8_t decor_level = kDecorLevelNone; // no decor
if (decor) {
+ if (kv_size(decor->virt_text) || kv_size(decor->virt_lines)) {
+ decor_full = true;
+ decor = xmemdup(decor, sizeof *decor);
+ }
decor_level = kDecorLevelVisible; // decor affects redraw
if (kv_size(decor->virt_lines)) {
decor_level = kDecorLevelVirtLine; // decor affects horizontal size
@@ -77,50 +78,64 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t *idp, int row, colnr_T
}
if (id == 0) {
- id = ns->free_id++;
+ id = ++*ns;
} else {
- uint64_t old_mark = map_get(uint64_t, uint64_t)(ns->map, id);
- if (old_mark) {
- if (old_mark & MARKTREE_PAIRED_FLAG || end_row > -1) {
+ MarkTreeIter itr[1] = { 0 };
+ mtkey_t old_mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr);
+ if (old_mark.id) {
+ if (mt_paired(old_mark) || end_row > -1) {
extmark_del(buf, ns_id, id);
} else {
- MarkTreeIter itr[1] = { 0 };
- old_pos = marktree_lookup(buf->b_marktree, old_mark, itr);
+ // TODO(bfredl): we need to do more if "revising" a decoration mark.
assert(itr->node);
- if (old_pos.row == row && old_pos.col == col) {
- ExtmarkItem it = map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index,
- old_mark);
- if (it.decor) {
- decor_remove(buf, row, row, it.decor);
+ if (old_mark.pos.row == row && old_mark.pos.col == col) {
+ if (marktree_decor_level(old_mark) > kDecorLevelNone) {
+ decor_remove(buf, row, row, old_mark.decor_full);
+ old_mark.decor_full = NULL;
+ }
+ old_mark.flags = 0;
+ if (decor_full) {
+ old_mark.decor_full = decor;
+ } else if (decor) {
+ old_mark.hl_id = decor->hl_id;
+ // Workaround: the gcc compiler of functionaltest-lua build
+ // apparently incapable of handling basic integer constants.
+ // This can be underanged as soon as we bump minimal gcc version.
+ old_mark.flags = (uint16_t)(old_mark.flags
+ | (decor->hl_eol ? (uint16_t)MT_FLAG_HL_EOL : (uint16_t)0));
+ old_mark.priority = decor->priority;
}
- mark = marktree_revise(buf->b_marktree, itr, decor_level);
+ marktree_revise(buf->b_marktree, itr, decor_level, old_mark);
goto revised;
}
marktree_del_itr(buf->b_marktree, itr, false);
}
} else {
- ns->free_id = MAX(ns->free_id, id+1);
+ *ns = MAX(*ns, id);
}
}
- if (end_row > -1) {
- mark = marktree_put_pair(buf->b_marktree,
- row, col, right_gravity,
- end_row, end_col, end_right_gravity, decor_level);
- } else {
- mark = marktree_put(buf->b_marktree, row, col, right_gravity, decor_level);
+ mtkey_t mark = { { row, col }, ns_id, id, 0,
+ mt_flags(right_gravity, decor_level), 0, NULL };
+ if (decor_full) {
+ mark.decor_full = decor;
+ } else if (decor) {
+ mark.hl_id = decor->hl_id;
+ // workaround: see above
+ mark.flags = (uint16_t)(mark.flags | (decor->hl_eol ? (uint16_t)MT_FLAG_HL_EOL : (uint16_t)0));
+ mark.priority = decor->priority;
}
-revised:
- map_put(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark,
- (ExtmarkItem){ ns_id, id, decor });
- map_put(uint64_t, uint64_t)(ns->map, id, mark);
+ marktree_put(buf->b_marktree, mark, end_row, end_col, end_right_gravity);
+revised:
if (op != kExtmarkNoUndo) {
// TODO(bfredl): this doesn't cover all the cases and probably shouldn't
// be done "prematurely". Any movement in undo history might necessitate
- // adding new marks to old undo headers.
- u_extmark_set(buf, mark, row, col);
+ // adding new marks to old undo headers. add a test case for this (doesn't
+ // fail extmark_spec.lua, and it should)
+ uint64_t mark_id = mt_lookup_id(ns_id, id, false);
+ u_extmark_set(buf, mark_id, row, col);
}
if (decor) {
@@ -133,18 +148,17 @@ revised:
if (idp) {
*idp = id;
}
- return mark;
}
static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col)
{
MarkTreeIter itr[1] = { 0 };
- mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr);
- if (pos.row == -1) {
+ mtkey_t key = marktree_lookup(buf->b_marktree, mark, itr);
+ if (key.pos.row == -1) {
return false;
}
- if (pos.row == row && pos.col == col) {
+ if (key.pos.row == row && key.pos.col == col) {
return true;
}
@@ -154,45 +168,35 @@ static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col)
// Remove an extmark
// Returns 0 on missing id
-bool extmark_del(buf_T *buf, uint64_t ns_id, uint64_t id)
+bool extmark_del(buf_T *buf, uint32_t ns_id, uint32_t id)
{
- ExtmarkNs *ns = buf_ns_ref(buf, ns_id, false);
- if (!ns) {
- return false;
- }
-
- uint64_t mark = map_get(uint64_t, uint64_t)(ns->map, id);
- if (!mark) {
+ MarkTreeIter itr[1] = { 0 };
+ mtkey_t key = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr);
+ if (!key.id) {
return false;
}
-
- MarkTreeIter itr[1] = { 0 };
- mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr);
- assert(pos.row >= 0);
+ assert(key.pos.row >= 0);
marktree_del_itr(buf->b_marktree, itr, false);
- ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark);
- mtpos_t pos2 = pos;
- if (mark & MARKTREE_PAIRED_FLAG) {
- pos2 = marktree_lookup(buf->b_marktree, mark|MARKTREE_END_FLAG, itr);
- assert(pos2.row >= 0);
+ mtkey_t key2 = key;
+
+ if (mt_paired(key)) {
+ key2 = marktree_lookup_ns(buf->b_marktree, ns_id, id, true, itr);
+ assert(key2.pos.row >= 0);
marktree_del_itr(buf->b_marktree, itr, false);
}
- if (item.decor) {
- decor_remove(buf, pos.row, pos2.row, item.decor);
+ if (marktree_decor_level(key) > kDecorLevelNone) {
+ decor_remove(buf, key.pos.row, key2.pos.row, key.decor_full);
}
- map_del(uint64_t, uint64_t)(ns->map, id);
- map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark);
-
// TODO(bfredl): delete it from current undo header, opportunistically?
return true;
}
// Free extmarks in a ns between lines
// if ns = 0, it means clear all namespaces
-bool extmark_clear(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_row, colnr_T u_col)
+bool extmark_clear(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_row, colnr_T u_col)
{
if (!map_size(buf->b_extmark_ns)) {
return false;
@@ -201,68 +205,58 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_r
bool marks_cleared = false;
bool all_ns = (ns_id == 0);
- ExtmarkNs *ns = NULL;
+ uint32_t *ns = NULL;
if (!all_ns) {
ns = buf_ns_ref(buf, ns_id, false);
if (!ns) {
// nothing to do
return false;
}
-
- // TODO(bfredl): if map_size(ns->map) << buf->b_marktree.n_nodes
- // it could be faster to iterate over the map instead
}
// the value is either zero or the lnum (row+1) if highlight was present.
static Map(uint64_t, ssize_t) delete_set = MAP_INIT;
- typedef struct { Decoration *decor; int row1; } DecorItem;
+ typedef struct { int row1; } DecorItem;
static kvec_t(DecorItem) decors;
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, l_row, l_col, itr);
while (true) {
- mtmark_t mark = marktree_itr_current(itr);
- if (mark.row < 0
- || mark.row > u_row
- || (mark.row == u_row && mark.col > u_col)) {
+ mtkey_t mark = marktree_itr_current(itr);
+ if (mark.pos.row < 0
+ || mark.pos.row > u_row
+ || (mark.pos.row == u_row && mark.pos.col > u_col)) {
break;
}
- ssize_t *del_status = map_ref(uint64_t, ssize_t)(&delete_set, mark.id,
+ ssize_t *del_status = map_ref(uint64_t, ssize_t)(&delete_set, mt_lookup_key(mark),
false);
if (del_status) {
marktree_del_itr(buf->b_marktree, itr, false);
if (*del_status >= 0) { // we had a decor_id
DecorItem it = kv_A(decors, *del_status);
- decor_remove(buf, it.row1, mark.row, it.decor);
+ decor_remove(buf, it.row1, mark.pos.row, mark.decor_full);
}
- map_del(uint64_t, ssize_t)(&delete_set, mark.id);
+ map_del(uint64_t, ssize_t)(&delete_set, mt_lookup_key(mark));
continue;
}
- uint64_t start_id = mark.id & ~MARKTREE_END_FLAG;
- ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index,
- start_id);
-
- assert(item.ns_id > 0 && item.mark_id > 0);
- if (item.mark_id > 0 && (item.ns_id == ns_id || all_ns)) {
+ assert(mark.ns > 0 && mark.id > 0);
+ if (mark.ns == ns_id || all_ns) {
marks_cleared = true;
- if (mark.id & MARKTREE_PAIRED_FLAG) {
- uint64_t other = mark.id ^ MARKTREE_END_FLAG;
+ if (mt_paired(mark)) {
+ uint64_t other = mt_lookup_id(mark.ns, mark.id, !mt_end(mark));
ssize_t decor_id = -1;
- if (item.decor) {
+ if (marktree_decor_level(mark) > kDecorLevelNone) {
// Save the decoration and the first pos. Clear the decoration
// later when we know the full range.
decor_id = (ssize_t)kv_size(decors);
kv_push(decors,
- ((DecorItem) { .decor = item.decor, .row1 = mark.row }));
+ ((DecorItem) { .row1 = mark.pos.row }));
}
map_put(uint64_t, ssize_t)(&delete_set, other, decor_id);
- } else if (item.decor) {
- decor_remove(buf, mark.row, mark.row, item.decor);
+ } else if (mark.decor_full) {
+ decor_remove(buf, mark.pos.row, mark.pos.row, mark.decor_full);
}
- ExtmarkNs *my_ns = all_ns ? buf_ns_ref(buf, item.ns_id, false) : ns;
- map_del(uint64_t, uint64_t)(my_ns->map, item.mark_id);
- map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, start_id);
marktree_del_itr(buf->b_marktree, itr, false);
} else {
marktree_itr_next(buf->b_marktree, itr);
@@ -271,12 +265,12 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_r
uint64_t id;
ssize_t decor_id;
map_foreach(&delete_set, id, decor_id, {
- mtpos_t pos = marktree_lookup(buf->b_marktree, id, itr);
+ mtkey_t mark = marktree_lookup(buf->b_marktree, id, itr);
assert(itr->node);
marktree_del_itr(buf->b_marktree, itr, false);
if (decor_id >= 0) {
DecorItem it = kv_A(decors, decor_id);
- decor_remove(buf, it.row1, pos.row, it.decor);
+ decor_remove(buf, it.row1, mark.pos.row, mark.decor_full);
}
});
map_clear(uint64_t, ssize_t)(&delete_set);
@@ -290,7 +284,7 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_r
// 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
-ExtmarkInfoArray extmark_get(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_row,
+ExtmarkInfoArray extmark_get(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_row,
colnr_T u_col, int64_t amount, bool reverse)
{
ExtmarkInfoArray array = KV_INITIAL_VALUE;
@@ -300,30 +294,26 @@ ExtmarkInfoArray extmark_get(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_co
itr, reverse, false, NULL);
int order = reverse ? -1 : 1;
while ((int64_t)kv_size(array) < amount) {
- mtmark_t mark = marktree_itr_current(itr);
- mtpos_t endpos = { -1, -1 };
- if (mark.row < 0
- || (mark.row - u_row) * order > 0
- || (mark.row == u_row && (mark.col - u_col) * order > 0)) {
+ mtkey_t mark = marktree_itr_current(itr);
+ if (mark.pos.row < 0
+ || (mark.pos.row - u_row) * order > 0
+ || (mark.pos.row == u_row && (mark.pos.col - u_col) * order > 0)) {
break;
}
- if (mark.id & MARKTREE_END_FLAG) {
+ if (mt_end(mark)) {
goto next_mark;
- } else if (mark.id & MARKTREE_PAIRED_FLAG) {
- endpos = marktree_lookup(buf->b_marktree, mark.id | MARKTREE_END_FLAG,
- NULL);
}
-
- ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index,
- mark.id);
- if (item.ns_id == ns_id) {
- kv_push(array, ((ExtmarkInfo) { .ns_id = item.ns_id,
- .mark_id = item.mark_id,
- .row = mark.row, .col = mark.col,
- .end_row = endpos.row,
- .end_col = endpos.col,
- .decor = item.decor }));
+ if (mark.ns == ns_id) {
+ mtkey_t end = marktree_get_alt(buf->b_marktree, mark, NULL);
+ kv_push(array, ((ExtmarkInfo) { .ns_id = mark.ns,
+ .mark_id = mark.id,
+ .row = mark.pos.row, .col = mark.pos.col,
+ .end_row = end.pos.row,
+ .end_col = end.pos.col,
+ .right_gravity = mt_right(mark),
+ .end_right_gravity = mt_right(end),
+ .decor = get_decor(mark) }));
}
next_mark:
if (reverse) {
@@ -336,36 +326,25 @@ next_mark:
}
// Lookup an extmark by id
-ExtmarkInfo extmark_from_id(buf_T *buf, uint64_t ns_id, uint64_t id)
+ExtmarkInfo extmark_from_id(buf_T *buf, uint32_t ns_id, uint32_t id)
{
- ExtmarkNs *ns = buf_ns_ref(buf, ns_id, false);
- ExtmarkInfo ret = { 0, 0, -1, -1, -1, -1, NULL };
- if (!ns) {
+ ExtmarkInfo ret = { 0, 0, -1, -1, -1, -1, false, false, DECORATION_INIT };
+ mtkey_t mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, NULL);
+ if (!mark.id) {
return ret;
}
-
- uint64_t mark = map_get(uint64_t, uint64_t)(ns->map, id);
- if (!mark) {
- return ret;
- }
-
- mtpos_t pos = marktree_lookup(buf->b_marktree, mark, NULL);
- mtpos_t endpos = { -1, -1 };
- if (mark & MARKTREE_PAIRED_FLAG) {
- endpos = marktree_lookup(buf->b_marktree, mark | MARKTREE_END_FLAG, NULL);
- }
- assert(pos.row >= 0);
-
- ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index,
- mark);
+ assert(mark.pos.row >= 0);
+ mtkey_t end = marktree_get_alt(buf->b_marktree, mark, NULL);
ret.ns_id = ns_id;
ret.mark_id = id;
- ret.row = pos.row;
- ret.col = pos.col;
- ret.end_row = endpos.row;
- ret.end_col = endpos.col;
- ret.decor = item.decor;
+ ret.row = mark.pos.row;
+ ret.col = mark.pos.col;
+ ret.end_row = end.pos.row;
+ ret.end_col = end.pos.col;
+ ret.right_gravity = mt_right(mark);
+ ret.end_right_gravity = mt_right(end);
+ ret.decor = get_decor(mark);
return ret;
}
@@ -378,25 +357,26 @@ void extmark_free_all(buf_T *buf)
return;
}
- uint64_t id;
- ExtmarkNs ns;
- ExtmarkItem item;
+ MarkTreeIter itr[1] = { 0 };
+ marktree_itr_get(buf->b_marktree, 0, 0, itr);
+ while (true) {
+ mtkey_t mark = marktree_itr_current(itr);
+ if (mark.pos.row < 0) {
+ break;
+ }
- marktree_clear(buf->b_marktree);
+ // don't free mark.decor_full twice for a paired mark.
+ if (!(mt_paired(mark) && mt_end(mark))) {
+ decor_free(mark.decor_full);
+ }
- map_foreach(buf->b_extmark_ns, id, ns, {
- (void)id;
- map_destroy(uint64_t, uint64_t)(ns.map);
- });
- map_destroy(uint64_t, ExtmarkNs)(buf->b_extmark_ns);
- map_init(uint64_t, ExtmarkNs, buf->b_extmark_ns);
+ marktree_itr_next(buf->b_marktree, itr);
+ }
- map_foreach(buf->b_extmark_index, id, item, {
- (void)id;
- decor_free(item.decor);
- });
- map_destroy(uint64_t, ExtmarkItem)(buf->b_extmark_index);
- map_init(uint64_t, ExtmarkItem, buf->b_extmark_index);
+ marktree_clear(buf->b_marktree);
+
+ map_destroy(uint32_t, uint32_t)(buf->b_extmark_ns);
+ map_init(uint32_t, uint32_t, buf->b_extmark_ns);
}
@@ -437,16 +417,16 @@ void u_extmark_copy(buf_T *buf, int l_row, colnr_T l_col, int u_row, colnr_T u_c
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, l_row, l_col, itr);
while (true) {
- mtmark_t mark = marktree_itr_current(itr);
- if (mark.row < 0
- || mark.row > u_row
- || (mark.row == u_row && mark.col > u_col)) {
+ mtkey_t mark = marktree_itr_current(itr);
+ if (mark.pos.row < 0
+ || mark.pos.row > u_row
+ || (mark.pos.row == u_row && mark.pos.col > u_col)) {
break;
}
ExtmarkSavePos pos;
- pos.mark = mark.id;
- pos.old_row = mark.row;
- pos.old_col = mark.col;
+ pos.mark = mt_lookup_key(mark);
+ pos.old_row = mark.pos.row;
+ pos.old_col = mark.pos.col;
pos.row = -1;
pos.col = -1;
diff --git a/src/nvim/extmark.h b/src/nvim/extmark.h
index c70db9f7aa..af9526cd43 100644
--- a/src/nvim/extmark.h
+++ b/src/nvim/extmark.h
@@ -3,6 +3,7 @@
#include "nvim/buffer_defs.h"
#include "nvim/extmark_defs.h"
+#include "nvim/decoration.h"
#include "nvim/marktree.h"
#include "nvim/pos.h"
@@ -15,7 +16,9 @@ typedef struct {
colnr_T col;
int end_row;
colnr_T end_col;
- Decoration *decor;
+ bool right_gravity;
+ bool end_right_gravity;
+ Decoration decor; // TODO(bfredl): CHONKY
} ExtmarkInfo;
typedef kvec_t(ExtmarkInfo) ExtmarkInfoArray;
diff --git a/src/nvim/extmark_defs.h b/src/nvim/extmark_defs.h
index bbe8504ebf..5570b5c71e 100644
--- a/src/nvim/extmark_defs.h
+++ b/src/nvim/extmark_defs.h
@@ -4,23 +4,11 @@
#include "nvim/lib/kvec.h"
#include "nvim/types.h"
-typedef struct Decoration Decoration;
-
typedef struct {
char *text;
int hl_id;
} VirtTextChunk;
-
-typedef struct {
- uint64_t ns_id;
- uint64_t mark_id;
- // TODO(bfredl): a lot of small allocations. Should probably use
- // kvec_t(Decoration) as an arena. Alternatively, store ns_id/mark_id
- // _inline_ in MarkTree and use the map only for decorations.
- Decoration *decor;
-} ExtmarkItem;
-
typedef struct undo_object ExtmarkUndoObject;
typedef kvec_t(ExtmarkUndoObject) extmark_undo_vec_t;
diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c
index b2cd5c510b..25dbf680de 100644
--- a/src/nvim/file_search.c
+++ b/src/nvim/file_search.c
@@ -483,8 +483,14 @@ void *vim_findfile_init(char_u *path, char_u *filename, char_u *stopdirs, int le
int len = 0;
if (p > search_ctx->ffsc_fix_path) {
+ // do not add '..' to the path and start upwards searching
len = (int)(p - search_ctx->ffsc_fix_path) - 1;
- STRNCAT(ff_expand_buffer, search_ctx->ffsc_fix_path, len);
+ if ((len >= 2 && STRNCMP(search_ctx->ffsc_fix_path, "..", 2) == 0)
+ && (len == 2 || search_ctx->ffsc_fix_path[2] == PATHSEP)) {
+ xfree(buf);
+ goto error_return;
+ }
+ STRLCAT(ff_expand_buffer, search_ctx->ffsc_fix_path, eb_len + (size_t)len + 1);
add_pathsep((char *)ff_expand_buffer);
} else {
len = (int)STRLEN(search_ctx->ffsc_fix_path);
@@ -1433,7 +1439,11 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first
rel_fname = NULL;
}
- if (first == TRUE) {
+ if (first == true) {
+ if (len == 0) {
+ return NULL;
+ }
+
// copy file name into NameBuff, expanding environment variables
save_char = ptr[len];
ptr[len] = NUL;
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index f8cf341836..8e1be3bbf7 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -242,6 +242,7 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski
bool notconverted = false; // true if conversion wanted but it wasn't possible
char_u conv_rest[CONV_RESTLEN];
int conv_restlen = 0; // nr of bytes in conv_rest[]
+ pos_T orig_start;
buf_T *old_curbuf;
char_u *old_b_ffname;
char_u *old_b_fname;
@@ -298,14 +299,10 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski
fname = sfname;
#endif
- /*
- * The BufReadCmd and FileReadCmd events intercept the reading process by
- * executing the associated commands instead.
- */
+ // The BufReadCmd and FileReadCmd events intercept the reading process by
+ // executing the associated commands instead.
if (!filtering && !read_stdin && !read_buffer) {
- pos_T pos;
-
- pos = curbuf->b_op_start;
+ orig_start = curbuf->b_op_start;
// Set '[ mark to the line above where the lines go (line 1 if zero).
curbuf->b_op_start.lnum = ((from == 0) ? 1 : from);
@@ -335,7 +332,7 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski
return aborting() ? FAIL : OK;
}
- curbuf->b_op_start = pos;
+ curbuf->b_op_start = orig_start;
}
if ((shortmess(SHM_OVER) || curbuf->b_help) && p_verbose == 0) {
@@ -576,9 +573,8 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski
++no_wait_return; // don't wait for return yet
- /*
- * Set '[ mark to the line above where the lines go (line 1 if zero).
- */
+ // Set '[ mark to the line above where the lines go (line 1 if zero).
+ orig_start = curbuf->b_op_start;
curbuf->b_op_start.lnum = ((from == 0) ? 1 : from);
curbuf->b_op_start.col = 0;
@@ -618,6 +614,7 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski
try_mac = (vim_strchr(p_ffs, 'm') != NULL);
try_dos = (vim_strchr(p_ffs, 'd') != NULL);
try_unix = (vim_strchr(p_ffs, 'x') != NULL);
+ curbuf->b_op_start = orig_start;
if (msg_scrolled == n) {
msg_scroll = m;
@@ -1888,13 +1885,13 @@ failed:
check_cursor_lnum();
beginline(BL_WHITE | BL_FIX); // on first non-blank
- /*
- * Set '[ and '] marks to the newly read lines.
- */
- curbuf->b_op_start.lnum = from + 1;
- curbuf->b_op_start.col = 0;
- curbuf->b_op_end.lnum = from + linecnt;
- curbuf->b_op_end.col = 0;
+ if (!cmdmod.lockmarks) {
+ // Set '[ and '] marks to the newly read lines.
+ curbuf->b_op_start.lnum = from + 1;
+ curbuf->b_op_start.col = 0;
+ curbuf->b_op_end.lnum = from + linecnt;
+ curbuf->b_op_end.col = 0;
+ }
}
msg_scroll = msg_save;
@@ -2252,6 +2249,8 @@ int buf_write(buf_T *buf, char_u *fname, char_u *sfname, linenr_T start, linenr_
int write_undo_file = FALSE;
context_sha256_T sha_ctx;
unsigned int bkc = get_bkc_value(buf);
+ const pos_T orig_start = buf->b_op_start;
+ const pos_T orig_end = buf->b_op_end;
if (fname == NULL || *fname == NUL) { // safety check
return FAIL;
@@ -2432,7 +2431,13 @@ int buf_write(buf_T *buf, char_u *fname, char_u *sfname, linenr_T start, linenr_
if (buf == NULL || (buf->b_ml.ml_mfp == NULL && !empty_memline)
|| did_cmd || nofile_err
|| aborting()) {
- --no_wait_return;
+ if (buf != NULL && cmdmod.lockmarks) {
+ // restore the original '[ and '] positions
+ buf->b_op_start = orig_start;
+ buf->b_op_end = orig_end;
+ }
+
+ no_wait_return--;
msg_scroll = msg_save;
if (nofile_err) {
emsg(_("E676: No matching autocommands for acwrite buffer"));
@@ -2513,6 +2518,11 @@ int buf_write(buf_T *buf, char_u *fname, char_u *sfname, linenr_T start, linenr_
}
}
+ if (cmdmod.lockmarks) {
+ // restore the original '[ and '] positions
+ buf->b_op_start = orig_start;
+ buf->b_op_end = orig_end;
+ }
if (shortmess(SHM_OVER) && !exiting) {
msg_scroll = FALSE; // overwrite previous file message
@@ -3308,7 +3318,7 @@ restore_backup:
if (end == 0
|| (lnum == end
&& (write_bin || !buf->b_p_fixeol)
- && (lnum == buf->b_no_eol_lnum
+ && ((write_bin && lnum == buf->b_no_eol_lnum)
|| (lnum == buf->b_ml.ml_line_count && !buf->b_p_eol)))) {
lnum++; // written the line, count it
no_eol = true;
@@ -3853,7 +3863,7 @@ void msg_add_lines(int insert_space, long lnum, off_T nchars)
*p++ = ' ';
}
if (shortmess(SHM_LINES)) {
- vim_snprintf((char *)p, IOSIZE - (p - IObuff), "%" PRId64 "L, %" PRId64 "C",
+ vim_snprintf((char *)p, IOSIZE - (p - IObuff), "%" PRId64 "L, %" PRId64 "B",
(int64_t)lnum, (int64_t)nchars);
} else {
vim_snprintf((char *)p, IOSIZE - (p - IObuff),
@@ -3861,7 +3871,7 @@ void msg_add_lines(int insert_space, long lnum, off_T nchars)
(int64_t)lnum);
p += STRLEN(p);
vim_snprintf((char *)p, IOSIZE - (p - IObuff),
- NGETTEXT("%" PRId64 " character", "%" PRId64 " characters", nchars),
+ NGETTEXT("%" PRId64 " byte", "%" PRId64 " bytes", nchars),
(int64_t)nchars);
}
}
diff --git a/src/nvim/generators/gen_keysets.lua b/src/nvim/generators/gen_keysets.lua
index 01d8c1d357..633c5da184 100644
--- a/src/nvim/generators/gen_keysets.lua
+++ b/src/nvim/generators/gen_keysets.lua
@@ -27,7 +27,8 @@ local defspipe = io.open(defs_file, 'wb')
local keysets = require'api.keysets'
local keywords = {
- register = true,
+ register = true;
+ default = true;
}
local function sanitize(key)
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 5565e17597..5d8a8ddbfe 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -59,25 +59,18 @@
static int curscript = 0;
FileDescriptor *scriptin[NSCRIPT] = { NULL };
-/*
- * These buffers are used for storing:
- * - stuffed characters: A command that is translated into another command.
- * - redo characters: will redo the last change.
- * - recorded characters: for the "q" command.
- *
- * The bytes are stored like in the typeahead buffer:
- * - K_SPECIAL introduces a special key (two more bytes follow). A literal
- * K_SPECIAL is stored as K_SPECIAL KS_SPECIAL KE_FILLER.
- * - CSI introduces a GUI termcap code (also when gui.in_use is FALSE,
- * otherwise switching the GUI on would make mappings invalid).
- * A literal CSI is stored as CSI KS_EXTRA KE_CSI.
- * These translations are also done on multi-byte characters!
- *
- * Escaping CSI bytes is done by the system-specific input functions, called
- * by ui_inchar().
- * Escaping K_SPECIAL is done by inchar().
- * Un-escaping is done by vgetc().
- */
+// These buffers are used for storing:
+// - stuffed characters: A command that is translated into another command.
+// - redo characters: will redo the last change.
+// - recorded characters: for the "q" command.
+//
+// The bytes are stored like in the typeahead buffer:
+// - K_SPECIAL introduces a special key (two more bytes follow). A literal
+// K_SPECIAL is stored as K_SPECIAL KS_SPECIAL KE_FILLER.
+// These translations are also done on multi-byte characters!
+//
+// Escaping K_SPECIAL is done by inchar().
+// Un-escaping is done by vgetc().
#define MINIMAL_SIZE 20 // minimal size for b_str
@@ -170,10 +163,11 @@ void free_buff(buffheader_T *buf)
xfree(p);
}
buf->bh_first.b_next = NULL;
+ buf->bh_curr = NULL;
}
/// Return the contents of a buffer as a single string.
-/// K_SPECIAL and CSI in the returned string are escaped.
+/// K_SPECIAL in the returned string is escaped.
///
/// @param dozero count == zero is not an error
static char_u *get_buffcont(buffheader_T *buffer, int dozero)
@@ -202,11 +196,9 @@ static char_u *get_buffcont(buffheader_T *buffer, int dozero)
return p;
}
-/*
- * Return the contents of the record buffer as a single string
- * and clear the record buffer.
- * K_SPECIAL and CSI in the returned string are escaped.
- */
+/// Return the contents of the record buffer as a single string
+/// and clear the record buffer.
+/// K_SPECIAL in the returned string is escaped.
char_u *get_recorded(void)
{
char_u *p;
@@ -236,10 +228,8 @@ char_u *get_recorded(void)
return p;
}
-/*
- * Return the contents of the redo buffer as a single string.
- * K_SPECIAL and CSI in the returned string are escaped.
- */
+/// Return the contents of the redo buffer as a single string.
+/// K_SPECIAL in the returned string is escaped.
char_u *get_inserted(void)
{
return get_buffcont(&redobuff, FALSE);
@@ -247,7 +237,7 @@ char_u *get_inserted(void)
/// Add string after the current block of the given buffer
///
-/// K_SPECIAL and CSI should have been escaped already.
+/// K_SPECIAL should have been escaped already.
///
/// @param[out] buf Buffer to add to.
/// @param[in] s String to add.
@@ -295,9 +285,23 @@ static void add_buff(buffheader_T *const buf, const char *const s, ptrdiff_t sle
}
}
-/*
- * Add number "n" to buffer "buf".
- */
+/// Delete "slen" bytes from the end of "buf".
+/// Only works when it was just added.
+static void delete_buff_tail(buffheader_T *buf, int slen)
+{
+ int len;
+
+ if (buf->bh_curr == NULL) {
+ return; // nothing to delete
+ }
+ len = (int)STRLEN(buf->bh_curr->b_str);
+ if (len >= slen) {
+ buf->bh_curr->b_str[len - slen] = NUL;
+ buf->bh_space += (size_t)slen;
+ }
+}
+
+/// Add number "n" to buffer "buf".
static void add_num_buff(buffheader_T *buf, long n)
{
char number[32];
@@ -305,10 +309,8 @@ static void add_num_buff(buffheader_T *buf, long n)
add_buff(buf, number, -1L);
}
-/*
- * Add character 'c' to buffer "buf".
- * Translates special keys, NUL, CSI, K_SPECIAL and multibyte characters.
- */
+/// Add character 'c' to buffer "buf".
+/// Translates special keys, NUL, K_SPECIAL and multibyte characters.
static void add_char_buff(buffheader_T *buf, int c)
{
uint8_t bytes[MB_MAXBYTES + 1];
@@ -340,12 +342,10 @@ static void add_char_buff(buffheader_T *buf, int c)
}
}
-/*
- * Get one byte from the read buffers. Use readbuf1 one first, use readbuf2
- * if that one is empty.
- * If advance == TRUE go to the next char.
- * No translation is done K_SPECIAL and CSI are escaped.
- */
+/// Get one byte from the read buffers. Use readbuf1 one first, use readbuf2
+/// if that one is empty.
+/// If advance == TRUE go to the next char.
+/// No translation is done K_SPECIAL is escaped.
static int read_readbuffers(int advance)
{
int c;
@@ -524,10 +524,8 @@ void restoreRedobuff(save_redo_T *save_redo)
old_redobuff = save_redo->sr_old_redobuff;
}
-/*
- * Append "s" to the redo buffer.
- * K_SPECIAL and CSI should already have been escaped.
- */
+/// Append "s" to the redo buffer.
+/// K_SPECIAL should already have been escaped.
void AppendToRedobuff(const char *s)
{
if (!block_redo) {
@@ -536,7 +534,7 @@ void AppendToRedobuff(const char *s)
}
/// Append to Redo buffer literally, escaping special characters with CTRL-V.
-/// K_SPECIAL and CSI are escaped as well.
+/// K_SPECIAL is escaped as well.
///
/// @param str String to append
/// @param len Length of `str` or -1 for up to the NUL.
@@ -584,10 +582,8 @@ void AppendToRedobuffLit(const char_u *str, int len)
}
}
-/*
- * Append a character to the redo buffer.
- * Translates special keys, NUL, CSI, K_SPECIAL and multibyte characters.
- */
+/// Append a character to the redo buffer.
+/// Translates special keys, NUL, K_SPECIAL and multibyte characters.
void AppendCharToRedobuff(int c)
{
if (!block_redo) {
@@ -605,17 +601,15 @@ void AppendNumberToRedobuff(long n)
}
}
-/*
- * Append string "s" to the stuff buffer.
- * CSI and K_SPECIAL must already have been escaped.
- */
+/// Append string "s" to the stuff buffer.
+/// K_SPECIAL must already have been escaped.
void stuffReadbuff(const char *s)
{
add_buff(&readbuf1, s, -1L);
}
/// Append string "s" to the redo stuff buffer.
-/// @remark CSI and K_SPECIAL must already have been escaped.
+/// @remark K_SPECIAL must already have been escaped.
void stuffRedoReadbuff(const char *s)
{
add_buff(&readbuf2, s, -1L);
@@ -626,11 +620,9 @@ void stuffReadbuffLen(const char *s, long len)
add_buff(&readbuf1, s, len);
}
-/*
- * Stuff "s" into the stuff buffer, leaving special key codes unmodified and
- * escaping other K_SPECIAL and CSI bytes.
- * Change CR, LF and ESC into a space.
- */
+/// Stuff "s" into the stuff buffer, leaving special key codes unmodified and
+/// escaping other K_SPECIAL bytes.
+/// Change CR, LF and ESC into a space.
void stuffReadbuffSpec(const char *s)
{
while (*s != NUL) {
@@ -648,10 +640,8 @@ void stuffReadbuffSpec(const char *s)
}
}
-/*
- * Append a character to the stuff buffer.
- * Translates special keys, NUL, CSI, K_SPECIAL and multibyte characters.
- */
+/// Append a character to the stuff buffer.
+/// Translates special keys, NUL, K_SPECIAL and multibyte characters.
void stuffcharReadbuff(int c)
{
add_char_buff(&readbuf1, c);
@@ -665,12 +655,12 @@ void stuffnumReadbuff(long n)
add_num_buff(&readbuf1, n);
}
-// Read a character from the redo buffer. Translates K_SPECIAL, CSI and
-// multibyte characters.
-// The redo buffer is left as it is.
-// If init is true, prepare for redo, return FAIL if nothing to redo, OK
-// otherwise.
-// If old_redo is true, use old_redobuff instead of redobuff.
+/// Read a character from the redo buffer. Translates K_SPECIAL and
+/// multibyte characters.
+/// The redo buffer is left as it is.
+/// If init is true, prepare for redo, return FAIL if nothing to redo, OK
+/// otherwise.
+/// If old_redo is true, use old_redobuff instead of redobuff.
static int read_redo(bool init, bool old_redo)
{
static buffblock_T *bp;
@@ -724,9 +714,9 @@ static int read_redo(bool init, bool old_redo)
return c;
}
-// Copy the rest of the redo buffer into the stuff buffer (in a slow way).
-// If old_redo is true, use old_redobuff instead of redobuff.
-// The escaped K_SPECIAL and CSI are copied without translation.
+/// Copy the rest of the redo buffer into the stuff buffer (in a slow way).
+/// If old_redo is true, use old_redobuff instead of redobuff.
+/// The escaped K_SPECIAL is copied without translation.
static void copy_redo(bool old_redo)
{
int c;
@@ -992,36 +982,45 @@ int ins_typebuf(char_u *str, int noremap, int offset, bool nottyped, bool silent
return OK;
}
-/*
- * Put character "c" back into the typeahead buffer.
- * Can be used for a character obtained by vgetc() that needs to be put back.
- * Uses cmd_silent, KeyTyped and KeyNoremap to restore the flags belonging to
- * the char.
- */
-void ins_char_typebuf(int c)
+/// Put character "c" back into the typeahead buffer.
+/// Can be used for a character obtained by vgetc() that needs to be put back.
+/// Uses cmd_silent, KeyTyped and KeyNoremap to restore the flags belonging to
+/// the char.
+/// @return the length of what was inserted
+int ins_char_typebuf(int c, int modifier)
{
- char_u buf[MB_MAXBYTES + 1];
- if (IS_SPECIAL(c)) {
+ char_u buf[MB_MAXBYTES * 3 + 4];
+ int len = 0;
+ if (modifier != 0) {
buf[0] = K_SPECIAL;
- buf[1] = (char_u)K_SECOND(c);
- buf[2] = (char_u)K_THIRD(c);
+ buf[1] = KS_MODIFIER;
+ buf[2] = (char_u)modifier;
buf[3] = NUL;
+ len = 3;
+ }
+ if (IS_SPECIAL(c)) {
+ buf[len] = K_SPECIAL;
+ buf[len + 1] = (char_u)K_SECOND(c);
+ buf[len + 2] = (char_u)K_THIRD(c);
+ buf[len + 3] = NUL;
} else {
- buf[utf_char2bytes(c, buf)] = NUL;
- char_u *p = buf;
- while (*p) {
- if ((uint8_t)(*p) == CSI || (uint8_t)(*p) == K_SPECIAL) {
- bool is_csi = (uint8_t)(*p) == CSI;
- memmove(p + 3, p + 1, STRLEN(p + 1) + 1);
+ char_u *p = buf + len;
+ int char_len = utf_char2bytes(c, p);
+ len += char_len;
+ // If the character contains K_SPECIAL bytes they need escaping.
+ for (int i = char_len; --i >= 0; p++) {
+ if ((uint8_t)(*p) == K_SPECIAL) {
+ memmove(p + 3, p + 1, (size_t)i);
*p++ = K_SPECIAL;
- *p++ = is_csi ? KS_EXTRA : KS_SPECIAL;
- *p++ = is_csi ? KE_CSI : KE_FILLER;
- } else {
- p++;
+ *p++ = KS_SPECIAL;
+ *p = KE_FILLER;
+ len += 2;
}
}
+ *p = NUL;
}
(void)ins_typebuf(buf, KeyNoremap, 0, !KeyTyped, cmd_silent);
+ return len;
}
/// Return TRUE if the typeahead buffer was changed (while waiting for a
@@ -1181,6 +1180,18 @@ static void gotchars(const char_u *chars, size_t len)
maptick++;
}
+/// Undo the last gotchars() for "len" bytes. To be used when putting a typed
+/// character back into the typeahead buffer, thus gotchars() will be called
+/// again.
+/// Only affects recorded characters.
+void ungetchars(int len)
+{
+ if (reg_recording != 0) {
+ delete_buff_tail(&recordbuff, len);
+ last_recorded_len -= (size_t)len;
+ }
+}
+
/*
* Sync undo. Called when typed characters are obtained from the typeahead
* buffer, or when a menu is used.
@@ -1427,15 +1438,13 @@ static void updatescript(int c)
}
}
-/*
- * Get the next input character.
- * Can return a special key or a multi-byte character.
- * Can return NUL when called recursively, use safe_vgetc() if that's not
- * wanted.
- * This translates escaped K_SPECIAL and CSI bytes to a K_SPECIAL or CSI byte.
- * Collects the bytes of a multibyte character into the whole character.
- * Returns the modifiers in the global "mod_mask".
- */
+/// Get the next input character.
+/// Can return a special key or a multi-byte character.
+/// Can return NUL when called recursively, use safe_vgetc() if that's not
+/// wanted.
+/// This translates escaped K_SPECIAL bytes to a K_SPECIAL byte.
+/// Collects the bytes of a multibyte character into the whole character.
+/// Returns the modifiers in the global "mod_mask".
int vgetc(void)
{
int c, c2;
@@ -1461,8 +1470,9 @@ int vgetc(void)
mouse_row = old_mouse_row;
mouse_col = old_mouse_col;
} else {
- mod_mask = 0x0;
+ mod_mask = 0;
last_recorded_len = 0;
+
for (;;) { // this is done twice if there are modifiers
bool did_inc = false;
if (mod_mask) { // no mapping after modifier has been read
@@ -1572,14 +1582,9 @@ int vgetc(void)
buf[i] = (char_u)vgetorpeek(true);
if (buf[i] == K_SPECIAL) {
// Must be a K_SPECIAL - KS_SPECIAL - KE_FILLER sequence,
- // which represents a K_SPECIAL (0x80),
- // or a CSI - KS_EXTRA - KE_CSI sequence, which represents
- // a CSI (0x9B),
- // of a K_SPECIAL - KS_EXTRA - KE_CSI, which is CSI too.
- c = vgetorpeek(true);
- if (vgetorpeek(true) == KE_CSI && c == KS_EXTRA) {
- buf[i] = CSI;
- }
+ // which represents a K_SPECIAL (0x80).
+ (void)vgetorpeek(true); // skip KS_SPECIAL
+ (void)vgetorpeek(true); // skip KE_FILLER
}
}
no_mapping--;
@@ -1593,8 +1598,9 @@ int vgetc(void)
if (!no_mapping && KeyTyped && !(State & TERM_FOCUS)
&& (mod_mask == MOD_MASK_ALT || mod_mask == MOD_MASK_META)) {
mod_mask = 0;
- ins_char_typebuf(c);
- ins_char_typebuf(ESC);
+ int len = ins_char_typebuf(c, 0);
+ (void)ins_char_typebuf(ESC, 0);
+ ungetchars(len + 3); // The ALT/META modifier takes three more bytes
continue;
}
@@ -2043,7 +2049,7 @@ void vungetc(int c)
///
/// When `no_mapping` (global) is zero, checks for mappings in the current mode.
/// Only returns one byte (of a multi-byte character).
-/// K_SPECIAL and CSI may be escaped, need to get two more bytes then.
+/// K_SPECIAL may be escaped, need to get two more bytes then.
static int vgetorpeek(bool advance)
{
int c, c1;
@@ -2298,6 +2304,10 @@ static int vgetorpeek(bool advance)
c = ESC;
}
tc = c;
+
+ // no chars to block abbreviations for
+ typebuf.tb_no_abbr_cnt = 0;
+
break;
}
@@ -2573,7 +2583,7 @@ int fix_input_buffer(char_u *buf, int len)
FUNC_ATTR_NONNULL_ALL
{
if (!using_script()) {
- // Should not escape K_SPECIAL/CSI reading input from the user because vim
+ // Should not escape K_SPECIAL reading input from the user because vim
// key codes keys are processed in input.c/input_enqueue.
buf[len] = NUL;
return len;
@@ -2584,9 +2594,8 @@ int fix_input_buffer(char_u *buf, int len)
char_u *p = buf;
// Two characters are special: NUL and K_SPECIAL.
- // Replace NUL by K_SPECIAL KS_ZERO KE_FILLER
+ // Replace NUL by K_SPECIAL KS_ZERO KE_FILLER
// Replace K_SPECIAL by K_SPECIAL KS_SPECIAL KE_FILLER
- // Replace CSI by K_SPECIAL KS_EXTRA KE_CSI
for (i = len; --i >= 0; ++p) {
if (p[0] == NUL
|| (p[0] == K_SPECIAL
@@ -2665,9 +2674,10 @@ void set_maparg_lhs_rhs(const char_u *orig_lhs, const size_t orig_lhs_len,
}
} else {
char tmp_buf[64];
+ // orig_rhs is not used for Lua mappings, but still needs to be a string.
+ mapargs->orig_rhs = xcalloc(1, sizeof(char_u));
+ mapargs->orig_rhs_len = 0;
// stores <lua>ref_no<cr> in map_str
- mapargs->orig_rhs_len = (size_t)vim_snprintf(S_LEN(tmp_buf), "<LUA>%d<CR>", rhs_lua);
- mapargs->orig_rhs = vim_strsave((char_u *)tmp_buf);
mapargs->rhs_len = (size_t)vim_snprintf(S_LEN(tmp_buf), "%c%c%c%d\r", K_SPECIAL,
(char_u)KEY2TERMCAP0(K_LUA), KEY2TERMCAP1(K_LUA),
rhs_lua);
@@ -3477,10 +3487,10 @@ static void showmap(mapblock_T *mp, bool local)
} else if (mp->m_str == NULL) {
msg_puts_attr("<Nop>", HL_ATTR(HLF_8));
} else {
- // Remove escaping of CSI, because "m_str" is in a format to be used
+ // Remove escaping of K_SPECIAL, because "m_str" is in a format to be used
// as typeahead.
char_u *s = vim_strsave(mp->m_str);
- vim_unescape_csi(s);
+ vim_unescape_ks(s);
msg_outtrans_special(s, false, 0);
xfree(s);
}
@@ -3860,9 +3870,9 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol)
int match;
if (strchr((const char *)mp->m_keys, K_SPECIAL) != NULL) {
- // Might have CSI escaped mp->m_keys.
+ // Might have K_SPECIAL escaped mp->m_keys.
q = vim_strsave(mp->m_keys);
- vim_unescape_csi(q);
+ vim_unescape_ks(q);
qlen = (int)STRLEN(q);
}
// find entries with right mode and keys
@@ -3908,7 +3918,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol)
int newlen = utf_char2bytes(c, tb + j);
tb[j + newlen] = NUL;
// Need to escape K_SPECIAL.
- char_u *escaped = vim_strsave_escape_csi(tb + j);
+ char_u *escaped = vim_strsave_escape_ks(tb + j);
if (escaped != NULL) {
newlen = (int)STRLEN(escaped);
memmove(tb + j, escaped, (size_t)newlen);
@@ -3961,11 +3971,11 @@ static char_u *eval_map_expr(mapblock_T *mp, int c)
int save_msg_col;
int save_msg_row;
- /* Remove escaping of CSI, because "str" is in a format to be used as
- * typeahead. */
+ // Remove escaping of K_SPECIAL, because "str" is in a format to be used as
+ // typeahead.
if (mp->m_luaref == LUA_NOREF) {
expr = vim_strsave(mp->m_str);
- vim_unescape_csi(expr);
+ vim_unescape_ks(expr);
}
save_cmd = save_cmdline_alloc();
@@ -4005,18 +4015,16 @@ static char_u *eval_map_expr(mapblock_T *mp, int c)
if (p == NULL) {
return NULL;
}
- // Escape CSI in the result to be able to use the string as typeahead.
- res = vim_strsave_escape_csi(p);
+ // Escape K_SPECIAL in the result to be able to use the string as typeahead.
+ res = vim_strsave_escape_ks(p);
xfree(p);
return res;
}
-/*
- * Copy "p" to allocated memory, escaping K_SPECIAL and CSI so that the result
- * can be put in the typeahead buffer.
- */
-char_u *vim_strsave_escape_csi(char_u *p)
+/// Copy "p" to allocated memory, escaping K_SPECIAL so that the result
+/// can be put in the typeahead buffer.
+char_u *vim_strsave_escape_ks(char_u *p)
{
// Need a buffer to hold up to three times as much. Four in case of an
// illegal utf-8 byte:
@@ -4031,7 +4039,7 @@ char_u *vim_strsave_escape_csi(char_u *p)
*d++ = *s++;
} else {
// Add character, possibly multi-byte to destination, escaping
- // CSI and K_SPECIAL. Be careful, it can be an illegal byte!
+ // K_SPECIAL. Be careful, it can be an illegal byte!
d = add_char2buf(utf_ptr2char(s), d);
s += utf_ptr2len(s);
}
@@ -4041,11 +4049,9 @@ char_u *vim_strsave_escape_csi(char_u *p)
return res;
}
-/*
- * Remove escaping from CSI and K_SPECIAL characters. Reverse of
- * vim_strsave_escape_csi(). Works in-place.
- */
-void vim_unescape_csi(char_u *p)
+/// Remove escaping from K_SPECIAL characters. Reverse of
+/// vim_strsave_escape_ks(). Works in-place.
+void vim_unescape_ks(char_u *p)
{
char_u *s = p, *d = p;
@@ -4053,10 +4059,6 @@ void vim_unescape_csi(char_u *p)
if (s[0] == K_SPECIAL && s[1] == KS_SPECIAL && s[2] == KE_FILLER) {
*d++ = K_SPECIAL;
s += 3;
- } else if ((s[0] == K_SPECIAL || s[0] == CSI)
- && s[1] == KS_EXTRA && s[2] == (int)KE_CSI) {
- *d++ = CSI;
- s += 3;
} else {
*d++ = *s++;
}
@@ -4299,7 +4301,7 @@ int put_escstr(FILE *fd, char_u *strstart, int what)
for (; *str != NUL; str++) {
// Check for a multi-byte character, which may contain escaped
- // K_SPECIAL and CSI bytes.
+ // K_SPECIAL bytes.
const char *p = mb_unescape((const char **)&str);
if (p != NULL) {
while (*p != NUL) {
diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h
index be10e150e5..f24a4e7c7c 100644
--- a/src/nvim/getchar.h
+++ b/src/nvim/getchar.h
@@ -55,7 +55,7 @@ struct map_arguments {
char_u *orig_rhs; /// The original text of the {rhs}.
size_t orig_rhs_len;
- char *desc; /// map escription
+ char *desc; /// map description
};
typedef struct map_arguments MapArguments;
#define MAP_ARGUMENTS_INIT { false, false, false, false, false, false, false, \
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 697d4b11a7..98a38c5fe2 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -28,7 +28,7 @@
#endif
#ifndef FILETYPE_FILE
-# define FILETYPE_FILE "filetype.vim"
+# define FILETYPE_FILE "filetype.lua filetype.vim"
#endif
#ifndef FTPLUGIN_FILE
@@ -127,7 +127,7 @@ typedef off_t off_T;
// When vgetc() is called, it sets mod_mask to the set of modifiers that are
// held down based on the MOD_MASK_* symbols that are read first.
-EXTERN int mod_mask INIT(= 0x0); // current key modifiers
+EXTERN int mod_mask INIT(= 0); // current key modifiers
// Cmdline_row is the row where the command line starts, just below the
@@ -524,6 +524,8 @@ EXTERN pos_T VIsual;
EXTERN int VIsual_active INIT(= false);
/// Whether Select mode is active.
EXTERN int VIsual_select INIT(= false);
+/// Register name for Select mode
+EXTERN int VIsual_select_reg INIT(= 0);
/// Restart Select mode when next cmd finished
EXTERN int restart_VIsual_select INIT(= 0);
/// Whether to restart the selection after a Select-mode mapping or menu.
@@ -977,6 +979,7 @@ EXTERN char e_invalidreg[] INIT(= N_("E850: Invalid register name"));
EXTERN char e_dirnotf[] INIT(= N_("E919: Directory not found in '%s': \"%s\""));
EXTERN char e_au_recursive[] INIT(= N_("E952: Autocommand caused recursive behavior"));
EXTERN char e_autocmd_close[] INIT(= N_("E813: Cannot close autocmd window"));
+EXTERN char e_listarg[] INIT(= N_("E686: Argument of %s must be a List"));
EXTERN char e_unsupportedoption[] INIT(= N_("E519: Option not supported"));
EXTERN char e_fnametoolong[] INIT(= N_("E856: Filename too long"));
EXTERN char e_float_as_string[] INIT(= N_("E806: using Float as a String"));
@@ -997,6 +1000,8 @@ EXTERN char e_non_empty_string_required[] INIT(= N_("E1142: Non-empty string req
EXTERN char e_cannot_define_autocommands_for_all_events[] INIT(= N_("E1155: Cannot define autocommands for ALL events"));
+EXTERN char e_resulting_text_too_long[] INIT(= N_("E1240: Resulting text too long"));
+
EXTERN char e_highlight_group_name_too_long[] INIT(= N_("E1249: Highlight group name too long"));
EXTERN char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM"));
diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c
index 3050ca02de..fcd91cdf16 100644
--- a/src/nvim/highlight.c
+++ b/src/nvim/highlight.c
@@ -144,13 +144,19 @@ int hl_get_syn_attr(int ns_id, int idx, HlAttrs at_en)
}
}
-void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id)
+void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id, Dict(highlight) *dict)
{
- DecorProvider *p = get_decor_provider(ns_id, true);
if ((attrs.rgb_ae_attr & HL_DEFAULT)
&& map_has(ColorKey, ColorItem)(&ns_hl, ColorKey(ns_id, hl_id))) {
return;
}
+ if (ns_id == 0) {
+ assert(dict);
+ // set in global (':highlight') namespace
+ set_hl_group(hl_id, attrs, dict, link_id);
+ return;
+ }
+ DecorProvider *p = get_decor_provider(ns_id, true);
int attr_id = link_id > 0 ? -1 : hl_get_syn_attr(ns_id, hl_id, attrs);
ColorItem it = { .attr_id = attr_id,
.link_id = link_id,
@@ -192,23 +198,17 @@ int ns_get_hl(NS ns_id, int hl_id, bool link, bool nodefault)
int tmp = false;
HlAttrs attrs = HLATTRS_INIT;
if (ret.type == kObjectTypeDictionary) {
- Dictionary dict = ret.data.dictionary;
fallback = false;
- attrs = dict2hlattrs(dict, true, &it.link_id, &err);
- for (size_t i = 0; i < dict.size; i++) {
- char *key = dict.items[i].key.data;
- Object val = dict.items[i].value;
- bool truthy = api_object_to_bool(val, key, false, &err);
-
- if (strequal(key, "fallback")) {
- fallback = truthy;
- } else if (strequal(key, "temp")) {
- tmp = truthy;
+ Dict(highlight) dict = { 0 };
+ if (api_dict_to_keydict(&dict, KeyDict_highlight_get_field,
+ ret.data.dictionary, &err)) {
+ attrs = dict2hlattrs(&dict, true, &it.link_id, &err);
+ fallback = api_object_to_bool(dict.fallback, "fallback", true, &err);
+ tmp = api_object_to_bool(dict.fallback, "tmp", false, &err);
+ if (it.link_id >= 0) {
+ fallback = true;
}
}
- if (it.link_id >= 0) {
- fallback = true;
- }
}
it.attr_id = fallback ? -1 : hl_get_syn_attr((int)ns_id, hl_id, attrs);
@@ -796,112 +796,102 @@ Dictionary hlattrs2dict(HlAttrs ae, bool use_rgb)
return hl;
}
-HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err)
+HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *err)
{
HlAttrs hlattrs = HLATTRS_INIT;
-
int32_t fg = -1, bg = -1, ctermfg = -1, ctermbg = -1, sp = -1;
int16_t mask = 0;
int16_t cterm_mask = 0;
bool cterm_mask_provided = false;
- for (size_t i = 0; i < dict.size; i++) {
- char *key = dict.items[i].key.data;
- Object val = dict.items[i].value;
-
- struct {
- const char *name;
- int16_t flag;
- } flags[] = {
- { "bold", HL_BOLD },
- { "standout", HL_STANDOUT },
- { "underline", HL_UNDERLINE },
- { "undercurl", HL_UNDERCURL },
- { "italic", HL_ITALIC },
- { "reverse", HL_INVERSE },
- { "default", HL_DEFAULT },
- { "global", HL_GLOBAL },
- { NULL, 0 },
- };
-
- int j;
- for (j = 0; flags[j].name; j++) {
- if (strequal(flags[j].name, key)) {
- if (api_object_to_bool(val, key, false, err)) {
- mask = mask | flags[j].flag;
- }
- break;
- }
+#define CHECK_FLAG(d, m, name, extra, flag) \
+ if (api_object_to_bool(d->name ## extra, #name, false, err)) { \
+ m = m | flag; \
}
- // Handle cterm attrs
- if (strequal(key, "cterm") && val.type == kObjectTypeDictionary) {
- cterm_mask_provided = true;
- Dictionary cterm_dict = val.data.dictionary;
- for (size_t l = 0; l < cterm_dict.size; l++) {
- char *cterm_dict_key = cterm_dict.items[l].key.data;
- Object cterm_dict_val = cterm_dict.items[l].value;
- for (int m = 0; flags[m].name; m++) {
- if (strequal(flags[m].name, cterm_dict_key)) {
- if (api_object_to_bool(cterm_dict_val, cterm_dict_key, false,
- err)) {
- cterm_mask |= flags[m].flag;
- }
- break;
- }
- }
+ CHECK_FLAG(dict, mask, bold, , HL_BOLD);
+ CHECK_FLAG(dict, mask, standout, , HL_STANDOUT);
+ CHECK_FLAG(dict, mask, underline, , HL_UNDERLINE);
+ CHECK_FLAG(dict, mask, undercurl, , HL_UNDERCURL);
+ CHECK_FLAG(dict, mask, italic, , HL_ITALIC);
+ CHECK_FLAG(dict, mask, reverse, , HL_INVERSE);
+ CHECK_FLAG(dict, mask, strikethrough, , HL_STRIKETHROUGH);
+ CHECK_FLAG(dict, mask, nocombine, , HL_NOCOMBINE);
+ CHECK_FLAG(dict, mask, default, _, HL_DEFAULT);
+ CHECK_FLAG(dict, mask, global, , HL_GLOBAL);
+
+ if (HAS_KEY(dict->fg)) {
+ fg = object_to_color(dict->fg, "fg", err);
+ } else if (HAS_KEY(dict->foreground)) {
+ fg = object_to_color(dict->foreground, "foreground", err);
+ }
+ if (ERROR_SET(err)) {
+ return hlattrs;
+ }
+
+ if (HAS_KEY(dict->bg)) {
+ bg = object_to_color(dict->bg, "bg", err);
+ } else if (HAS_KEY(dict->background)) {
+ bg = object_to_color(dict->background, "background", err);
+ }
+ if (ERROR_SET(err)) {
+ return hlattrs;
+ }
+
+ if (HAS_KEY(dict->sp)) {
+ sp = object_to_color(dict->sp, "sp", err);
+ } else if (HAS_KEY(dict->special)) {
+ sp = object_to_color(dict->special, "special", err);
+ }
+ if (ERROR_SET(err)) {
+ return hlattrs;
+ }
+
+ if (HAS_KEY(dict->link)) {
+ if (link_id) {
+ *link_id = object_to_hl_id(dict->link, "link", err);
+ if (ERROR_SET(err)) {
+ return hlattrs;
}
+ } else {
+ api_set_error(err, kErrorTypeValidation, "Invalid Key: 'link'");
}
+ }
- struct {
- const char *name;
- const char *shortname;
- int *dest;
- } colors[] = {
- { "foreground", "fg", &fg },
- { "background", "bg", &bg },
- { "ctermfg", NULL, &ctermfg },
- { "ctermbg", NULL, &ctermbg },
- { "special", "sp", &sp },
- { NULL, NULL, NULL },
- };
-
- int k;
- for (k = 0; (!flags[j].name) && colors[k].name; k++) {
- if (strequal(colors[k].name, key) || strequal(colors[k].shortname, key)) {
- if (val.type == kObjectTypeInteger) {
- *colors[k].dest = (int)val.data.integer;
- } else if (val.type == kObjectTypeString) {
- String str = val.data.string;
- // TODO(bfredl): be more fancy with "bg", "fg" etc
- if (str.size) {
- *colors[k].dest = name_to_color(str.data);
- }
- } else {
- api_set_error(err, kErrorTypeValidation,
- "'%s' must be string or integer", key);
- }
- break;
- }
+ // Handle cterm attrs
+ if (dict->cterm.type == kObjectTypeDictionary) {
+ Dict(highlight_cterm) cterm[1] = { 0 };
+ if (!api_dict_to_keydict(cterm, KeyDict_highlight_cterm_get_field,
+ dict->cterm.data.dictionary, err)) {
+ return hlattrs;
}
- if (flags[j].name || colors[k].name) {
- // handled above
- } else if (link_id && strequal(key, "link")) {
- if (val.type == kObjectTypeString) {
- String str = val.data.string;
- *link_id = syn_check_group(str.data, (int)str.size);
- } else if (val.type == kObjectTypeInteger) {
- // TODO(bfredl): validate range?
- *link_id = (int)val.data.integer;
- } else {
- api_set_error(err, kErrorTypeValidation,
- "'link' must be string or integer");
- }
+ cterm_mask_provided = true;
+ CHECK_FLAG(cterm, cterm_mask, bold, , HL_BOLD);
+ CHECK_FLAG(cterm, cterm_mask, standout, , HL_STANDOUT);
+ CHECK_FLAG(cterm, cterm_mask, underline, , HL_UNDERLINE);
+ CHECK_FLAG(cterm, cterm_mask, undercurl, , HL_UNDERCURL);
+ CHECK_FLAG(cterm, cterm_mask, italic, , HL_ITALIC);
+ CHECK_FLAG(cterm, cterm_mask, reverse, , HL_INVERSE);
+ CHECK_FLAG(cterm, cterm_mask, strikethrough, , HL_STRIKETHROUGH);
+ CHECK_FLAG(cterm, cterm_mask, nocombine, , HL_NOCOMBINE);
+
+ } else if (HAS_KEY(dict->cterm)) {
+ api_set_error(err, kErrorTypeValidation, "'cterm' must be a Dictionary.");
+ }
+#undef CHECK_FLAG
+
+ if (HAS_KEY(dict->ctermfg)) {
+ ctermfg = object_to_color(dict->ctermfg, "ctermfg", err);
+ if (ERROR_SET(err)) {
+ return hlattrs;
}
+ }
+ if (HAS_KEY(dict->ctermbg)) {
+ ctermbg = object_to_color(dict->ctermbg, "ctermbg", err);
if (ERROR_SET(err)) {
- return hlattrs; // error set, caller should not use retval
+ return hlattrs;
}
}
@@ -927,6 +917,21 @@ HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err)
return hlattrs;
}
+
+int object_to_color(Object val, char *key, Error *err)
+{
+ if (val.type == kObjectTypeInteger) {
+ return (int)val.data.integer;
+ } else if (val.type == kObjectTypeString) {
+ String str = val.data.string;
+ // TODO(bfredl): be more fancy with "bg", "fg" etc
+ return str.size ? name_to_color(str.data) : 0;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "'%s' must be string or integer", key);
+ return 0;
+ }
+}
+
Array hl_inspect(int attr)
{
Array ret = ARRAY_DICT_INIT;
diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c
index daef8db267..9ca01137cf 100644
--- a/src/nvim/if_cscope.c
+++ b/src/nvim/if_cscope.c
@@ -919,8 +919,8 @@ static int cs_find(exarg_T *eap)
/// Common code for cscope find, shared by cs_find() and ex_cstag().
-static bool cs_find_common(char *opt, char *pat, int forceit, int verbose,
- bool use_ll, char_u *cmdline)
+static bool cs_find_common(char *opt, char *pat, int forceit, int verbose, bool use_ll,
+ char_u *cmdline)
{
char *cmd;
int *nummatches;
@@ -1594,7 +1594,6 @@ static char *cs_pathcomponents(char *path)
char *s = path + strlen(path) - 1;
for (int i = 0; i < p_cspc; i++) {
while (s > path && *--s != '/') {
- continue;
}
}
if ((s > path && *s == '/')) {
@@ -1813,7 +1812,6 @@ static int cs_read_prompt(size_t i)
static void sig_handler(int s)
{
// do nothing
- return;
}
#endif
diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c
index 0f0cab33ea..7f483d02ab 100644
--- a/src/nvim/indent_c.c
+++ b/src/nvim/indent_c.c
@@ -41,9 +41,7 @@ static pos_T *ind_find_start_comment(void) // XXX
pos_T *find_start_comment(int ind_maxcomment) // XXX
{
- pos_T *pos;
- char_u *line;
- char_u *p;
+ pos_T *pos;
int64_t cur_maxcomment = ind_maxcomment;
for (;; ) {
@@ -55,11 +53,9 @@ pos_T *find_start_comment(int ind_maxcomment) // XXX
* Check if the comment start we found is inside a string.
* If it is then restrict the search to below this line and try again.
*/
- line = ml_get(pos->lnum);
- for (p = line; *p && (colnr_T)(p - line) < pos->col; ++p)
- p = skip_string(p);
- if ((colnr_T)(p - line) <= pos->col)
+ if (!is_pos_in_string(ml_get(pos->lnum), pos->col)) {
break;
+ }
cur_maxcomment = curwin->w_cursor.lnum - pos->lnum - 1;
if (cur_maxcomment <= 0) {
pos = NULL;
@@ -110,8 +106,6 @@ static pos_T *ind_find_start_CORS(linenr_T *is_raw)
static pos_T *find_start_rawstring(int ind_maxcomment) // XXX
{
pos_T *pos;
- char_u *line;
- char_u *p;
long cur_maxcomment = ind_maxcomment;
for (;;)
@@ -124,11 +118,9 @@ static pos_T *find_start_rawstring(int ind_maxcomment) // XXX
* Check if the raw string start we found is inside a string.
* If it is then restrict the search to below this line and try again.
*/
- line = ml_get(pos->lnum);
- for (p = line; *p && (colnr_T)(p - line) < pos->col; ++p)
- p = skip_string(p);
- if ((colnr_T)(p - line) <= pos->col)
- break;
+ if (!is_pos_in_string(ml_get(pos->lnum), pos->col)) {
+ break;
+ }
cur_maxcomment = curwin->w_cursor.lnum - pos->lnum - 1;
if (cur_maxcomment <= 0)
{
@@ -143,7 +135,7 @@ static pos_T *find_start_rawstring(int ind_maxcomment) // XXX
* Skip to the end of a "string" and a 'c' character.
* If there is no string or character, return argument unmodified.
*/
-static char_u *skip_string(char_u *p)
+static const char_u *skip_string(const char_u *p)
{
int i;
@@ -152,11 +144,11 @@ static char_u *skip_string(char_u *p)
*/
for (;; p++) {
if (p[0] == '\'') { // 'c' or '\n' or '\000'
- if (!p[1]) { // ' at end of line
+ if (p[1] == NUL) { // ' at end of line
break;
}
i = 2;
- if (p[1] == '\\') { // '\n' or '\000'
+ if (p[1] == '\\' && p[2] != NUL) { // '\n' or '\000'
i++;
while (ascii_isdigit(p[i - 1])) { // '\000'
i++;
@@ -178,24 +170,24 @@ static char_u *skip_string(char_u *p)
continue; // continue for another string
}
} else if (p[0] == 'R' && p[1] == '"') {
- // Raw string: R"[delim](...)[delim]"
- char_u *delim = p + 2;
- char_u *paren = vim_strchr(delim, '(');
-
- if (paren != NULL) {
- const ptrdiff_t delim_len = paren - delim;
-
- for (p += 3; *p; ++p)
- if (p[0] == ')' && STRNCMP(p + 1, delim, delim_len) == 0
- && p[delim_len + 1] == '"')
- {
- p += delim_len + 1;
- break;
- }
- if (p[0] == '"') {
- continue; // continue for another string
- }
+ // Raw string: R"[delim](...)[delim]"
+ const char_u *delim = p + 2;
+ const char_u *paren = vim_strchr(delim, '(');
+
+ if (paren != NULL) {
+ const ptrdiff_t delim_len = paren - delim;
+
+ for (p += 3; *p; p++) {
+ if (p[0] == ')' && STRNCMP(p + 1, delim, delim_len) == 0
+ && p[delim_len + 1] == '"') {
+ p += delim_len + 1;
+ break;
+ }
+ }
+ if (p[0] == '"') {
+ continue; // continue for another string
}
+ }
}
break; // no string found
}
@@ -205,6 +197,16 @@ static char_u *skip_string(char_u *p)
return p;
}
+/// @returns true if "line[col]" is inside a C string.
+int is_pos_in_string(const char_u *line, colnr_T col)
+{
+ const char_u *p;
+
+ for (p = line; *p && (colnr_T)(p - line) < col; p++) {
+ p = skip_string(p);
+ }
+ return !((colnr_T)(p - line) <= col);
+}
/*
* Functions for C-indenting.
@@ -218,7 +220,7 @@ static char_u *skip_string(char_u *p)
/*
* Return true if the string "line" starts with a word from 'cinwords'.
*/
-bool cin_is_cinword(char_u *line)
+bool cin_is_cinword(const char_u *line)
{
bool retval = false;
@@ -246,10 +248,10 @@ bool cin_is_cinword(char_u *line)
* Skip over white space and C comments within the line.
* Also skip over Perl/shell comments if desired.
*/
-static char_u *cin_skipcomment(char_u *s)
+static const char_u *cin_skipcomment(const char_u *s)
{
while (*s) {
- char_u *prev_s = s;
+ const char_u *prev_s = s;
s = skipwhite(s);
@@ -283,7 +285,7 @@ static char_u *cin_skipcomment(char_u *s)
* Return TRUE if there is no code at *s. White space and comments are
* not considered code.
*/
-static int cin_nocode(char_u *s)
+static int cin_nocode(const char_u *s)
{
return *cin_skipcomment(s) == NUL;
}
@@ -312,9 +314,9 @@ static pos_T *find_line_comment(void) // XXX
}
/// Checks if `text` starts with "key:".
-static bool cin_has_js_key(char_u *text)
+static bool cin_has_js_key(const char_u *text)
{
- char_u *s = skipwhite(text);
+ const char_u *s = skipwhite(text);
char_u quote = 0;
if (*s == '\'' || *s == '"') {
@@ -341,7 +343,7 @@ static bool cin_has_js_key(char_u *text)
/// Checks if string matches "label:"; move to character after ':' if true.
/// "*s" must point to the start of the label, if there is one.
-static bool cin_islabel_skip(char_u **s)
+static bool cin_islabel_skip(const char_u **s)
FUNC_ATTR_NONNULL_ALL
{
if (!vim_isIDc(**s)) { // need at least one ID character
@@ -361,7 +363,7 @@ static bool cin_islabel_skip(char_u **s)
// Note: curwin->w_cursor must be where we are looking for the label.
bool cin_islabel(void) // XXX
{
- char_u *s = cin_skipcomment(get_cursor_line_ptr());
+ const char_u *s = cin_skipcomment(get_cursor_line_ptr());
// Exclude "default" from labels, since it should be indented
// like a switch label. Same for C++ scope declarations.
@@ -380,8 +382,8 @@ bool cin_islabel(void) // XXX
* label.
*/
pos_T cursor_save;
- pos_T *trypos;
- char_u *line;
+ pos_T *trypos;
+ const char_u *line;
cursor_save = curwin->w_cursor;
while (curwin->w_cursor.lnum > 1) {
@@ -424,8 +426,8 @@ bool cin_islabel(void) // XXX
*/
static int cin_isinit(void)
{
- char_u *s;
- static char *skip[] = {"static", "public", "protected", "private"};
+ const char_u *s;
+ static char *skip[] = { "static", "public", "protected", "private" };
s = cin_skipcomment(get_cursor_line_ptr());
@@ -460,7 +462,7 @@ static int cin_isinit(void)
* Recognize a switch label: "case .*:" or "default:".
*/
bool cin_iscase(
- char_u *s,
+ const char_u *s,
bool strict // Allow relaxed check of case statement for JS
)
{
@@ -503,7 +505,7 @@ bool cin_iscase(
/*
* Recognize a "default" switch label.
*/
-static int cin_isdefault(char_u *s)
+static int cin_isdefault(const char_u *s)
{
return STRNCMP(s, "default", 7) == 0
&& *(s = cin_skipcomment(s + 7)) == ':'
@@ -513,7 +515,7 @@ static int cin_isdefault(char_u *s)
/*
* Recognize a "public/private/protected" scope declaration label.
*/
-bool cin_isscopedecl(char_u *s)
+bool cin_isscopedecl(const char_u *s)
{
int i;
@@ -534,13 +536,18 @@ bool cin_isscopedecl(char_u *s)
#define FIND_NAMESPACE_LIM 20
// Recognize a "namespace" scope declaration.
-static bool cin_is_cpp_namespace(char_u *s)
+static bool cin_is_cpp_namespace(const char_u *s)
{
- char_u *p;
+ const char_u *p;
bool has_name = false;
bool has_name_start = false;
s = cin_skipcomment(s);
+
+ if (STRNCMP(s, "inline", 6) == 0 && (s[6] == NUL || !vim_iswordc(s[6]))) {
+ s = cin_skipcomment(skipwhite(s + 6));
+ }
+
if (STRNCMP(s, "namespace", 9) == 0 && (s[9] == NUL || !vim_iswordc(s[9]))) {
p = cin_skipcomment(skipwhite(s + 9));
while (*p != NUL) {
@@ -576,7 +583,7 @@ static bool cin_is_cpp_namespace(char_u *s)
* case 234: a = b;
* ^
*/
-static char_u *after_label(char_u *l)
+static const char_u *after_label(const char_u *l)
{
for (; *l; ++l) {
if (*l == ':') {
@@ -603,10 +610,10 @@ static char_u *after_label(char_u *l)
*/
static int get_indent_nolabel(linenr_T lnum) // XXX
{
- char_u *l;
+ const char_u *l;
pos_T fp;
colnr_T col;
- char_u *p;
+ const char_u *p;
l = ml_get(lnum);
p = after_label(l);
@@ -625,9 +632,9 @@ static int get_indent_nolabel(linenr_T lnum) // XXX
* label: if (asdf && asdfasdf)
* ^
*/
-static int skip_label(linenr_T lnum, char_u **pp)
+static int skip_label(linenr_T lnum, const char_u **pp)
{
- char_u *l;
+ const char_u *l;
int amount;
pos_T cursor_save;
@@ -708,8 +715,8 @@ static int cin_first_id_amount(void)
*/
static int cin_get_equal_amount(linenr_T lnum)
{
- char_u *line;
- char_u *s;
+ const char_u *line;
+ const char_u *s;
colnr_T col;
pos_T fp;
@@ -747,7 +754,7 @@ static int cin_get_equal_amount(linenr_T lnum)
/*
* Recognize a preprocessor statement: Any line that starts with '#'.
*/
-static int cin_ispreproc(char_u *s)
+static int cin_ispreproc(const char_u *s)
{
if (*skipwhite(s) == '#')
return TRUE;
@@ -758,9 +765,9 @@ static int cin_ispreproc(char_u *s)
/// continuation line of a preprocessor statement. Decrease "*lnump" to the
/// start and return the line in "*pp".
/// Put the amount of indent in "*amount".
-static int cin_ispreproc_cont(char_u **pp, linenr_T *lnump, int *amount)
+static int cin_ispreproc_cont(const char_u **pp, linenr_T *lnump, int *amount)
{
- char_u *line = *pp;
+ const char_u *line = *pp;
linenr_T lnum = *lnump;
int retval = false;
int candidate_amount = *amount;
@@ -794,7 +801,7 @@ static int cin_ispreproc_cont(char_u **pp, linenr_T *lnump, int *amount)
/*
* Recognize the start of a C or C++ comment.
*/
-static int cin_iscomment(char_u *p)
+static int cin_iscomment(const char_u *p)
{
return p[0] == '/' && (p[1] == '*' || p[1] == '/');
}
@@ -802,7 +809,7 @@ static int cin_iscomment(char_u *p)
/*
* Recognize the start of a "//" comment.
*/
-static int cin_islinecomment(char_u *p)
+static int cin_islinecomment(const char_u *p)
{
return p[0] == '/' && p[1] == '/';
}
@@ -817,8 +824,8 @@ static int cin_islinecomment(char_u *p)
* both apply in order to determine initializations).
*/
static char_u
-cin_isterminated (
- char_u *s,
+cin_isterminated(
+ const char_u *s,
int incl_open, // include '{' at the end as terminator
int incl_comma // recognize a trailing comma
)
@@ -867,9 +874,9 @@ cin_isterminated (
/// lines here.
/// @param[in] first_lnum Where to start looking.
/// @param[in] min_lnum The line before which we will not be looking.
-static int cin_isfuncdecl(char_u **sp, linenr_T first_lnum, linenr_T min_lnum)
+static int cin_isfuncdecl(const char_u **sp, linenr_T first_lnum, linenr_T min_lnum)
{
- char_u *s;
+ const char_u *s;
linenr_T lnum = first_lnum;
linenr_T save_lnum = curwin->w_cursor.lnum;
int retval = false;
@@ -970,12 +977,12 @@ done:
return retval;
}
-static int cin_isif(char_u *p)
+static int cin_isif(const char_u *p)
{
return STRNCMP(p, "if", 2) == 0 && !vim_isIDc(p[2]);
}
-static int cin_iselse(char_u *p)
+static int cin_iselse(const char_u *p)
{
if (*p == '}') { // accept "} else"
p = cin_skipcomment(p + 1);
@@ -983,7 +990,7 @@ static int cin_iselse(char_u *p)
return STRNCMP(p, "else", 4) == 0 && !vim_isIDc(p[4]);
}
-static int cin_isdo(char_u *p)
+static int cin_isdo(const char_u *p)
{
return STRNCMP(p, "do", 2) == 0 && !vim_isIDc(p[2]);
}
@@ -993,7 +1000,7 @@ static int cin_isdo(char_u *p)
* We only accept a "while (condition) ;", with only white space between the
* ')' and ';'. The condition may be spread over several lines.
*/
-static int cin_iswhileofdo(char_u *p, linenr_T lnum) // XXX
+static int cin_iswhileofdo(const char_u *p, linenr_T lnum) // XXX
{
pos_T cursor_save;
pos_T *trypos;
@@ -1027,7 +1034,7 @@ static int cin_iswhileofdo(char_u *p, linenr_T lnum) // XXX
* Otherwise return !0 and update "*poffset" to point to the place where the
* string was found.
*/
-static int cin_is_if_for_while_before_offset(char_u *line, int *poffset)
+static int cin_is_if_for_while_before_offset(const char_u *line, int *poffset)
{
int offset = *poffset;
@@ -1071,10 +1078,10 @@ probablyFound:
*/
static int cin_iswhileofdo_end(int terminated)
{
- char_u *line;
- char_u *p;
- char_u *s;
- pos_T *trypos;
+ const char_u *line;
+ const char_u *p;
+ const char_u *s;
+ pos_T *trypos;
int i;
if (terminated != ';') { // there must be a ';' at the end
@@ -1114,7 +1121,7 @@ static int cin_iswhileofdo_end(int terminated)
return FALSE;
}
-static int cin_isbreak(char_u *p)
+static int cin_isbreak(const char_u *p)
{
return STRNCMP(p, "break", 5) == 0 && !vim_isIDc(p[5]);
}
@@ -1134,10 +1141,10 @@ static int cin_isbreak(char_u *p)
*/
static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) {
lpos_T *pos = &cached->lpos; // find position
- char_u *s;
+ const char_u *s;
int class_or_struct, lookfor_ctor_init, cpp_base_class;
linenr_T lnum = curwin->w_cursor.lnum;
- char_u *line = get_cursor_line_ptr();
+ const char_u *line = get_cursor_line_ptr();
if (pos->lnum <= lnum) {
return cached->found; // Use the cached result
@@ -1305,10 +1312,10 @@ static int get_baseclass_amount(int col)
* white space and comments. Skip strings and comments.
* Ignore "ignore" after "find" if it's not NULL.
*/
-static int cin_ends_in(char_u *s, char_u *find, char_u *ignore)
+static int cin_ends_in(const char_u *s, const char_u *find, const char_u *ignore)
{
- char_u *p = s;
- char_u *r;
+ const char_u *p = s;
+ const char_u *r;
int len = (int)STRLEN(find);
while (*p != NUL) {
@@ -1329,7 +1336,7 @@ static int cin_ends_in(char_u *s, char_u *find, char_u *ignore)
/*
* Return TRUE when "s" starts with "word" and then a non-ID character.
*/
-static int cin_starts_with(char_u *s, char *word)
+static int cin_starts_with(const char_u *s, const char *word)
{
int l = (int)STRLEN(word);
@@ -1337,10 +1344,10 @@ static int cin_starts_with(char_u *s, char *word)
}
/// Recognize a `extern "C"` or `extern "C++"` linkage specifications.
-static int cin_is_cpp_extern_c(char_u *s)
+static int cin_is_cpp_extern_c(const char_u *s)
{
- char_u *p;
- int has_string_literal = false;
+ const char_u *p;
+ int has_string_literal = false;
s = cin_skipcomment(s);
if (STRNCMP(s, "extern", 6) == 0 && (s[6] == NUL || !vim_iswordc(s[6]))) {
@@ -1379,9 +1386,9 @@ static int cin_is_cpp_extern_c(char_u *s)
*/
static int cin_skip2pos(pos_T *trypos)
{
- char_u *line;
- char_u *p;
- char_u *new_p;
+ const char_u *line;
+ const char_u *p;
+ const char_u *new_p;
p = line = ml_get(trypos->lnum);
while (*p && (colnr_T)(p - line) < trypos->col) {
@@ -1411,8 +1418,8 @@ static int cin_skip2pos(pos_T *trypos)
static pos_T *find_start_brace(void) // XXX
{
pos_T cursor_save;
- pos_T *trypos;
- pos_T *pos;
+ pos_T *trypos;
+ pos_T *pos;
static pos_T pos_copy;
cursor_save = curwin->w_cursor;
@@ -1427,7 +1434,7 @@ static pos_T *find_start_brace(void) // XXX
break;
}
if (pos != NULL) {
- curwin->w_cursor.lnum = pos->lnum;
+ curwin->w_cursor = *pos;
}
}
curwin->w_cursor = cursor_save;
@@ -1524,7 +1531,7 @@ static int corr_ind_maxparen(pos_T *startpos)
* Set w_cursor.col to the column number of the last unmatched ')' or '{' in
* line "l". "l" must point to the start of the line.
*/
-static int find_last_paren(char_u *l, int start, int end)
+static int find_last_paren(const char_u *l, int start, int end)
{
int i;
int retval = FALSE;
@@ -1796,8 +1803,8 @@ int get_c_indent(void)
#define BRACE_AT_START 2 // '{' is at start of line
#define BRACE_AT_END 3 // '{' is at end of line
linenr_T ourscope;
- char_u *l;
- char_u *look;
+ const char_u *l;
+ const char_u *look;
char_u terminated;
int lookfor;
#define LOOKFOR_INITIAL 0
@@ -1901,12 +1908,25 @@ int get_c_indent(void)
* If we're inside a "//" comment and there is a "//" comment in a
* previous line, lineup with that one.
*/
- if (cin_islinecomment(theline)
- && (trypos = find_line_comment()) != NULL) { // XXX
- // find how indented the line beginning the comment is
- getvcol(curwin, trypos, &col, NULL, NULL);
- amount = col;
- goto theend;
+ if (cin_islinecomment(theline)) {
+ pos_T linecomment_pos;
+
+ trypos = find_line_comment(); // XXX
+ if (trypos == NULL && curwin->w_cursor.lnum > 1) {
+ // There may be a statement before the comment, search from the end
+ // of the line for a comment start.
+ linecomment_pos.col = check_linecomment(ml_get(curwin->w_cursor.lnum - 1));
+ if (linecomment_pos.col != MAXCOL) {
+ trypos = &linecomment_pos;
+ trypos->lnum = curwin->w_cursor.lnum - 1;
+ }
+ }
+ if (trypos != NULL) {
+ // find how indented the line beginning the comment is
+ getvcol(curwin, trypos, &col, NULL, NULL);
+ amount = col;
+ goto theend;
+ }
}
/*
* If we're inside a comment and not looking at the start of the
@@ -3597,9 +3617,9 @@ laterend:
static int find_match(int lookfor, linenr_T ourscope)
{
- char_u *look;
- pos_T *theirscope;
- char_u *mightbeif;
+ const char_u *look;
+ pos_T *theirscope;
+ const char_u *mightbeif;
int elselevel;
int whilelevel;
diff --git a/src/nvim/input.c b/src/nvim/input.c
index 2f7c5c2c16..5fa9b8b343 100644
--- a/src/nvim/input.c
+++ b/src/nvim/input.c
@@ -105,7 +105,7 @@ int get_keystroke(MultiQueue *events)
// terminal code to complete.
n = os_inchar(buf + len, maxlen, len == 0 ? -1L : 100L, 0, events);
if (n > 0) {
- // Replace zero and CSI by a special key code.
+ // Replace zero and K_SPECIAL by a special key code.
n = fix_input_buffer(buf + len, n);
len += n;
waited = 0;
diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c
index abf016b832..32f2158d7b 100644
--- a/src/nvim/keymap.c
+++ b/src/nvim/keymap.c
@@ -158,7 +158,6 @@ static const struct key_name_entry {
{ ESC, "Esc" },
{ ESC, "Escape" }, // Alternative name
{ CSI, "CSI" },
- { K_CSI, "xCSI" },
{ '|', "Bar" },
{ '\\', "Bslash" },
{ K_DEL, "Del" },
@@ -964,7 +963,6 @@ char_u *replace_termcodes(const char_u *from, const size_t from_len, char_u **bu
for (i = utfc_ptr2len_len(src, (int)(end - src) + 1); i > 0; i--) {
// If the character is K_SPECIAL, replace it with K_SPECIAL
// KS_SPECIAL KE_FILLER.
- // If compiled with the GUI replace CSI with K_CSI.
if (*src == K_SPECIAL) {
result[dlen++] = K_SPECIAL;
result[dlen++] = KS_SPECIAL;
diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h
index 5a74d1dc00..ae2ec7835e 100644
--- a/src/nvim/keymap.h
+++ b/src/nvim/keymap.h
@@ -122,8 +122,6 @@
//
// Entries must be in the range 0x02-0x7f (see comment at K_SPECIAL).
enum key_extra {
- KE_NAME = 3, // name of this terminal entry
-
KE_S_UP = 4, // shift-up
KE_S_DOWN = 5, // shift-down
@@ -220,7 +218,7 @@ enum key_extra {
KE_KINS = 79, // keypad Insert key
KE_KDEL = 80, // keypad Delete key
- KE_CSI = 81, // CSI typed directly
+ // KE_CSI = 81, // Nvim doesn't need escaping CSI
KE_SNR = 82, // <SNR>
KE_PLUG = 83, // <Plug>
KE_CMDWIN = 84, // open command-line window from Command-line Mode
@@ -435,7 +433,6 @@ enum key_extra {
#define K_MOUSELEFT TERMCAP2KEY(KS_EXTRA, KE_MOUSELEFT)
#define K_MOUSERIGHT TERMCAP2KEY(KS_EXTRA, KE_MOUSERIGHT)
-#define K_CSI TERMCAP2KEY(KS_EXTRA, KE_CSI)
#define K_SNR TERMCAP2KEY(KS_EXTRA, KE_SNR)
#define K_PLUG TERMCAP2KEY(KS_EXTRA, KE_PLUG)
#define K_CMDWIN TERMCAP2KEY(KS_EXTRA, KE_CMDWIN)
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
index 8a702ddd60..0fbd56ed53 100644
--- a/src/nvim/lua/converter.c
+++ b/src/nvim/lua/converter.c
@@ -617,6 +617,14 @@ bool nlua_push_typval(lua_State *lstate, typval_T *const tv, bool special)
semsg(_("E1502: Lua failed to grow stack to %i"), initial_size + 4);
return false;
}
+ if (tv->v_type == VAR_FUNC) {
+ ufunc_T *fp = find_func(tv->vval.v_string);
+ assert(fp != NULL);
+ if (fp->uf_cb == nlua_CFunction_func_call) {
+ nlua_pushref(lstate, ((LuaCFunctionState *)fp->uf_cb_state)->lua_callable.func_ref);
+ return true;
+ }
+ }
if (encode_vim_to_lua(lstate, tv, "nlua_push_typval argument") == FAIL) {
return false;
}
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index 7d43d21d53..cbfb8364f6 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -4,6 +4,7 @@
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
+#include <tree_sitter/api.h>
#include "luv/luv.h"
#include "nvim/api/private/defs.h"
@@ -434,6 +435,15 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
// [package, loaded, module]
lua_setfield(lstate, -2, "vim.F"); // [package, loaded]
+ code = (char *)&lua_filetype_module[0];
+ if (luaL_loadbuffer(lstate, code, sizeof(lua_filetype_module) - 1, "@vim/filetype.lua")
+ || nlua_pcall(lstate, 0, 1)) {
+ nlua_error(lstate, _("E5106: Error while creating vim.filetype module: %.*s"));
+ return 1;
+ }
+ // [package, loaded, module]
+ lua_setfield(lstate, -2, "vim.filetype"); // [package, loaded]
+
lua_pop(lstate, 2); // []
}
@@ -672,7 +682,7 @@ int nlua_call(lua_State *lstate)
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);
+ lua_pushvalue(lstate, i+2);
if (!nlua_pop_typval(lstate, &vim_args[i])) {
api_set_error(&err, kErrorTypeException,
"error converting argument %d", i+1);
@@ -737,7 +747,7 @@ static int nlua_rpc(lua_State *lstate, bool request)
Array args = ARRAY_DICT_INIT;
for (int i = 0; i < nargs; i++) {
- lua_pushvalue(lstate, (int)i+3);
+ lua_pushvalue(lstate, i+3);
ADD(args, nlua_pop_Object(lstate, false, &err));
if (ERROR_SET(&err)) {
api_free_array(args);
@@ -1115,11 +1125,23 @@ void ex_lua(exarg_T *const eap)
FUNC_ATTR_NONNULL_ALL
{
size_t len;
- char *const code = script_get(eap, &len);
+ char *code = script_get(eap, &len);
if (eap->skip) {
xfree(code);
return;
}
+ // When =expr is used transform it to print(vim.inspect(expr))
+ if (code[0] == '=') {
+ len += sizeof("vim.pretty_print()") - sizeof("=");
+ // code_buf needs to be 1 char larger then len for null byte in the end.
+ // lua nlua_typval_exec doesn't expect null terminated string so len
+ // needs to end before null byte.
+ char *code_buf = xmallocz(len);
+ vim_snprintf(code_buf, len+1, "vim.pretty_print(%s)", code+1);
+ xfree(code);
+ code = code_buf;
+ }
+
nlua_typval_exec(code, len, ":lua", NULL, 0, false, NULL);
xfree(code);
@@ -1246,6 +1268,12 @@ int tslua_get_language_version(lua_State *L)
return 1;
}
+int tslua_get_minimum_language_version(lua_State *L)
+{
+ lua_pushnumber(L, TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION);
+ return 1;
+}
+
static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
{
tslua_init(lstate);
@@ -1267,6 +1295,9 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
lua_pushcfunction(lstate, tslua_get_language_version);
lua_setfield(lstate, -2, "_ts_get_language_version");
+
+ lua_pushcfunction(lstate, tslua_get_minimum_language_version);
+ lua_setfield(lstate, -2, "_ts_get_minimum_language_version");
}
int nlua_expand_pat(expand_T *xp, char_u *pat, int *num_results, char_u ***results)
diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c
index 18a579ed0f..55b23cf0c8 100644
--- a/src/nvim/lua/stdlib.c
+++ b/src/nvim/lua/stdlib.c
@@ -25,6 +25,7 @@
#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
+#include "nvim/globals.h"
#include "nvim/lua/converter.h"
#include "nvim/lua/executor.h"
#include "nvim/lua/stdlib.h"
@@ -408,6 +409,12 @@ int nlua_getvar(lua_State *lstate)
const char *name = luaL_checklstring(lstate, 3, &len);
dictitem_T *di = tv_dict_find(dict, name, (ptrdiff_t)len);
+ if (di == NULL && dict == &globvardict) { // try to autoload script
+ if (!script_autoload(name, len, false) || aborting()) {
+ return 0; // nil
+ }
+ di = tv_dict_find(dict, name, (ptrdiff_t)len);
+ }
if (di == NULL) {
return 0; // nil
}
diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c
index 60a000843f..f4067ad02f 100644
--- a/src/nvim/lua/treesitter.c
+++ b/src/nvim/lua/treesitter.c
@@ -129,6 +129,10 @@ void tslua_init(lua_State *L)
build_meta(L, TS_META_QUERY, query_meta);
build_meta(L, TS_META_QUERYCURSOR, querycursor_meta);
build_meta(L, TS_META_TREECURSOR, treecursor_meta);
+
+#ifdef NVIM_TS_HAS_SET_ALLOCATOR
+ ts_set_allocator(xmalloc, xcalloc, xrealloc, xfree);
+#endif
}
int tslua_has_language(lua_State *L)
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
index f9b15d242a..f5993c3f55 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -40,6 +40,9 @@ assert(vim)
vim.inspect = package.loaded['vim.inspect']
assert(vim.inspect)
+vim.filetype = package.loaded['vim.filetype']
+assert(vim.filetype)
+
local pathtrails = {}
vim._so_trails = {}
for s in (package.cpath..';'):gmatch('[^;]*;') do
@@ -84,7 +87,8 @@ function vim._load_package(name)
return nil
end
-table.insert(package.loaders, 1, vim._load_package)
+-- Insert vim._load_package after the preloader at position 2
+table.insert(package.loaders, 2, vim._load_package)
-- These are for loading runtime modules lazily since they aren't available in
-- the nvim binary as specified in executor.c
@@ -112,6 +116,9 @@ setmetatable(vim, {
elseif key == 'ui' then
t.ui = require('vim.ui')
return t.ui
+ elseif key == 'keymap' then
+ t.keymap = require('vim.keymap')
+ return t.keymap
end
end
})
@@ -419,23 +426,43 @@ function vim.defer_fn(fn, timeout)
end
---- Notification provider
+--- Display a notification to the user.
+---
+--- This function can be overridden by plugins to display notifications using a
+--- custom provider (such as the system notification provider). By default,
+--- writes to |:messages|.
---
---- Without a runtime, writes to :Messages
----@see :help nvim_notify
----@param msg string Content of the notification to show to the user
----@param log_level number|nil enum from |vim.log.levels|
----@param opts table|nil additional options (timeout, etc)
-function vim.notify(msg, log_level, opts) -- luacheck: no unused
- if log_level == vim.log.levels.ERROR then
+---@param msg string Content of the notification to show to the user.
+---@param level number|nil One of the values from |vim.log.levels|.
+---@param opts table|nil Optional parameters. Unused by default.
+function vim.notify(msg, level, opts) -- luacheck: no unused args
+ if level == vim.log.levels.ERROR then
vim.api.nvim_err_writeln(msg)
- elseif log_level == vim.log.levels.WARN then
+ elseif level == vim.log.levels.WARN then
vim.api.nvim_echo({{msg, 'WarningMsg'}}, true, {})
else
vim.api.nvim_echo({{msg}}, true, {})
end
end
+do
+ local notified = {}
+
+ --- Display a notification only one time.
+ ---
+ --- Like |vim.notify()|, but subsequent calls with the same message will not
+ --- display a notification.
+ ---
+ ---@param msg string Content of the notification to show to the user.
+ ---@param level number|nil One of the values from |vim.log.levels|.
+ ---@param opts table|nil Optional parameters. Unused by default.
+ function vim.notify_once(msg, level, opts) -- luacheck: no unused args
+ if not notified[msg] then
+ vim.notify(msg, level, opts)
+ notified[msg] = true
+ end
+ end
+end
---@private
function vim.register_keystroke_callback()
@@ -663,4 +690,23 @@ vim._expand_pat_get_parts = function(lua_string)
return parts, search_index
end
+---Prints given arguments in human-readable format.
+---Example:
+---<pre>
+--- -- Print highlight group Normal and store it's contents in a variable.
+--- local hl_normal = vim.pretty_print(vim.api.nvim_get_hl_by_name("Normal", true))
+---</pre>
+---@see |vim.inspect()|
+---@return given arguments.
+function vim.pretty_print(...)
+ local objects = {}
+ for i = 1, select('#', ...) do
+ local v = select(i, ...)
+ table.insert(objects, vim.inspect(v))
+ end
+
+ print(table.concat(objects, ' '))
+ return ...
+end
+
return module
diff --git a/src/nvim/lua/xdiff.c b/src/nvim/lua/xdiff.c
index b2e971f9f3..ea7a700e1e 100644
--- a/src/nvim/lua/xdiff.c
+++ b/src/nvim/lua/xdiff.c
@@ -184,11 +184,11 @@ static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, xdemitconf_t *cfg,
if (strequal("myers", v->data.string.data)) {
// default
} else if (strequal("minimal", v->data.string.data)) {
- cfg->flags |= XDF_NEED_MINIMAL;
+ params->flags |= XDF_NEED_MINIMAL;
} else if (strequal("patience", v->data.string.data)) {
- cfg->flags |= XDF_PATIENCE_DIFF;
+ params->flags |= XDF_PATIENCE_DIFF;
} else if (strequal("histogram", v->data.string.data)) {
- cfg->flags |= XDF_HISTOGRAM_DIFF;
+ params->flags |= XDF_HISTOGRAM_DIFF;
} else {
api_set_error(err, kErrorTypeValidation, "not a valid algorithm");
goto exit_1;
diff --git a/src/nvim/macros.h b/src/nvim/macros.h
index c2b2c89abf..d5611f587b 100644
--- a/src/nvim/macros.h
+++ b/src/nvim/macros.h
@@ -105,6 +105,9 @@
#define MB_PTR_BACK(s, p) \
(p -= utf_head_off((char_u *)s, (char_u *)p - 1) + 1)
+// MB_CHAR2BYTES(): convert character to bytes and advance pointer to bytes
+#define MB_CHAR2BYTES(c, b) ((b) += utf_char2bytes((c), (b)))
+
#define RESET_BINDING(wp) \
do { \
(wp)->w_p_scb = false; \
diff --git a/src/nvim/main.c b/src/nvim/main.c
index cbd1f53727..8991ae7f00 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -341,9 +341,11 @@ int main(int argc, char **argv)
init_default_autocmds();
TIME_MSG("init default autocommands");
+ bool vimrc_none = params.use_vimrc != NULL && strequal(params.use_vimrc, "NONE");
+
// Reset 'loadplugins' for "-u NONE" before "--cmd" arguments.
// Allows for setting 'loadplugins' there.
- if (params.use_vimrc != NULL && strequal(params.use_vimrc, "NONE")) {
+ if (vimrc_none) {
// When using --clean we still want to load plugins
p_lpl = params.clean;
}
@@ -351,14 +353,23 @@ int main(int argc, char **argv)
// Execute --cmd arguments.
exe_pre_commands(&params);
+ if (!vimrc_none) {
+ // Sources ftplugin.vim and indent.vim. We do this *before* the user startup scripts to ensure
+ // ftplugins run before FileType autocommands defined in the init file (which allows those
+ // autocommands to overwrite settings from ftplugins).
+ filetype_plugin_enable();
+ }
+
// Source startup scripts.
source_startup_scripts(&params);
// If using the runtime (-u is not NONE), enable syntax & filetype plugins.
- if (params.use_vimrc == NULL || !strequal(params.use_vimrc, "NONE")) {
- // Does ":filetype plugin indent on".
+ if (!vimrc_none) {
+ // Sources filetype.lua and filetype.vim unless the user explicitly disabled it with :filetype
+ // off.
filetype_maybe_enable();
- // Sources syntax/syntax.vim, which calls `:filetype on`.
+ // Sources syntax/syntax.vim. We do this *after* the user startup scripts so that users can
+ // disable syntax highlighting with `:syntax off` if they wish.
syn_maybe_enable();
}
@@ -1549,7 +1560,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd)
// When w_arg_idx is -1 remove the window (see create_windows()).
if (curwin->w_arg_idx == -1) {
- win_close(curwin, true);
+ win_close(curwin, true, false);
advance = false;
}
@@ -1561,7 +1572,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd)
// When w_arg_idx is -1 remove the window (see create_windows()).
if (curwin->w_arg_idx == -1) {
arg_idx++;
- win_close(curwin, true);
+ win_close(curwin, true, false);
advance = false;
continue;
}
@@ -1608,7 +1619,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd)
did_emsg = FALSE; // avoid hit-enter prompt
getout(1);
}
- win_close(curwin, true);
+ win_close(curwin, true, false);
advance = false;
}
if (arg_idx == GARGCOUNT - 1) {
diff --git a/src/nvim/map.c b/src/nvim/map.c
index c77433df71..77ebc2a387 100644
--- a/src/nvim/map.c
+++ b/src/nvim/map.c
@@ -171,10 +171,7 @@ MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(uint64_t, ssize_t, SSIZE_INITIALIZER)
MAP_IMPL(uint64_t, uint64_t, DEFAULT_INITIALIZER)
-#define EXTMARK_NS_INITIALIZER { { MAP_INIT }, 1 }
-MAP_IMPL(uint64_t, ExtmarkNs, EXTMARK_NS_INITIALIZER)
-#define EXTMARK_ITEM_INITIALIZER { 0, 0, NULL }
-MAP_IMPL(uint64_t, ExtmarkItem, EXTMARK_ITEM_INITIALIZER)
+MAP_IMPL(uint32_t, uint32_t, DEFAULT_INITIALIZER)
MAP_IMPL(handle_T, ptr_t, DEFAULT_INITIALIZER)
#define MSGPACK_HANDLER_INITIALIZER { .fn = NULL, .fast = false }
MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER)
diff --git a/src/nvim/map.h b/src/nvim/map.h
index dbd85a4e1f..5e56f4dd65 100644
--- a/src/nvim/map.h
+++ b/src/nvim/map.h
@@ -40,16 +40,8 @@ MAP_DECLS(ptr_t, ptr_t)
MAP_DECLS(uint64_t, ptr_t)
MAP_DECLS(uint64_t, ssize_t)
MAP_DECLS(uint64_t, uint64_t)
+MAP_DECLS(uint32_t, uint32_t)
-// NB: this is the only way to define a struct both containing and contained
-// in a map...
-typedef struct ExtmarkNs { // For namespacing extmarks
- Map(uint64_t, uint64_t) map[1]; // For fast lookup
- uint64_t free_id; // For automatically assigning id's
-} ExtmarkNs;
-
-MAP_DECLS(uint64_t, ExtmarkNs)
-MAP_DECLS(uint64_t, ExtmarkItem)
MAP_DECLS(handle_T, ptr_t)
MAP_DECLS(String, MsgpackRpcRequestHandler)
MAP_DECLS(HlEntry, int)
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index 39f18b333d..8b29aa3676 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -217,8 +217,8 @@ void checkpcmark(void)
&& (equalpos(curwin->w_pcmark, curwin->w_cursor)
|| curwin->w_pcmark.lnum == 0)) {
curwin->w_pcmark = curwin->w_prev_pcmark;
- curwin->w_prev_pcmark.lnum = 0; // Show it has been checked
}
+ curwin->w_prev_pcmark.lnum = 0; // it has been checked
}
/*
diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c
index 38014ab375..918db8b76c 100644
--- a/src/nvim/marktree.c
+++ b/src/nvim/marktree.c
@@ -56,12 +56,6 @@
#define T MT_BRANCH_FACTOR
#define ILEN (sizeof(mtnode_t)+(2 * T) * sizeof(void *))
-#define RIGHT_GRAVITY (((uint64_t)1) << 63)
-#define ANTIGRAVITY(id) ((id)&(RIGHT_GRAVITY-1))
-#define IS_RIGHT(id) ((id)&RIGHT_GRAVITY)
-
-#define PAIRED MARKTREE_PAIRED_FLAG
-#define END_FLAG MARKTREE_END_FLAG
#define ID_INCR (((uint64_t)1) << 2)
#define rawkey(itr) (itr->node->key[itr->i])
@@ -119,7 +113,7 @@ static int key_cmp(mtkey_t a, mtkey_t b)
}
// NB: keeping the events at the same pos sorted by id is actually not
// necessary only make sure that START is before END etc.
- return mt_generic_cmp(a.id, b.id);
+ return mt_generic_cmp(a.flags, b.flags);
}
static inline int marktree_getp_aux(const mtnode_t *x, mtkey_t k, int *r)
@@ -148,7 +142,7 @@ static inline int marktree_getp_aux(const mtnode_t *x, mtkey_t k, int *r)
static inline void refkey(MarkTree *b, mtnode_t *x, int i)
{
- pmap_put(uint64_t)(b->id2node, ANTIGRAVITY(x->key[i].id), x);
+ pmap_put(uint64_t)(b->id2node, mt_lookup_key(x->key[i]), x);
}
// put functions
@@ -221,38 +215,28 @@ static inline void marktree_putp_aux(MarkTree *b, mtnode_t *x, mtkey_t k)
}
}
-uint64_t marktree_put(MarkTree *b, int row, int col, bool right_gravity, uint8_t decor_level)
+void marktree_put(MarkTree *b, mtkey_t key, int end_row, int end_col, bool end_right)
{
- uint64_t id = (b->next_id+=ID_INCR);
- assert(decor_level < DECOR_LEVELS);
- id = id | ((uint64_t)decor_level << DECOR_OFFSET);
- uint64_t keyid = id;
- if (right_gravity) {
- // order all right gravity keys after the left ones, for effortless
- // insertion (but not deletion!)
- keyid |= RIGHT_GRAVITY;
- }
- marktree_put_key(b, row, col, keyid);
- return id;
-}
+ assert(!(key.flags & ~MT_FLAG_EXTERNAL_MASK));
+ if (end_row >= 0) {
+ key.flags |= MT_FLAG_PAIRED;
+ }
-uint64_t marktree_put_pair(MarkTree *b, int start_row, int start_col, bool start_right, int end_row,
- int end_col, bool end_right, uint8_t decor_level)
-{
- uint64_t id = (b->next_id+=ID_INCR)|PAIRED;
- assert(decor_level < DECOR_LEVELS);
- id = id | ((uint64_t)decor_level << DECOR_OFFSET);
- uint64_t start_id = id|(start_right?RIGHT_GRAVITY:0);
- uint64_t end_id = id|END_FLAG|(end_right?RIGHT_GRAVITY:0);
- marktree_put_key(b, start_row, start_col, start_id);
- marktree_put_key(b, end_row, end_col, end_id);
- return id;
+ marktree_put_key(b, key);
+
+ if (end_row >= 0) {
+ mtkey_t end_key = key;
+ end_key.flags = (uint16_t)((uint16_t)(key.flags & ~MT_FLAG_RIGHT_GRAVITY)
+ |(uint16_t)MT_FLAG_END
+ |(uint16_t)(end_right ? MT_FLAG_RIGHT_GRAVITY : 0));
+ end_key.pos = (mtpos_t){ end_row, end_col };
+ marktree_put_key(b, end_key);
+ }
}
-void marktree_put_key(MarkTree *b, int row, int col, uint64_t id)
+void marktree_put_key(MarkTree *b, mtkey_t k)
{
- mtkey_t k = { .pos = { .row = row, .col = col }, .id = id };
-
+ k.flags |= MT_FLAG_REAL; // let's be real.
if (!b->root) {
b->root = (mtnode_t *)xcalloc(1, ILEN);
b->n_nodes++;
@@ -302,7 +286,7 @@ void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
mtnode_t *cur = itr->node;
int curi = itr->i;
- uint64_t id = cur->key[curi].id;
+ uint64_t id = mt_lookup_key(cur->key[curi]);
// fprintf(stderr, "\nDELET %lu\n", id);
if (itr->node->level) {
@@ -364,7 +348,7 @@ void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
}
b->n_keys--;
- pmap_del(uint64_t)(b->id2node, ANTIGRAVITY(id));
+ pmap_del(uint64_t)(b->id2node, id);
// 5.
bool itr_dirty = false;
@@ -570,23 +554,29 @@ void marktree_free_node(mtnode_t *x)
}
/// NB: caller must check not pair!
-uint64_t marktree_revise(MarkTree *b, MarkTreeIter *itr, uint8_t decor_level)
+void marktree_revise(MarkTree *b, MarkTreeIter *itr, uint8_t decor_level, mtkey_t key)
{
- uint64_t old_id = rawkey(itr).id;
- pmap_del(uint64_t)(b->id2node, ANTIGRAVITY(old_id));
- uint64_t new_id = (b->next_id += ID_INCR) + ((uint64_t)decor_level << DECOR_OFFSET);
- rawkey(itr).id = new_id + (RIGHT_GRAVITY&old_id);
- refkey(b, itr->node, itr->i);
- return new_id;
+ // TODO(bfredl): clean up this mess and re-instantiate &= and |= forms
+ // once we upgrade to a non-broken version of gcc in functionaltest-lua CI
+ rawkey(itr).flags = (uint16_t)((uint16_t)rawkey(itr).flags & (uint16_t)~MT_FLAG_DECOR_MASK);
+ rawkey(itr).flags = (uint16_t)((uint16_t)rawkey(itr).flags
+ | (uint16_t)(decor_level << MT_FLAG_DECOR_OFFSET)
+ | (uint16_t)(key.flags & MT_FLAG_DECOR_MASK));
+ rawkey(itr).decor_full = key.decor_full;
+ rawkey(itr).hl_id = key.hl_id;
+ rawkey(itr).priority = key.priority;
}
void marktree_move(MarkTree *b, MarkTreeIter *itr, int row, int col)
{
- uint64_t old_id = rawkey(itr).id;
+ mtkey_t key = rawkey(itr);
// TODO(bfredl): optimize when moving a mark within a leaf without moving it
// across neighbours!
marktree_del_itr(b, itr, false);
- marktree_put_key(b, row, col, old_id);
+ key.pos = (mtpos_t){ row, col };
+
+
+ marktree_put_key(b, key);
itr->node = NULL; // itr might become invalid by put
}
@@ -602,14 +592,15 @@ bool marktree_itr_get(MarkTree *b, int row, int col, MarkTreeIter *itr)
bool marktree_itr_get_ext(MarkTree *b, mtpos_t p, MarkTreeIter *itr, bool last, bool gravity,
mtpos_t *oldbase)
{
- mtkey_t k = { .pos = p, .id = gravity ? RIGHT_GRAVITY : 0 };
- if (last && !gravity) {
- k.id = UINT64_MAX;
- }
if (b->n_keys == 0) {
itr->node = NULL;
return false;
}
+
+ mtkey_t k = { .pos = p, .flags = gravity ? MT_FLAG_RIGHT_GRAVITY : 0 };
+ if (last && !gravity) {
+ k.flags = MT_FLAG_LAST;
+ }
itr->pos = (mtpos_t){ 0, 0 };
itr->node = b->root;
itr->lvl = 0;
@@ -816,25 +807,29 @@ mtpos_t marktree_itr_pos(MarkTreeIter *itr)
return pos;
}
-mtmark_t marktree_itr_current(MarkTreeIter *itr)
+mtkey_t marktree_itr_current(MarkTreeIter *itr)
{
if (itr->node) {
- uint64_t keyid = rawkey(itr).id;
- mtpos_t pos = marktree_itr_pos(itr);
- mtmark_t mark = { .row = pos.row,
- .col = pos.col,
- .id = ANTIGRAVITY(keyid),
- .right_gravity = keyid & RIGHT_GRAVITY };
- return mark;
- }
- return (mtmark_t){ -1, -1, 0, false };
+ mtkey_t key = rawkey(itr);
+ key.pos = marktree_itr_pos(itr);
+ return key;
+ }
+ return MT_INVALID_KEY;
+}
+
+static bool itr_eq(MarkTreeIter *itr1, MarkTreeIter *itr2)
+{
+ return (&rawkey(itr1) == &rawkey(itr2));
}
-static void swap_id(uint64_t *id1, uint64_t *id2)
+static void itr_swap(MarkTreeIter *itr1, MarkTreeIter *itr2)
{
- uint64_t temp = *id1;
- *id1 = *id2;
- *id2 = temp;
+ mtkey_t key1 = rawkey(itr1);
+ mtkey_t key2 = rawkey(itr2);
+ rawkey(itr1) = key2;
+ rawkey(itr1).pos = key1.pos;
+ rawkey(itr2) = key1;
+ rawkey(itr2).pos = key2.pos;
}
bool marktree_splice(MarkTree *b, int start_line, int start_col, int old_extent_line,
@@ -865,7 +860,7 @@ bool marktree_splice(MarkTree *b, int start_line, int start_col, int old_extent_
mtpos_t ipos = marktree_itr_pos(itr);
if (!pos_leq(old_extent, ipos)
|| (old_extent.row == ipos.row && old_extent.col == ipos.col
- && !IS_RIGHT(rawkey(itr).id))) {
+ && !mt_right(rawkey(itr)))) {
marktree_itr_get_ext(b, old_extent, enditr, true, true, NULL);
assert(enditr->node);
// "assert" (itr <= enditr)
@@ -895,13 +890,13 @@ continue_same_node:
break;
}
- if (IS_RIGHT(rawkey(itr).id)) {
- while (rawkey(itr).id != rawkey(enditr).id
- && IS_RIGHT(rawkey(enditr).id)) {
+ if (mt_right(rawkey(itr))) {
+ while (!itr_eq(itr, enditr)
+ && mt_right(rawkey(enditr))) {
marktree_itr_prev(b, enditr);
}
- if (!IS_RIGHT(rawkey(enditr).id)) {
- swap_id(&rawkey(itr).id, &rawkey(enditr).id);
+ if (!mt_right(rawkey(enditr))) {
+ itr_swap(itr, enditr);
refkey(b, itr->node, itr->i);
refkey(b, enditr->node, enditr->i);
} else {
@@ -911,7 +906,7 @@ continue_same_node:
}
}
- if (rawkey(itr).id == rawkey(enditr).id) {
+ if (itr_eq(itr, enditr)) {
// actually, will be past_right after this key
past_right = true;
}
@@ -1006,13 +1001,13 @@ void marktree_move_region(MarkTree *b, int start_row, colnr_T start_col, int ext
marktree_itr_get_ext(b, start, itr, false, true, NULL);
kvec_t(mtkey_t) saved = KV_INITIAL_VALUE;
while (itr->node) {
- mtpos_t pos = marktree_itr_pos(itr);
- if (!pos_leq(pos, end) || (pos.row == end.row && pos.col == end.col
- && rawkey(itr).id & RIGHT_GRAVITY)) {
+ mtkey_t k = marktree_itr_current(itr);
+ if (!pos_leq(k.pos, end) || (k.pos.row == end.row && k.pos.col == end.col
+ && mt_right(k))) {
break;
}
- relative(start, &pos);
- kv_push(saved, ((mtkey_t){ .pos = pos, .id = rawkey(itr).id }));
+ relative(start, &k.pos);
+ kv_push(saved, k);
marktree_del_itr(b, itr, false);
}
@@ -1024,30 +1019,36 @@ void marktree_move_region(MarkTree *b, int start_row, colnr_T start_col, int ext
for (size_t i = 0; i < kv_size(saved); i++) {
mtkey_t item = kv_A(saved, i);
unrelative(new, &item.pos);
- marktree_put_key(b, item.pos.row, item.pos.col, item.id);
+ marktree_put_key(b, item);
}
kv_destroy(saved);
}
/// @param itr OPTIONAL. set itr to pos.
-mtpos_t marktree_lookup(MarkTree *b, uint64_t id, MarkTreeIter *itr)
+mtkey_t marktree_lookup_ns(MarkTree *b, uint32_t ns, uint32_t id, bool end, MarkTreeIter *itr)
+{
+ return marktree_lookup(b, mt_lookup_id(ns, id, end), itr);
+}
+
+/// @param itr OPTIONAL. set itr to pos.
+mtkey_t marktree_lookup(MarkTree *b, uint64_t id, MarkTreeIter *itr)
{
mtnode_t *n = pmap_get(uint64_t)(b->id2node, id);
if (n == NULL) {
if (itr) {
itr->node = NULL;
}
- return (mtpos_t){ -1, -1 };
+ return MT_INVALID_KEY;
}
int i = 0;
for (i = 0; i < n->n; i++) {
- if (ANTIGRAVITY(n->key[i].id) == id) {
+ if (mt_lookup_key(n->key[i]) == id) {
goto found;
}
}
abort();
found: {}
- mtpos_t pos = n->key[i].pos;
+ mtkey_t key = n->key[i];
if (itr) {
itr->i = i;
itr->node = n;
@@ -1066,14 +1067,28 @@ found_node:
itr->s[b->root->level-p->level].i = i;
}
if (i > 0) {
- unrelative(p->key[i-1].pos, &pos);
+ unrelative(p->key[i-1].pos, &key.pos);
}
n = p;
}
if (itr) {
marktree_itr_fix_pos(b, itr);
}
- return pos;
+ return key;
+}
+
+mtpos_t marktree_get_altpos(MarkTree *b, mtkey_t mark, MarkTreeIter *itr)
+{
+ return marktree_get_alt(b, mark, itr).pos;
+}
+
+mtkey_t marktree_get_alt(MarkTree *b, mtkey_t mark, MarkTreeIter *itr)
+{
+ mtkey_t end = MT_INVALID_KEY;
+ if (mt_paired(mark)) {
+ end = marktree_lookup_ns(b, mark.ns, mark.id, !mt_end(mark), itr);
+ }
+ return end;
}
static void marktree_itr_fix_pos(MarkTree *b, MarkTreeIter *itr)
@@ -1092,6 +1107,20 @@ static void marktree_itr_fix_pos(MarkTree *b, MarkTreeIter *itr)
assert(x == itr->node);
}
+// for unit test
+void marktree_put_test(MarkTree *b, uint32_t id, int row, int col, bool right_gravity)
+{
+ mtkey_t key = { { row, col }, UINT32_MAX, id, 0,
+ mt_flags(right_gravity, 0), 0, NULL };
+ marktree_put(b, key, -1, -1, false);
+}
+
+// for unit test
+bool mt_right_test(mtkey_t key)
+{
+ return mt_right(key);
+}
+
void marktree_check(MarkTree *b)
{
#ifndef NDEBUG
@@ -1134,11 +1163,11 @@ static size_t check_node(MarkTree *b, mtnode_t *x, mtpos_t *last, bool *last_rig
}
assert(pos_leq(*last, x->key[i].pos));
if (last->row == x->key[i].pos.row && last->col == x->key[i].pos.col) {
- assert(!*last_right || IS_RIGHT(x->key[i].id));
+ assert(!*last_right || mt_right(x->key[i]));
}
- *last_right = IS_RIGHT(x->key[i].id);
+ *last_right = mt_right(x->key[i]);
assert(x->key[i].pos.col >= 0);
- assert(pmap_get(uint64_t)(b->id2node, ANTIGRAVITY(x->key[i].id)) == x);
+ assert(pmap_get(uint64_t)(b->id2node, mt_lookup_key(x->key[i])) == x);
}
if (x->level) {
diff --git a/src/nvim/marktree.h b/src/nvim/marktree.h
index a1dcdf5164..30f5aacebc 100644
--- a/src/nvim/marktree.h
+++ b/src/nvim/marktree.h
@@ -2,9 +2,12 @@
#define NVIM_MARKTREE_H
#include <stdint.h>
+#include <assert.h>
+#include "nvim/assert.h"
#include "nvim/garray.h"
#include "nvim/map.h"
+#include "nvim/types.h"
#include "nvim/pos.h"
#define MT_MAX_DEPTH 20
@@ -15,13 +18,6 @@ typedef struct {
int32_t col;
} mtpos_t;
-typedef struct {
- int32_t row;
- int32_t col;
- uint64_t id;
- bool right_gravity;
-} mtmark_t;
-
typedef struct mtnode_s mtnode_t;
typedef struct {
int oldcol;
@@ -39,12 +35,75 @@ typedef struct {
// Internal storage
//
-// NB: actual marks have id > 0, so we can use (row,col,0) pseudo-key for
+// NB: actual marks have flags > 0, so we can use (row,col,0) pseudo-key for
// "space before (row,col)"
typedef struct {
mtpos_t pos;
- uint64_t id;
+ uint32_t ns;
+ uint32_t id;
+ int32_t hl_id;
+ uint16_t flags;
+ uint16_t priority;
+ Decoration *decor_full;
} mtkey_t;
+#define MT_INVALID_KEY (mtkey_t) { { -1, -1 }, 0, 0, 0, 0, 0, NULL }
+
+#define MT_FLAG_REAL (((uint16_t)1) << 0)
+#define MT_FLAG_END (((uint16_t)1) << 1)
+#define MT_FLAG_PAIRED (((uint16_t)1) << 2)
+#define MT_FLAG_HL_EOL (((uint16_t)1) << 3)
+
+#define DECOR_LEVELS 4
+#define MT_FLAG_DECOR_OFFSET 4
+#define MT_FLAG_DECOR_MASK (((uint16_t)(DECOR_LEVELS-1)) << MT_FLAG_DECOR_OFFSET)
+
+// next flag is (((uint16_t)1) << 6)
+
+// These _must_ be last to preserve ordering of marks
+#define MT_FLAG_RIGHT_GRAVITY (((uint16_t)1) << 14)
+#define MT_FLAG_LAST (((uint16_t)1) << 15)
+
+#define MT_FLAG_EXTERNAL_MASK (MT_FLAG_DECOR_MASK | MT_FLAG_RIGHT_GRAVITY | MT_FLAG_HL_EOL)
+
+#define MARKTREE_END_FLAG (((uint64_t)1) << 63)
+static inline uint64_t mt_lookup_id(uint32_t ns, uint32_t id, bool enda)
+{
+ return (uint64_t)ns << 32 | id | (enda?MARKTREE_END_FLAG:0);
+}
+#undef MARKTREE_END_FLAG
+
+static inline uint64_t mt_lookup_key(mtkey_t key)
+{
+ return mt_lookup_id(key.ns, key.id, key.flags & MT_FLAG_END);
+}
+
+static inline bool mt_paired(mtkey_t key)
+{
+ return key.flags & MT_FLAG_PAIRED;
+}
+
+static inline bool mt_end(mtkey_t key)
+{
+ return key.flags & MT_FLAG_END;
+}
+
+static inline bool mt_right(mtkey_t key)
+{
+ return key.flags & MT_FLAG_RIGHT_GRAVITY;
+}
+
+static inline uint8_t marktree_decor_level(mtkey_t key)
+{
+ return (uint8_t)((key.flags&MT_FLAG_DECOR_MASK) >> MT_FLAG_DECOR_OFFSET);
+}
+
+static inline uint16_t mt_flags(bool right_gravity, uint8_t decor_level)
+{
+ assert(decor_level < DECOR_LEVELS);
+ return (uint16_t)((right_gravity ? MT_FLAG_RIGHT_GRAVITY : 0)
+ | (decor_level << MT_FLAG_DECOR_OFFSET));
+}
+
struct mtnode_s {
int32_t n;
@@ -61,7 +120,6 @@ struct mtnode_s {
typedef struct {
mtnode_t *root;
size_t n_keys, n_nodes;
- uint64_t next_id;
// TODO(bfredl): the pointer to node could be part of the larger
// Map(uint64_t, ExtmarkItem) essentially;
PMap(uint64_t) id2node[1];
@@ -72,16 +130,4 @@ typedef struct {
# include "marktree.h.generated.h"
#endif
-#define MARKTREE_PAIRED_FLAG (((uint64_t)1) << 1)
-#define MARKTREE_END_FLAG (((uint64_t)1) << 0)
-
-#define DECOR_LEVELS 4
-#define DECOR_OFFSET 61
-#define DECOR_MASK (((uint64_t)(DECOR_LEVELS-1)) << DECOR_OFFSET)
-
-static inline uint8_t marktree_decor_level(uint64_t id)
-{
- return (uint8_t)((id&DECOR_MASK) >> DECOR_OFFSET);
-}
-
#endif // NVIM_MARKTREE_H
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index 5eb209a6f6..e5fa80a242 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -1067,7 +1067,7 @@ bool utf_printable(int c)
static struct interval nonprint[] =
{
{ 0x070f, 0x070f }, { 0x180b, 0x180e }, { 0x200b, 0x200f }, { 0x202a, 0x202e },
- { 0x206a, 0x206f }, { 0xd800, 0xdfff }, { 0xfeff, 0xfeff }, { 0xfff9, 0xfffb },
+ { 0x2060, 0x206f }, { 0xd800, 0xdfff }, { 0xfeff, 0xfeff }, { 0xfff9, 0xfffb },
{ 0xfffe, 0xffff }
};
@@ -1821,12 +1821,10 @@ void mb_copy_char(const char_u **const fp, char_u **const tp)
*fp += l;
}
-/*
- * Return the offset from "p" to the first byte of a character. When "p" is
- * at the start of a character 0 is returned, otherwise the offset to the next
- * character. Can start anywhere in a stream of bytes.
- */
-int mb_off_next(char_u *base, char_u *p)
+/// Return the offset from "p" to the first byte of a character. When "p" is
+/// at the start of a character 0 is returned, otherwise the offset to the next
+/// character. Can start anywhere in a stream of bytes.
+int mb_off_next(const char_u *base, const char_u *p)
{
int i;
int j;
@@ -1854,7 +1852,7 @@ int mb_off_next(char_u *base, char_u *p)
/// Return the offset from "p" to the last byte of the character it points
/// into. Can start anywhere in a stream of bytes.
/// Composing characters are not included.
-int mb_tail_off(char_u *base, char_u *p)
+int mb_tail_off(const char_u *base, const char_u *p)
{
int i;
int j;
@@ -1882,12 +1880,13 @@ int mb_tail_off(char_u *base, char_u *p)
/// Return the offset from "p" to the first byte of the character it points
/// into. Can start anywhere in a stream of bytes.
+/// Unlike utf_head_off() this doesn't include composing characters and returns a negative value.
///
/// @param[in] base Pointer to start of string
/// @param[in] p Pointer to byte for which to return the offset to the previous codepoint
//
/// @return 0 if invalid sequence, else offset to previous codepoint
-int mb_head_off(char_u *base, char_u *p)
+int mb_head_off(const char_u *base, const char_u *p)
{
int i;
int j;
@@ -2037,13 +2036,11 @@ char_u *mb_prevptr(char_u *line, char_u *p)
return p;
}
-/*
- * Return the character length of "str". Each multi-byte character (with
- * following composing characters) counts as one.
- */
-int mb_charlen(char_u *str)
+/// Return the character length of "str". Each multi-byte character (with
+/// following composing characters) counts as one.
+int mb_charlen(const char_u *str)
{
- char_u *p = str;
+ const char_u *p = str;
int count;
if (p == NULL) {
@@ -2057,12 +2054,10 @@ int mb_charlen(char_u *str)
return count;
}
-/*
- * Like mb_charlen() but for a string with specified length.
- */
-int mb_charlen_len(char_u *str, int len)
+/// Like mb_charlen() but for a string with specified length.
+int mb_charlen_len(const char_u *str, int len)
{
- char_u *p = str;
+ const char_u *p = str;
int count;
for (count = 0; *p != NUL && p < str + len; count++) {
@@ -2089,8 +2084,7 @@ const char *mb_unescape(const char **const pp)
size_t buf_idx = 0;
uint8_t *str = (uint8_t *)(*pp);
- // Must translate K_SPECIAL KS_SPECIAL KE_FILLER to K_SPECIAL and CSI
- // KS_EXTRA KE_CSI to CSI.
+ // Must translate K_SPECIAL KS_SPECIAL KE_FILLER to K_SPECIAL.
// Maximum length of a utf-8 character is 4 bytes.
for (size_t str_idx = 0; str[str_idx] != NUL && buf_idx < 4; str_idx++) {
if (str[str_idx] == K_SPECIAL
@@ -2098,11 +2092,6 @@ const char *mb_unescape(const char **const pp)
&& str[str_idx + 2] == KE_FILLER) {
buf[buf_idx++] = (char)K_SPECIAL;
str_idx += 2;
- } else if ((str[str_idx] == K_SPECIAL)
- && str[str_idx + 1] == KS_EXTRA
- && str[str_idx + 2] == KE_CSI) {
- buf[buf_idx++] = (char)CSI;
- str_idx += 2;
} else if (str[str_idx] == K_SPECIAL) {
break; // A special key can't be a multibyte char.
} else {
@@ -2207,11 +2196,9 @@ char_u *enc_canonize(char_u *enc) FUNC_ATTR_NONNULL_RET
return r;
}
-/*
- * Search for an encoding alias of "name".
- * Returns -1 when not found.
- */
-static int enc_alias_search(char_u *name)
+/// Search for an encoding alias of "name".
+/// Returns -1 when not found.
+static int enc_alias_search(const char_u *name)
{
int i;
diff --git a/src/nvim/memfile.c b/src/nvim/memfile.c
index 3397296b3a..2a72d1e6a0 100644
--- a/src/nvim/memfile.c
+++ b/src/nvim/memfile.c
@@ -247,7 +247,7 @@ bhdr_T *mf_new(memfile_T *mfp, bool negative, unsigned page_count)
} else { // need to allocate memory for this block
// If the number of pages matches use the bhdr_T from the free list and
// allocate the data.
- void *p = xmalloc(mfp->mf_page_size * page_count);
+ void *p = xmalloc((size_t)mfp->mf_page_size * page_count);
hp = mf_rem_free(mfp);
hp->bh_data = p;
}
@@ -269,7 +269,7 @@ bhdr_T *mf_new(memfile_T *mfp, bool negative, unsigned page_count)
// Init the data to all zero, to avoid reading uninitialized data.
// This also avoids that the passwd file ends up in the swap file!
- (void)memset(hp->bh_data, 0, mfp->mf_page_size * page_count);
+ (void)memset(hp->bh_data, 0, (size_t)mfp->mf_page_size * page_count);
return hp;
}
@@ -528,7 +528,7 @@ bool mf_release_all(void)
static bhdr_T *mf_alloc_bhdr(memfile_T *mfp, unsigned page_count)
{
bhdr_T *hp = xmalloc(sizeof(bhdr_T));
- hp->bh_data = xmalloc(mfp->mf_page_size * page_count);
+ hp->bh_data = xmalloc((size_t)mfp->mf_page_size * page_count);
hp->bh_page_count = page_count;
return hp;
}
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index 08521c0dc3..9925971783 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -1032,9 +1032,9 @@ void ml_recover(bool checkext)
line_count = 0;
idx = 0; // start with first index in block 1
error = 0;
- buf->b_ml.ml_stack_top = 0;
+ buf->b_ml.ml_stack_top = 0; // -V1048
buf->b_ml.ml_stack = NULL;
- buf->b_ml.ml_stack_size = 0; // no stack yet
+ buf->b_ml.ml_stack_size = 0; // -V1048
if (curbuf->b_ffname == NULL) {
cannot_open = true;
@@ -4139,7 +4139,7 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff)
|| (offset != 0
&& offset > size +
buf->b_ml.ml_chunksize[curix].mlcs_totalsize
- + ffdos * buf->b_ml.ml_chunksize[curix].mlcs_numlines))) {
+ + (long)ffdos * buf->b_ml.ml_chunksize[curix].mlcs_numlines))) {
curline += buf->b_ml.ml_chunksize[curix].mlcs_numlines;
size += buf->b_ml.ml_chunksize[curix].mlcs_totalsize;
if (offset && ffdos) {
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 39b023132e..93742ccbdb 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -262,7 +262,6 @@ void msg_multiline_attr(const char *s, int attr, bool check_int, bool *need_clea
if (*s != NUL) {
msg_outtrans_attr((char_u *)s, attr);
}
- return;
}
@@ -329,7 +328,7 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline)
}
retval = msg_end();
- if (keep && retval && vim_strsize((char_u *)s) < (int)(Rows - cmdline_row - 1)
+ if (keep && retval && vim_strsize((char_u *)s) < (Rows - cmdline_row - 1)
* Columns + sc_col) {
set_keep_msg((char *)s, 0);
}
@@ -356,10 +355,10 @@ char_u *msg_strtrunc(char_u *s, int force)
len = vim_strsize(s);
if (msg_scrolled != 0) {
// Use all the columns.
- room = (int)(Rows - msg_row) * Columns - 1;
+ room = (Rows - msg_row) * Columns - 1;
} else {
// Use up to 'showcmd' column.
- room = (int)(Rows - msg_row - 1) * Columns + sc_col - 1;
+ room = (Rows - msg_row - 1) * Columns + sc_col - 1;
}
if (len > room && room > 0) {
// may have up to 18 bytes per cell (6 per char, up to two
@@ -873,7 +872,7 @@ char_u *msg_may_trunc(bool force, char_u *s)
{
int room;
- room = (int)(Rows - cmdline_row - 1) * Columns + sc_col - 1;
+ room = (Rows - cmdline_row - 1) * Columns + sc_col - 1;
if ((force || (shortmess(SHM_TRUNC) && !exmode_active))
&& (int)STRLEN(s) - room > 0) {
int size = vim_strsize(s);
@@ -1095,6 +1094,10 @@ void wait_return(int redraw)
return;
}
+ if (headless_mode && !ui_active()) {
+ return;
+ }
+
/*
* When inside vgetc(), we can't wait for a typed character at all.
* With the global command (and some others) we only need one return at
@@ -1215,7 +1218,7 @@ void wait_return(int redraw)
} else if (vim_strchr((char_u *)"\r\n ", c) == NULL && c != Ctrl_C) {
// Put the character back in the typeahead buffer. Don't use the
// stuff buffer, because lmaps wouldn't work.
- ins_char_typebuf(c);
+ ins_char_typebuf(c, mod_mask);
do_redraw = true; // need a redraw even though there is
// typeahead
}
@@ -2647,6 +2650,17 @@ static void msg_puts_printf(const char *str, const ptrdiff_t maxlen)
char buf[7];
char *p;
+ if (on_print.type != kCallbackNone) {
+ typval_T argv[1];
+ argv[0].v_type = VAR_STRING;
+ argv[0].v_lock = VAR_UNLOCKED;
+ argv[0].vval.v_string = (char_u *)str;
+ typval_T rettv = TV_INITIAL_VALUE;
+ callback_call(&on_print, 1, argv, &rettv);
+ tv_clear(&rettv);
+ return;
+ }
+
while ((maxlen < 0 || s - str < maxlen) && *s != NUL) {
int len = utf_ptr2len((const char_u *)s);
if (!(silent_mode && p_verbose == 0)) {
@@ -3497,7 +3511,7 @@ int do_dialog(int type, char_u *title, char_u *message, char_u *buttons, int dfl
}
if (c == ':' && ex_cmd) {
retval = dfltbutton;
- ins_char_typebuf(':');
+ ins_char_typebuf(':', 0);
break;
}
diff --git a/src/nvim/move.c b/src/nvim/move.c
index 67ec19903f..27cc2b341c 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -346,10 +346,10 @@ void update_topline(win_T *wp)
*/
void update_topline_win(win_T *win)
{
- win_T *save_curwin;
- switch_win(&save_curwin, NULL, win, NULL, true);
+ switchwin_T switchwin;
+ switch_win(&switchwin, win, NULL, true);
update_topline(curwin);
- restore_win(save_curwin, NULL, true);
+ restore_win(&switchwin, true);
}
/*
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 60bf393085..21c465434a 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -164,7 +164,7 @@ static const struct nv_cmd {
{ Ctrl_O, nv_ctrlo, 0, 0 },
{ Ctrl_P, nv_up, NV_STS, false },
{ Ctrl_Q, nv_visual, 0, false },
- { Ctrl_R, nv_redo, 0, 0 },
+ { Ctrl_R, nv_redo_or_register, 0, 0 },
{ Ctrl_S, nv_ignore, 0, 0 },
{ Ctrl_T, nv_tagpop, NV_NCW, 0 },
{ Ctrl_U, nv_halfpage, 0, 0 },
@@ -961,6 +961,7 @@ normal_end:
&& s->oa.regname == 0) {
if (restart_VIsual_select == 1) {
VIsual_select = true;
+ VIsual_select_reg = 0;
trigger_modechanged();
showmode();
restart_VIsual_select = 0;
@@ -1010,7 +1011,14 @@ static int normal_execute(VimState *state, int key)
// restart automatically.
// Insert the typed character in the typeahead buffer, so that it can
// be mapped in Insert mode. Required for ":lmap" to work.
- ins_char_typebuf(s->c);
+ int len = ins_char_typebuf(s->c, mod_mask);
+
+ // When recording and gotchars() was called the character will be
+ // recorded again, remove the previous recording.
+ if (KeyTyped) {
+ ungetchars(len);
+ }
+
if (restart_edit != 0) {
s->c = 'd';
} else {
@@ -1022,7 +1030,7 @@ static int normal_execute(VimState *state, int key)
s->need_flushbuf = add_to_showcmd(s->c);
- while (normal_get_command_count(s)) { continue; }
+ while (normal_get_command_count(s)) { }
if (s->c == K_EVENT) {
// Save the count values so that ca.opcount and ca.count0 are exactly
@@ -3090,8 +3098,14 @@ static void nv_gd(oparg_T *oap, int nchar, int thisblock)
if ((len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0
|| !find_decl(ptr, len, nchar == 'd', thisblock, SEARCH_START)) {
clearopbeep(oap);
- } else if ((fdo_flags & FDO_SEARCH) && KeyTyped && oap->op_type == OP_NOP) {
- foldOpenCursor();
+ } else {
+ if ((fdo_flags & FDO_SEARCH) && KeyTyped && oap->op_type == OP_NOP) {
+ foldOpenCursor();
+ }
+ // clear any search statistics
+ if (messaging() && !msg_silent && !shortmess(SHM_SEARCHCOUNT)) {
+ clear_cmdline = true;
+ }
}
}
@@ -3281,7 +3295,7 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist)
int col_off1; // margin offset for first screen line
int col_off2; // margin offset for wrapped screen line
int width1; // text width for first screen line
- int width2; // test width for wrapped screen line
+ int width2; // text width for wrapped screen line
oap->motion_type = kMTCharWise;
oap->inclusive = (curwin->w_curswant == MAXCOL);
@@ -3405,6 +3419,13 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist)
virtcol -= vim_strsize(get_showbreak_value(curwin));
}
+ int c = utf_ptr2char(get_cursor_pos_ptr());
+ if (dir == FORWARD && virtcol < curwin->w_curswant
+ && (curwin->w_curswant <= (colnr_T)width1)
+ && !vim_isprintc(c) && c > 255) {
+ oneright();
+ }
+
if (virtcol > curwin->w_curswant
&& (curwin->w_curswant < (colnr_T)width1
? (curwin->w_curswant > (colnr_T)width1 / 2)
@@ -4078,7 +4099,7 @@ static void nv_colon(cmdarg_T *cap)
if (is_lua) {
cmd_result = map_execute_lua();
} else {
- // get a command line and execute it
+ // get a command line and execute it
cmd_result = do_cmdline(NULL, is_cmdkey ? getcmdkeycmd : getexline, NULL,
cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0);
}
@@ -4437,11 +4458,7 @@ static void nv_ident(cmdarg_T *cap)
// Start insert mode in terminal buffer
restart_edit = 'i';
- add_map((char_u *)"<buffer> <esc> <Cmd>call jobstop(&channel)<CR>", TERM_FOCUS, true);
- do_cmdline_cmd("autocmd TermClose <buffer> "
- " if !v:event.status |"
- " exec 'bdelete! ' .. expand('<abuf>') |"
- " endif");
+ add_map((char_u *)"<buffer> <esc> <Cmd>bdelete!<CR>", TERM_FOCUS, true);
}
}
@@ -4476,8 +4493,13 @@ bool get_visual_text(cmdarg_T *cap, char_u **pp, size_t *lenp)
*pp = ml_get_pos(&VIsual);
*lenp = (size_t)curwin->w_cursor.col - (size_t)VIsual.col + 1;
}
- // Correct the length to include the whole last character.
- *lenp += (size_t)(utfc_ptr2len(*pp + (*lenp - 1)) - 1);
+ if (**pp == NUL) {
+ *lenp = 0;
+ }
+ if (*lenp > 0) {
+ // Correct the length to include all bytes of the last character.
+ *lenp += (size_t)(utfc_ptr2len(*pp + (*lenp - 1)) - 1);
+ }
}
reset_VIsual_and_resel();
return true;
@@ -5011,9 +5033,7 @@ static void nv_brackets(cmdarg_T *cap)
* identifier "]i" "[i" "]I" "[I" "]^I" "[^I"
* define "]d" "[d" "]D" "[D" "]^D" "[^D"
*/
- if (vim_strchr((char_u *)
- "iI\011dD\004",
- cap->nchar) != NULL) {
+ if (vim_strchr((char_u *)"iI\011dD\004", cap->nchar) != NULL) {
char_u *ptr;
size_t len;
@@ -5967,11 +5987,8 @@ static void nv_visual(cmdarg_T *cap)
* was only one -- webb
*/
if (resel_VIsual_mode != 'v' || resel_VIsual_line_count > 1) {
- curwin->w_cursor.lnum +=
- resel_VIsual_line_count * cap->count0 - 1;
- if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
- curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
- }
+ curwin->w_cursor.lnum += resel_VIsual_line_count * cap->count0 - 1;
+ check_cursor();
}
VIsual_mode = resel_VIsual_mode;
if (VIsual_mode == 'v') {
@@ -6050,7 +6067,7 @@ static void n_start_visual_mode(int c)
// Corner case: the 0 position in a tab may change when going into
// virtualedit. Recalculate curwin->w_cursor to avoid bad highlighting.
//
- if (c == Ctrl_V && (ve_flags & VE_BLOCK) && gchar_cursor() == TAB) {
+ if (c == Ctrl_V && (get_ve_flags() & VE_BLOCK) && gchar_cursor() == TAB) {
validate_virtcol();
coladvance(curwin->w_virtcol);
}
@@ -6190,6 +6207,7 @@ static void nv_g_cmd(cmdarg_T *cap)
// start Select mode.
if (cap->arg) {
VIsual_select = true;
+ VIsual_select_reg = 0;
} else {
may_start_select('c');
}
@@ -6316,20 +6334,17 @@ 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();
-
+ case 'M':
oap->motion_type = kMTCharWise;
oap->inclusive = false;
- i = (int)mb_string2cells_len(ptr, STRLEN(ptr));
+ i = linetabsize(get_cursor_line_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;
+ break;
case '_':
/* "g_": to the last non-blank character in the line or <count> lines
@@ -6673,9 +6688,8 @@ static void n_opencmd(cmdarg_T *cap)
(cap->cmdchar == 'o' ? 1 : 0))
)
&& open_line(cap->cmdchar == 'O' ? BACKWARD : FORWARD,
- has_format_option(FO_OPEN_COMS)
- ? OPENLINE_DO_COM : 0,
- 0)) {
+ has_format_option(FO_OPEN_COMS) ? OPENLINE_DO_COM : 0,
+ 0, NULL)) {
if (win_cursorline_standout(curwin)) {
// force redraw of cursorline
curwin->w_valid &= ~VALID_CROW;
@@ -6702,11 +6716,26 @@ static void nv_dot(cmdarg_T *cap)
}
}
-/*
- * CTRL-R: undo undo
- */
-static void nv_redo(cmdarg_T *cap)
+// CTRL-R: undo undo or specify register in select mode
+static void nv_redo_or_register(cmdarg_T *cap)
{
+ if (VIsual_select && VIsual_active) {
+ int reg;
+ // Get register name
+ no_mapping++;
+ reg = plain_vgetc();
+ LANGMAP_ADJUST(reg, true);
+ no_mapping--;
+
+ if (reg == '"') {
+ // the unnamed register is 0
+ reg = 0;
+ }
+
+ VIsual_select_reg = valid_yank_reg(reg, true) ? reg : 0;
+ return;
+ }
+
if (!checkclearopq(cap->oap)) {
u_redo((int)cap->count1);
curwin->w_set_curswant = true;
@@ -6955,7 +6984,7 @@ static void adjust_cursor(oparg_T *oap)
if (curwin->w_cursor.col > 0 && gchar_cursor() == NUL
&& (!VIsual_active || *p_sel == 'o')
&& !virtual_active()
- && (ve_flags & VE_ONEMORE) == 0) {
+ && (get_ve_flags() & VE_ONEMORE) == 0) {
curwin->w_cursor.col--;
// prevent cursor from moving on the trail byte
mb_adjust_cursor();
@@ -7027,6 +7056,7 @@ static void nv_select(cmdarg_T *cap)
{
if (VIsual_active) {
VIsual_select = true;
+ VIsual_select_reg = 0;
} else if (VIsual_reselect) {
cap->nchar = 'v'; // fake "gv" command
cap->arg = true;
@@ -7161,7 +7191,7 @@ static void nv_esc(cmdarg_T *cap)
void set_cursor_for_append_to_line(void)
{
curwin->w_set_curswant = true;
- if (ve_flags == VE_ALL) {
+ if (get_ve_flags() == VE_ALL) {
const int save_State = State;
// Pretend Insert mode here to allow the cursor on the
@@ -7479,9 +7509,9 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
// overwrites if the old contents is being put.
was_visual = true;
regname = cap->oap->regname;
+ bool save_unnamed = cap->cmdchar == 'P';
// '+' and '*' could be the same selection
- bool clipoverwrite = (regname == '+' || regname == '*')
- && (cb_flags & CB_UNNAMEDMASK);
+ bool clipoverwrite = (regname == '+' || regname == '*') && (cb_flags & CB_UNNAMEDMASK);
if (regname == 0 || regname == '"' || clipoverwrite
|| ascii_isdigit(regname) || regname == '-') {
// The delete might overwrite the register we want to put, save it first
@@ -7494,6 +7524,10 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
// do_put(), which requires the visual selection to still be active.
if (!VIsual_active || VIsual_mode == 'V' || regname != '.') {
// Now delete the selected text. Avoid messages here.
+ yankreg_T *old_y_previous;
+ if (save_unnamed) {
+ old_y_previous = get_y_previous();
+ }
cap->cmdchar = 'd';
cap->nchar = NUL;
cap->oap->regname = NUL;
@@ -7503,6 +7537,10 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
empty = (curbuf->b_ml.ml_flags & ML_EMPTY);
msg_silent--;
+ if (save_unnamed) {
+ set_y_previous(old_y_previous);
+ }
+
// delete PUT_LINE_BACKWARD;
cap->oap->regname = regname;
}
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 6c2db1e9ac..f999b68236 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -135,10 +135,18 @@ static char opchars[][3] =
{ Ctrl_X, NUL, OPF_CHANGE }, // OP_NR_SUB
};
-/*
- * Translate a command name into an operator type.
- * Must only be called with a valid operator name!
- */
+yankreg_T *get_y_previous(void)
+{
+ return y_previous;
+}
+
+void set_y_previous(yankreg_T *yreg)
+{
+ y_previous = yreg;
+}
+
+/// Translate a command name into an operator type.
+/// Must only be called with a valid operator name!
int get_op_type(int char1, int char2)
{
int i;
@@ -267,14 +275,14 @@ void op_shift(oparg_T *oap, int curs_top, int amount)
msg_attr_keep((char *)IObuff, 0, true, false);
}
- /*
- * Set "'[" and "']" marks.
- */
- curbuf->b_op_start = oap->start;
- curbuf->b_op_end.lnum = oap->end.lnum;
- curbuf->b_op_end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum));
- if (curbuf->b_op_end.col > 0) {
- curbuf->b_op_end.col--;
+ if (!cmdmod.lockmarks) {
+ // Set "'[" and "']" marks.
+ curbuf->b_op_start = oap->start;
+ curbuf->b_op_end.lnum = oap->end.lnum;
+ curbuf->b_op_end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum));
+ if (curbuf->b_op_end.col > 0) {
+ curbuf->b_op_end.col--;
+ }
}
changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L, true);
@@ -381,8 +389,8 @@ static void shift_block(oparg_T *oap, int amount)
}
}
for (; ascii_iswhite(*bd.textstart);) {
- // TODO: is passing bd.textstart for start of the line OK?
- incr = lbr_chartabsize_adv(bd.textstart, &bd.textstart, (bd.start_vcol));
+ // TODO(fmoralesc): is passing bd.textstart for start of the line OK?
+ incr = lbr_chartabsize_adv(bd.textstart, &bd.textstart, bd.start_vcol);
total += incr;
bd.start_vcol += incr;
}
@@ -566,7 +574,7 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def
if (b_insert) {
off = utf_head_off(oldp, oldp + offset + spaces);
} else {
- off = (*mb_off_next)(oldp, oldp + offset);
+ off = mb_off_next(oldp, oldp + offset);
offset += off;
}
spaces -= off;
@@ -694,9 +702,11 @@ void op_reindent(oparg_T *oap, Indenter how)
"%" PRId64 " lines indented ", i),
(int64_t)i);
}
- // set '[ and '] marks
- curbuf->b_op_start = oap->start;
- curbuf->b_op_end = oap->end;
+ if (!cmdmod.lockmarks) {
+ // set '[ and '] marks
+ curbuf->b_op_start = oap->start;
+ curbuf->b_op_end = oap->end;
+ }
}
/*
@@ -915,10 +925,29 @@ int do_record(int c)
apply_autocmds(EVENT_RECORDINGENTER, NULL, NULL, false, curbuf);
}
} else { // stop recording
- // Get the recorded key hits. K_SPECIAL and CSI will be escaped, this
+ save_v_event_T save_v_event;
+ // Set the v:event dictionary with information about the recording.
+ dict_T *dict = get_v_event(&save_v_event);
+
+ // The recorded text contents.
+ p = get_recorded();
+ if (p != NULL) {
+ // Remove escaping for K_SPECIAL in multi-byte chars.
+ vim_unescape_ks(p);
+ (void)tv_dict_add_str(dict, S_LEN("regcontents"), (const char *)p);
+ }
+
+ // Name of requested register, or empty string for unnamed operation.
+ char buf[NUMBUFLEN+2];
+ buf[0] = (char)regname;
+ buf[1] = NUL;
+ (void)tv_dict_add_str(dict, S_LEN("regname"), buf);
+
+ // Get the recorded key hits. K_SPECIAL will be escaped, this
// needs to be removed again to put it in a register. exec_reg then
// adds the escaping back later.
apply_autocmds(EVENT_RECORDINGLEAVE, NULL, NULL, false, curbuf);
+ restore_v_event(dict, &save_v_event);
reg_recorded = reg_recording;
reg_recording = 0;
if (ui_has(kUIMessages)) {
@@ -926,13 +955,9 @@ int do_record(int c)
} else {
msg("");
}
- p = get_recorded();
if (p == NULL) {
retval = FAIL;
} else {
- // Remove escaping for CSI and K_SPECIAL in multi-byte chars.
- vim_unescape_csi(p);
-
// We don't want to change the default register here, so save and
// restore the current register name.
old_y_previous = y_previous;
@@ -1084,7 +1109,7 @@ int do_execreg(int regname, int colon, int addcr, int silent)
return FAIL;
}
}
- escaped = vim_strsave_escape_csi(reg->y_array[i]);
+ escaped = vim_strsave_escape_ks(reg->y_array[i]);
retval = ins_typebuf(escaped, remap, 0, true, silent);
xfree(escaped);
if (retval == FAIL) {
@@ -1126,7 +1151,7 @@ static void put_reedit_in_typebuf(int silent)
/// Insert register contents "s" into the typeahead buffer, so that it will be
/// executed again.
///
-/// @param esc when true then it is to be taken literally: Escape CSI
+/// @param esc when true then it is to be taken literally: Escape K_SPECIAL
/// characters and no remapping.
/// @param colon add ':' before the line
static int put_in_typebuf(char_u *s, bool esc, bool colon, int silent)
@@ -1141,7 +1166,7 @@ static int put_in_typebuf(char_u *s, bool esc, bool colon, int silent)
char_u *p;
if (esc) {
- p = vim_strsave_escape_csi(s);
+ p = vim_strsave_escape_ks(s);
} else {
p = s;
}
@@ -1420,6 +1445,11 @@ int op_delete(oparg_T *oap)
return FAIL;
}
+ if (VIsual_select && oap->is_VIsual) {
+ // Use the register given with CTRL_R, defaults to zero
+ oap->regname = VIsual_select_reg;
+ }
+
mb_adjust_opend(oap);
/*
@@ -1716,13 +1746,15 @@ int op_delete(oparg_T *oap)
msgmore(curbuf->b_ml.ml_line_count - old_lcount);
setmarks:
- if (oap->motion_type == kMTBlockWise) {
- curbuf->b_op_end.lnum = oap->end.lnum;
- curbuf->b_op_end.col = oap->start.col;
- } else {
- curbuf->b_op_end = oap->start;
+ if (!cmdmod.lockmarks) {
+ if (oap->motion_type == kMTBlockWise) {
+ curbuf->b_op_end.lnum = oap->end.lnum;
+ curbuf->b_op_end.col = oap->start.col;
+ } else {
+ curbuf->b_op_end = oap->start;
+ }
+ curbuf->b_op_start = oap->start;
}
- curbuf->b_op_start = oap->start;
return OK;
}
@@ -1987,9 +2019,11 @@ static int op_replace(oparg_T *oap, int c)
check_cursor();
changed_lines(oap->start.lnum, oap->start.col, oap->end.lnum + 1, 0L, true);
- // Set "'[" and "']" marks.
- curbuf->b_op_start = oap->start;
- curbuf->b_op_end = oap->end;
+ if (!cmdmod.lockmarks) {
+ // Set "'[" and "']" marks.
+ curbuf->b_op_start = oap->start;
+ curbuf->b_op_end = oap->end;
+ }
return OK;
}
@@ -2058,11 +2092,11 @@ void op_tilde(oparg_T *oap)
redraw_curbuf_later(INVERTED);
}
- /*
- * Set '[ and '] marks.
- */
- curbuf->b_op_start = oap->start;
- curbuf->b_op_end = oap->end;
+ if (!cmdmod.lockmarks) {
+ // Set '[ and '] marks.
+ curbuf->b_op_start = oap->start;
+ curbuf->b_op_end = oap->end;
+ }
if (oap->line_count > p_report) {
smsg(NGETTEXT("%" PRId64 " line changed",
@@ -2181,19 +2215,22 @@ void op_insert(oparg_T *oap, long count1)
// doing block_prep(). When only "block" is used, virtual edit is
// already disabled, but still need it when calling
// coladvance_force().
+ // coladvance_force() uses get_ve_flags() to get the 'virtualedit'
+ // state for the current window. To override that state, we need to
+ // set the window-local value of ve_flags rather than the global value.
if (curwin->w_cursor.coladd > 0) {
- unsigned old_ve_flags = ve_flags;
+ unsigned old_ve_flags = curwin->w_ve_flags;
- ve_flags = VE_ALL;
if (u_save_cursor() == FAIL) {
return;
}
+ curwin->w_ve_flags = VE_ALL;
coladvance_force(oap->op_type == OP_APPEND
? oap->end_vcol + 1 : getviscol());
if (oap->op_type == OP_APPEND) {
--curwin->w_cursor.col;
}
- ve_flags = old_ve_flags;
+ curwin->w_ve_flags = old_ve_flags;
}
// Get the info about the block before entering the text
block_prep(oap, &bd, oap->start.lnum, true);
@@ -2751,17 +2788,15 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
}
}
- /*
- * Set "'[" and "']" marks.
- */
- curbuf->b_op_start = oap->start;
- curbuf->b_op_end = oap->end;
- if (yank_type == kMTLineWise) {
- curbuf->b_op_start.col = 0;
- curbuf->b_op_end.col = MAXCOL;
+ if (!cmdmod.lockmarks) {
+ // Set "'[" and "']" marks.
+ curbuf->b_op_start = oap->start;
+ curbuf->b_op_end = oap->end;
+ if (yank_type == kMTLineWise) {
+ curbuf->b_op_start.col = 0;
+ curbuf->b_op_end.col = MAXCOL;
+ }
}
-
- return;
}
// Copy a block range into a register.
@@ -2786,7 +2821,7 @@ static void yank_copy_line(yankreg_T *reg, struct block_def *bd, size_t y_idx,
if (exclude_trailing_space) {
int s = bd->textlen + bd->endspaces;
- while (ascii_iswhite(*(bd->textstart + s - 1)) && s > 0) {
+ while (s > 0 && ascii_iswhite(*(bd->textstart + s - 1))) {
s = s - utf_head_off(bd->textstart, bd->textstart + s - 1) - 1;
pnew--;
}
@@ -2891,6 +2926,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
char_u *insert_string = NULL;
bool allocated = false;
long cnt;
+ const pos_T orig_start = curbuf->b_op_start;
+ const pos_T orig_end = curbuf->b_op_end;
+ unsigned int cur_ve_flags = get_ve_flags();
if (flags & PUT_FIXINDENT) {
orig_indent = get_indent();
@@ -2961,7 +2999,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
eol = (*(cursor_pos + utfc_ptr2len(cursor_pos)) == NUL);
}
- bool ve_allows = (ve_flags == VE_ALL || ve_flags == VE_ONEMORE);
+ bool ve_allows = (cur_ve_flags == VE_ALL || cur_ve_flags == VE_ONEMORE);
bool eof = curbuf->b_ml.ml_line_count == curwin->w_cursor.lnum
&& one_past_line;
if (ve_allows || !(eol || eof)) {
@@ -3137,13 +3175,14 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
yanklen = (int)STRLEN(y_array[0]);
- if (ve_flags == VE_ALL && y_type == kMTCharWise) {
+ if (cur_ve_flags == VE_ALL && y_type == kMTCharWise) {
if (gchar_cursor() == TAB) {
- /* Don't need to insert spaces when "p" on the last position of a
- * tab or "P" on the first position. */
int viscol = getviscol();
+ long ts = curbuf->b_p_ts;
+ // Don't need to insert spaces when "p" on the last position of a
+ // tab or "P" on the first position.
if (dir == FORWARD
- ? tabstop_padding(viscol, curbuf->b_p_ts, curbuf->b_p_vts_array) != 1
+ ? tabstop_padding(viscol, ts, curbuf->b_p_vts_array) != 1
: curwin->w_cursor.coladd > 0) {
coladvance_force(viscol);
} else {
@@ -3165,7 +3204,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
colnr_T endcol2 = 0;
if (dir == FORWARD && c != NUL) {
- if (ve_flags == VE_ALL) {
+ if (cur_ve_flags == VE_ALL) {
getvcol(curwin, &curwin->w_cursor, &col, NULL, &endcol2);
} else {
getvcol(curwin, &curwin->w_cursor, NULL, NULL, &col);
@@ -3179,9 +3218,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
}
col += curwin->w_cursor.coladd;
- if (ve_flags == VE_ALL
- && (curwin->w_cursor.coladd > 0
- || endcol2 == curwin->w_cursor.col)) {
+ if (cur_ve_flags == VE_ALL
+ && (curwin->w_cursor.coladd > 0 || endcol2 == curwin->w_cursor.col)) {
if (dir == FORWARD && c == NUL) {
col++;
}
@@ -3440,6 +3478,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
curwin->w_cursor.col -= first_byte_off;
}
} else {
+ linenr_T new_lnum = new_cursor.lnum;
+ size_t len;
+
// Insert at least one line. When y_type is kMTCharWise, break the first
// line in two.
for (cnt = 1; cnt <= count; cnt++) {
@@ -3456,6 +3497,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
STRCAT(newp, ptr);
// insert second line
ml_append(lnum, newp, (colnr_T)0, false);
+ new_lnum++;
xfree(newp);
oldp = ml_get(lnum);
@@ -3471,10 +3513,11 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
}
for (; i < y_size; i++) {
- if ((y_type != kMTCharWise || i < y_size - 1)
- && ml_append(lnum, y_array[i], (colnr_T)0, false)
- == FAIL) {
- goto error;
+ if ((y_type != kMTCharWise || i < y_size - 1)) {
+ if (ml_append(lnum, y_array[i], (colnr_T)0, false) == FAIL) {
+ goto error;
+ }
+ new_lnum++;
}
lnum++;
++nr_lines;
@@ -3524,6 +3567,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
extmark_splice(curbuf, (int)new_cursor.lnum-1, col + 1, 0, 0, 0,
(int)y_size+1, 0, totsize+2, kExtmarkUndo);
}
+
+ if (cnt == 1) {
+ new_lnum = lnum;
+ }
}
error:
@@ -3551,11 +3598,12 @@ error:
// Put the '] mark on the first byte of the last inserted character.
// Correct the length for change in indent.
- curbuf->b_op_end.lnum = lnum;
- col = (colnr_T)STRLEN(y_array[y_size - 1]) - lendiff;
+ curbuf->b_op_end.lnum = new_lnum;
+ len = STRLEN(y_array[y_size - 1]);
+ col = (colnr_T)len - lendiff;
if (col > 1) {
curbuf->b_op_end.col = col - 1 - utf_head_off(y_array[y_size - 1],
- y_array[y_size - 1] + col - 1);
+ y_array[y_size - 1] + len - 1);
} else {
curbuf->b_op_end.col = 0;
}
@@ -3574,8 +3622,12 @@ error:
}
curwin->w_cursor.col = 0;
} else {
- curwin->w_cursor.lnum = lnum;
+ curwin->w_cursor.lnum = new_lnum;
curwin->w_cursor.col = col;
+ curbuf->b_op_end = curwin->w_cursor;
+ if (col > 1) {
+ curbuf->b_op_end.col = col - 1;
+ }
}
} else if (y_type == kMTLineWise) {
// put cursor on first non-blank in first inserted line
@@ -3594,6 +3646,10 @@ error:
curwin->w_set_curswant = TRUE;
end:
+ if (cmdmod.lockmarks) {
+ curbuf->b_op_start = orig_start;
+ curbuf->b_op_end = orig_end;
+ }
if (allocated) {
xfree(insert_string);
}
@@ -3613,14 +3669,16 @@ end:
*/
void adjust_cursor_eol(void)
{
+ unsigned int cur_ve_flags = get_ve_flags();
+
if (curwin->w_cursor.col > 0
&& gchar_cursor() == NUL
- && (ve_flags & VE_ONEMORE) == 0
+ && (cur_ve_flags & VE_ONEMORE) == 0
&& !(restart_edit || (State & INSERT))) {
// Put the cursor on the last character in the line.
dec_cursor();
- if (ve_flags == VE_ALL) {
+ if (cur_ve_flags == VE_ALL) {
colnr_T scol, ecol;
// Coladd is set to the width of the last character.
@@ -3674,7 +3732,7 @@ void ex_display(exarg_T *eap)
int name;
char_u *arg = eap->arg;
int clen;
- char_u type[2];
+ int type;
if (arg != NULL && *arg == NUL) {
arg = NULL;
@@ -3687,11 +3745,11 @@ void ex_display(exarg_T *eap)
name = get_register_name(i);
switch (get_reg_type(name, NULL)) {
case kMTLineWise:
- type[0] = 'l'; break;
+ type = 'l'; break;
case kMTCharWise:
- type[0] = 'c'; break;
+ type = 'c'; break;
default:
- type[0] = 'b'; break;
+ type = 'b'; break;
}
if (arg != NULL && vim_strchr(arg, name) == NULL) {
@@ -3718,88 +3776,87 @@ void ex_display(exarg_T *eap)
}
if (yb->y_array != NULL) {
- msg_putchar('\n');
- msg_puts(" ");
- msg_putchar(type[0]);
- msg_puts(" ");
- msg_putchar('"');
- msg_putchar(name);
- msg_puts(" ");
-
- int n = Columns - 11;
- for (size_t j = 0; j < yb->y_size && n > 1; j++) {
- if (j) {
- msg_puts_attr("^J", attr);
- n -= 2;
+ bool do_show = false;
+
+ for (size_t j = 0; !do_show && j < yb->y_size; j++) {
+ do_show = !message_filtered(yb->y_array[j]);
+ }
+
+ if (do_show || yb->y_size == 0) {
+ msg_putchar('\n');
+ msg_puts(" ");
+ msg_putchar(type);
+ msg_puts(" ");
+ msg_putchar('"');
+ msg_putchar(name);
+ msg_puts(" ");
+
+ int n = Columns - 11;
+ for (size_t j = 0; j < yb->y_size && n > 1; j++) {
+ if (j) {
+ msg_puts_attr("^J", attr);
+ n -= 2;
+ }
+ for (p = yb->y_array[j]; *p && (n -= ptr2cells(p)) >= 0; p++) {
+ clen = utfc_ptr2len(p);
+ msg_outtrans_len(p, clen);
+ p += clen - 1;
+ }
}
- for (p = yb->y_array[j]; *p && (n -= ptr2cells(p)) >= 0; p++) { // -V1019 NOLINT(whitespace/line_length)
- clen = utfc_ptr2len(p);
- msg_outtrans_len(p, clen);
- p += clen - 1;
+ if (n > 1 && yb->y_type == kMTLineWise) {
+ msg_puts_attr("^J", attr);
}
+ ui_flush(); // show one line at a time
}
- if (n > 1 && yb->y_type == kMTLineWise) {
- msg_puts_attr("^J", attr);
- }
- ui_flush(); // show one line at a time
+ os_breakcheck();
}
- os_breakcheck();
}
- /*
- * display last inserted text
- */
+ // display last inserted text
if ((p = get_last_insert()) != NULL
- && (arg == NULL || vim_strchr(arg, '.') != NULL) && !got_int) {
+ && (arg == NULL || vim_strchr(arg, '.') != NULL) && !got_int
+ && !message_filtered(p)) {
msg_puts("\n c \". ");
dis_msg(p, true);
}
- /*
- * display last command line
- */
+ // display last command line
if (last_cmdline != NULL && (arg == NULL || vim_strchr(arg, ':') != NULL)
- && !got_int) {
+ && !got_int && !message_filtered(last_cmdline)) {
msg_puts("\n c \": ");
dis_msg(last_cmdline, false);
}
- /*
- * display current file name
- */
+ // display current file name
if (curbuf->b_fname != NULL
- && (arg == NULL || vim_strchr(arg, '%') != NULL) && !got_int) {
+ && (arg == NULL || vim_strchr(arg, '%') != NULL) && !got_int
+ && !message_filtered(curbuf->b_fname)) {
msg_puts("\n c \"% ");
dis_msg(curbuf->b_fname, false);
}
- /*
- * display alternate file name
- */
+ // display alternate file name
if ((arg == NULL || vim_strchr(arg, '%') != NULL) && !got_int) {
char_u *fname;
linenr_T dummy;
- if (buflist_name_nr(0, &fname, &dummy) != FAIL) {
+ if (buflist_name_nr(0, &fname, &dummy) != FAIL && !message_filtered(fname)) {
msg_puts("\n c \"# ");
dis_msg(fname, false);
}
}
- /*
- * display last search pattern
- */
+ // display last search pattern
if (last_search_pat() != NULL
- && (arg == NULL || vim_strchr(arg, '/') != NULL) && !got_int) {
+ && (arg == NULL || vim_strchr(arg, '/') != NULL) && !got_int
+ && !message_filtered(last_search_pat())) {
msg_puts("\n c \"/ ");
dis_msg(last_search_pat(), false);
}
- /*
- * display last used expression
- */
+ // display last used expression
if (expr_line != NULL && (arg == NULL || vim_strchr(arg, '=') != NULL)
- && !got_int) {
+ && !got_int && !message_filtered(expr_line)) {
msg_puts("\n c \"= ");
dis_msg(expr_line, false);
}
@@ -3938,7 +3995,7 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions
// and setup the array of space strings lengths
for (t = 0; t < (linenr_T)count; t++) {
curr = curr_start = ml_get((linenr_T)(curwin->w_cursor.lnum + t));
- if (t == 0 && setmark) {
+ if (t == 0 && setmark && !cmdmod.lockmarks) {
// Set the '[ mark.
curwin->w_buffer->b_op_start.lnum = curwin->w_cursor.lnum;
curwin->w_buffer->b_op_start.col = (colnr_T)STRLEN(curr);
@@ -4059,7 +4116,7 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions
ml_replace(curwin->w_cursor.lnum, newp, false);
- if (setmark) {
+ if (setmark && !cmdmod.lockmarks) {
// Set the '] mark.
curwin->w_buffer->b_op_end.lnum = curwin->w_cursor.lnum;
curwin->w_buffer->b_op_end.col = sumsize;
@@ -4197,8 +4254,10 @@ static void op_format(oparg_T *oap, int keep_cursor)
redraw_curbuf_later(INVERTED);
}
- // Set '[ mark at the start of the formatted area
- curbuf->b_op_start = oap->start;
+ if (!cmdmod.lockmarks) {
+ // Set '[ mark at the start of the formatted area
+ curbuf->b_op_start = oap->start;
+ }
// For "gw" remember the cursor position and put it back below (adjusted
// for joined and split lines).
@@ -4220,8 +4279,10 @@ static void op_format(oparg_T *oap, int keep_cursor)
old_line_count = curbuf->b_ml.ml_line_count - old_line_count;
msgmore(old_line_count);
- // put '] mark on the end of the formatted area
- curbuf->b_op_end = curwin->w_cursor;
+ if (!cmdmod.lockmarks) {
+ // put '] mark on the end of the formatted area
+ curbuf->b_op_end = curwin->w_cursor;
+ }
if (keep_cursor) {
curwin->w_cursor = saved_cursor;
@@ -4308,7 +4369,7 @@ void format_lines(linenr_T line_count, int avoid_fex)
int leader_len = 0; // leader len of current line
int next_leader_len; // leader len of next line
char_u *leader_flags = NULL; // flags for leader of current line
- char_u *next_leader_flags; // flags for leader of next line
+ char_u *next_leader_flags = NULL; // flags for leader of next line
bool advance = true;
int second_indent = -1; // indent for second line (comment aware)
bool first_par_line = true;
@@ -4425,7 +4486,14 @@ void format_lines(linenr_T line_count, int avoid_fex)
leader_len, leader_flags,
next_leader_len,
next_leader_flags)) {
- is_end_par = true;
+ // Special case: If the next line starts with a line comment
+ // and this line has a line comment after some text, the
+ // paragraph doesn't really end.
+ if (next_leader_flags == NULL
+ || STRNCMP(next_leader_flags, "://", 3) != 0
+ || check_linecomment(get_cursor_line_ptr()) == MAXCOL) {
+ is_end_par = true;
+ }
}
/*
@@ -4819,7 +4887,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd)
// Set '[ mark if something changed. Keep the last end
// position from do_addsub().
- if (change_cnt > 0) {
+ if (change_cnt > 0 && !cmdmod.lockmarks) {
curbuf->b_op_start = startpos;
}
@@ -5173,11 +5241,13 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
}
}
- // set the '[ and '] marks
- curbuf->b_op_start = startpos;
- curbuf->b_op_end = endpos;
- if (curbuf->b_op_end.col > 0) {
- curbuf->b_op_end.col--;
+ if (!cmdmod.lockmarks) {
+ // set the '[ and '] marks
+ curbuf->b_op_start = startpos;
+ curbuf->b_op_end = endpos;
+ if (curbuf->b_op_end.col > 0) {
+ curbuf->b_op_end.col--;
+ }
}
theend:
@@ -5990,6 +6060,8 @@ static void op_function(const oparg_T *oap)
{
const TriState save_virtual_op = virtual_op;
const bool save_finish_op = finish_op;
+ const pos_T orig_start = curbuf->b_op_start;
+ const pos_T orig_end = curbuf->b_op_end;
if (*p_opfunc == NUL) {
emsg(_("E774: 'operatorfunc' is empty"));
@@ -6023,6 +6095,10 @@ static void op_function(const oparg_T *oap)
virtual_op = save_virtual_op;
finish_op = save_finish_op;
+ if (cmdmod.lockmarks) {
+ curbuf->b_op_start = orig_start;
+ curbuf->b_op_end = orig_end;
+ }
}
}
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 659965b64c..c8e50d4494 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -281,7 +281,7 @@ typedef struct vimoption {
# include "options.generated.h"
#endif
-#define PARAM_COUNT ARRAY_SIZE(options)
+#define OPTION_COUNT ARRAY_SIZE(options)
static char *(p_ambw_values[]) = { "single", "double", NULL };
static char *(p_bg_values[]) = { "light", "dark", NULL };
@@ -931,6 +931,21 @@ void set_title_defaults(void)
}
}
+void ex_set(exarg_T *eap)
+{
+ int flags = 0;
+
+ if (eap->cmdidx == CMD_setlocal) {
+ flags = OPT_LOCAL;
+ } else if (eap->cmdidx == CMD_setglobal) {
+ flags = OPT_GLOBAL;
+ }
+ if (eap->forceit) {
+ flags |= OPT_ONECOLUMN;
+ }
+ (void)do_set(eap->arg, flags);
+}
+
/// Parse 'arg' for option settings.
///
/// 'arg' may be IObuff, but only when no errors can be present and option
@@ -1766,7 +1781,7 @@ static char *illegal_char(char *errbuf, size_t errbuflen, int c)
if (errbuf == NULL) {
return "";
}
- vim_snprintf((char *)errbuf, errbuflen, _("E539: Illegal character <%s>"),
+ vim_snprintf(errbuf, errbuflen, _("E539: Illegal character <%s>"),
(char *)transchar(c));
return errbuf;
}
@@ -1969,10 +1984,9 @@ static void didset_options(void)
(void)did_set_spell_option(true);
// set cedit_key
(void)check_cedit();
- briopt_check(curwin);
// initialize the table for 'breakat'.
fill_breakat_flags();
- fill_culopt_flags(NULL, curwin);
+ didset_window_options(curwin);
}
// More side effects of setting options.
@@ -1993,9 +2007,9 @@ static void didset_options2(void)
// Parse default for 'wildmode'.
check_opt_wim();
xfree(curbuf->b_p_vsts_array);
- tabstop_set(curbuf->b_p_vsts, &curbuf->b_p_vsts_array);
+ (void)tabstop_set(curbuf->b_p_vsts, &curbuf->b_p_vsts_array);
xfree(curbuf->b_p_vts_array);
- tabstop_set(curbuf->b_p_vts, &curbuf->b_p_vts_array);
+ (void)tabstop_set(curbuf->b_p_vts, &curbuf->b_p_vts_array);
}
/// Check for string options that are NULL (normally only termcap options).
@@ -2269,12 +2283,12 @@ static char *set_string_option(const int opt_idx, const char *const value, const
*varp = s;
char *const saved_oldval = xstrdup(oldval);
- char *const saved_oldval_l = (oldval_l != NULL) ? xstrdup((char *)oldval_l) : 0;
- char *const saved_oldval_g = (oldval_g != NULL) ? xstrdup((char *)oldval_g) : 0;
+ char *const saved_oldval_l = (oldval_l != NULL) ? xstrdup(oldval_l) : 0;
+ char *const saved_oldval_g = (oldval_g != NULL) ? xstrdup(oldval_g) : 0;
char *const saved_newval = xstrdup(s);
int value_checked = false;
- char *const r = did_set_string_option(opt_idx, (char_u **)varp, (int)true,
+ char *const r = did_set_string_option(opt_idx, (char_u **)varp, true,
(char_u *)oldval,
NULL, 0, opt_flags, &value_checked);
if (r == NULL) {
@@ -2762,7 +2776,7 @@ ambw_end:
if (!ascii_isdigit(*(s - 1))) {
if (errbuf != NULL) {
- vim_snprintf((char *)errbuf, errbuflen,
+ vim_snprintf(errbuf, errbuflen,
_("E526: Missing number after <%s>"),
transchar_byte(*(s - 1)));
errmsg = errbuf;
@@ -2953,7 +2967,7 @@ ambw_end:
}
} else {
if (errbuf != NULL) {
- vim_snprintf((char *)errbuf, errbuflen,
+ vim_snprintf(errbuf, errbuflen,
_("E535: Illegal character after <%c>"),
*--s);
errmsg = errbuf;
@@ -3084,14 +3098,27 @@ ambw_end:
if (foldmethodIsIndent(curwin)) {
foldUpdateAll(curwin);
}
- } else if (varp == &p_ve) { // 'virtualedit'
- if (opt_strings_flags(p_ve, p_ve_values, &ve_flags, true) != OK) {
- errmsg = e_invarg;
- } else if (STRCMP(p_ve, oldval) != 0) {
- // Recompute cursor position in case the new 've' setting
- // changes something.
- validate_virtcol();
- coladvance(curwin->w_virtcol);
+ } else if (gvarp == &p_ve) { // 'virtualedit'
+ char_u *ve = p_ve;
+ unsigned int *flags = &ve_flags;
+
+ if (opt_flags & OPT_LOCAL) {
+ ve = curwin->w_p_ve;
+ flags = &curwin->w_ve_flags;
+ }
+
+ if ((opt_flags & OPT_LOCAL) && *ve == NUL) {
+ // make the local value empty: use the global value
+ *flags = 0;
+ } else {
+ if (opt_strings_flags(ve, p_ve_values, flags, true) != OK) {
+ errmsg = e_invarg;
+ } else if (STRCMP(p_ve, oldval) != 0) {
+ // Recompute cursor position in case the new 've' setting
+ // changes something.
+ validate_virtcol();
+ coladvance(curwin->w_virtcol);
+ }
}
} else if (varp == &p_csqf) {
if (p_csqf != NULL) {
@@ -3150,10 +3177,7 @@ ambw_end:
char_u *cp;
if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) {
- if (curbuf->b_p_vsts_array) {
- xfree(curbuf->b_p_vsts_array);
- curbuf->b_p_vsts_array = 0;
- }
+ XFREE_CLEAR(curbuf->b_p_vsts_array);
} else {
for (cp = *varp; *cp; cp++) {
if (ascii_isdigit(*cp)) {
@@ -3178,10 +3202,7 @@ ambw_end:
char_u *cp;
if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) {
- if (curbuf->b_p_vts_array) {
- xfree(curbuf->b_p_vts_array);
- curbuf->b_p_vts_array = NULL;
- }
+ XFREE_CLEAR(curbuf->b_p_vts_array);
} else {
for (cp = *varp; *cp; cp++) {
if (ascii_isdigit(*cp)) {
@@ -4321,6 +4342,12 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf,
} else if (value > 10000) {
errmsg = e_invarg;
}
+ } else if (pp == &p_pyx) {
+ if (value == 0) {
+ value = 3;
+ } else if (value != 3) {
+ errmsg = e_invarg;
+ }
} else if (pp == &p_re) {
if (value < 0 || value > 2) {
errmsg = e_invarg;
@@ -4386,6 +4413,8 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf,
} else if (pp == &curbuf->b_p_ts || pp == &p_ts) {
if (value < 1) {
errmsg = e_positive;
+ } else if (value > TABSTOP_MAX) {
+ errmsg = e_invarg;
}
} else if (pp == &curbuf->b_p_tw || pp == &p_tw) {
if (value < 0) {
@@ -4399,7 +4428,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf,
// Don't change the value and return early if validation failed.
if (errmsg != NULL) {
- return (char *)errmsg;
+ return errmsg;
}
*pp = value;
@@ -4495,10 +4524,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf,
if (pum_drawn()) {
pum_redraw();
}
- } else if (pp == &p_pyx) {
- if (p_pyx != 0 && p_pyx != 2 && p_pyx != 3) {
- errmsg = e_invarg;
- }
} else if (pp == &p_ul || pp == &curbuf->b_p_ul) {
// sync undo before 'undolevels' changes
// use the old value, otherwise u_sync() may not work properly
@@ -4527,7 +4552,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf,
// Check the (new) bounds for Rows and Columns here.
if (p_lines < min_rows() && full_screen) {
if (errbuf != NULL) {
- vim_snprintf((char *)errbuf, errbuflen,
+ vim_snprintf(errbuf, errbuflen,
_("E593: Need at least %d lines"), min_rows());
errmsg = errbuf;
}
@@ -4535,7 +4560,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf,
}
if (p_columns < MIN_COLUMNS && full_screen) {
if (errbuf != NULL) {
- vim_snprintf((char *)errbuf, errbuflen,
+ vim_snprintf(errbuf, errbuflen,
_("E594: Need at least %d columns"), MIN_COLUMNS);
errmsg = errbuf;
}
@@ -4651,7 +4676,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf,
}
check_redraw(options[opt_idx].flags);
- return (char *)errmsg;
+ return errmsg;
}
/// Trigger the OptionSet autocommand.
@@ -5184,7 +5209,7 @@ static void showoptions(int all, int opt_flags)
#define INC 20
#define GAP 3
- vimoption_T **items = xmalloc(sizeof(vimoption_T *) * PARAM_COUNT);
+ vimoption_T **items = xmalloc(sizeof(vimoption_T *) * OPTION_COUNT);
// Highlight title
if (opt_flags & OPT_GLOBAL) {
@@ -5198,6 +5223,7 @@ static void showoptions(int all, int opt_flags)
// Do the loop two times:
// 1. display the short items
// 2. display the long items (only strings and numbers)
+ // When "opt_flags" has OPT_ONECOLUMN do everything in run 2.
for (run = 1; run <= 2 && !got_int; run++) {
// collect the items in items[]
item_count = 0;
@@ -5208,7 +5234,7 @@ static void showoptions(int all, int opt_flags)
}
varp = NULL;
- if (opt_flags != 0) {
+ if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) != 0) {
if (p->indir != PV_NONE) {
varp = get_varp_scope(p, opt_flags);
}
@@ -5217,8 +5243,10 @@ static void showoptions(int all, int opt_flags)
}
if (varp != NULL
&& (all == 1 || (all == 0 && !optval_default(p, varp)))) {
- if (p->flags & P_BOOL) {
- len = 1; // a toggle option fits always
+ if (opt_flags & OPT_ONECOLUMN) {
+ len = Columns;
+ } else if (p->flags & P_BOOL) {
+ len = 1; // a toggle option fits always
} else {
option_value2string(p, opt_flags);
len = (int)STRLEN(p->fullname) + vim_strsize(NameBuff) + 1;
@@ -5748,6 +5776,10 @@ void unset_global_local_option(char *name, void *from)
set_chars_option((win_T *)from, &((win_T *)from)->w_p_fcs, true);
redraw_later((win_T *)from, NOT_VALID);
break;
+ case PV_VE:
+ clear_string_option(&((win_T *)from)->w_p_ve);
+ ((win_T *)from)->w_ve_flags = 0;
+ break;
}
}
@@ -5814,6 +5846,8 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags)
return (char_u *)&(curwin->w_p_fcs);
case PV_LCS:
return (char_u *)&(curwin->w_p_lcs);
+ case PV_VE:
+ return (char_u *)&(curwin->w_p_ve);
}
return NULL; // "cannot happen"
}
@@ -5908,6 +5942,9 @@ static char_u *get_varp(vimoption_T *p)
case PV_LCS:
return *curwin->w_p_lcs != NUL
? (char_u *)&(curwin->w_p_lcs) : p->var;
+ case PV_VE:
+ return *curwin->w_p_ve != NUL
+ ? (char_u *)&curwin->w_p_ve : p->var;
case PV_ARAB:
return (char_u *)&(curwin->w_p_arab);
@@ -6136,6 +6173,7 @@ void win_copy_options(win_T *wp_from, win_T *wp_to)
{
copy_winopt(&wp_from->w_onebuf_opt, &wp_to->w_onebuf_opt);
copy_winopt(&wp_from->w_allbuf_opt, &wp_to->w_allbuf_opt);
+ didset_window_options(wp_to);
}
/// Copy the options from one winopt_T to another.
@@ -6148,6 +6186,8 @@ void copy_winopt(winopt_T *from, winopt_T *to)
to->wo_list = from->wo_list;
to->wo_nu = from->wo_nu;
to->wo_rnu = from->wo_rnu;
+ to->wo_ve = vim_strsave(from->wo_ve);
+ to->wo_ve_flags = from->wo_ve_flags;
to->wo_nuw = from->wo_nuw;
to->wo_rl = from->wo_rl;
to->wo_rlc = vim_strsave(from->wo_rlc);
@@ -6192,6 +6232,9 @@ void copy_winopt(winopt_T *from, winopt_T *to)
to->wo_fcs = vim_strsave(from->wo_fcs);
to->wo_lcs = vim_strsave(from->wo_lcs);
to->wo_winbl = from->wo_winbl;
+
+ // Copy the script context so that we know were the value was last set.
+ memmove(to->wo_script_ctx, from->wo_script_ctx, sizeof(to->wo_script_ctx));
check_winopt(to); // don't want NULL pointers
}
@@ -6224,6 +6267,7 @@ static void check_winopt(winopt_T *wop)
check_string_option(&wop->wo_winhl);
check_string_option(&wop->wo_fcs);
check_string_option(&wop->wo_lcs);
+ check_string_option(&wop->wo_ve);
}
/// Free the allocated memory inside a winopt_T.
@@ -6248,6 +6292,7 @@ void clear_winopt(winopt_T *wop)
clear_string_option(&wop->wo_winhl);
clear_string_option(&wop->wo_fcs);
clear_string_option(&wop->wo_lcs);
+ clear_string_option(&wop->wo_ve);
}
void didset_window_options(win_T *wp)
@@ -6262,11 +6307,30 @@ void didset_window_options(win_T *wp)
wp->w_grid_alloc.blending = wp->w_p_winbl > 0;
}
+/// Index into the options table for a buffer-local option enum.
+static int buf_opt_idx[BV_COUNT];
+#define COPY_OPT_SCTX(buf, bv) buf->b_p_script_ctx[bv] = options[buf_opt_idx[bv]].last_set
+
+/// Initialize buf_opt_idx[] if not done already.
+static void init_buf_opt_idx(void)
+{
+ static int did_init_buf_opt_idx = false;
+
+ if (did_init_buf_opt_idx) {
+ return;
+ }
+ did_init_buf_opt_idx = true;
+ for (int i = 0; options[i].fullname != NULL; i++) {
+ if (options[i].indir & PV_BUF) {
+ buf_opt_idx[options[i].indir & PV_MASK] = i;
+ }
+ }
+}
/// Copy global option values to local options for one buffer.
/// Used when creating a new buffer and sometimes when entering a buffer.
/// flags:
-/// BCO_ENTER We will enter the buf buffer.
+/// BCO_ENTER We will enter the buffer "buf".
/// BCO_ALWAYS Always copy the options, but only set b_p_initialized when
/// appropriate.
/// BCO_NOHELP Don't copy the values to a help buffer.
@@ -6302,11 +6366,12 @@ void buf_copy_options(buf_T *buf, int flags)
}
if (should_copy || (flags & BCO_ALWAYS)) {
- /* Don't copy the options specific to a help buffer when
- * BCO_NOHELP is given or the options were initialized already
- * (jumping back to a help file with CTRL-T or CTRL-O) */
- dont_do_help = ((flags & BCO_NOHELP) && buf->b_help)
- || buf->b_p_initialized;
+ memset(buf->b_p_script_ctx, 0, sizeof(buf->b_p_script_ctx));
+ init_buf_opt_idx();
+ // Don't copy the options specific to a help buffer when
+ // BCO_NOHELP is given or the options were initialized already
+ // (jumping back to a help file with CTRL-T or CTRL-O)
+ dont_do_help = ((flags & BCO_NOHELP) && buf->b_help) || buf->b_p_initialized;
if (dont_do_help) { // don't free b_p_isk
save_p_isk = buf->b_p_isk;
buf->b_p_isk = NULL;
@@ -6338,80 +6403,129 @@ void buf_copy_options(buf_T *buf, int flags)
}
buf->b_p_ai = p_ai;
+ COPY_OPT_SCTX(buf, BV_AI);
buf->b_p_ai_nopaste = p_ai_nopaste;
buf->b_p_sw = p_sw;
+ COPY_OPT_SCTX(buf, BV_SW);
buf->b_p_scbk = p_scbk;
+ COPY_OPT_SCTX(buf, BV_SCBK);
buf->b_p_tw = p_tw;
+ COPY_OPT_SCTX(buf, BV_TW);
buf->b_p_tw_nopaste = p_tw_nopaste;
buf->b_p_tw_nobin = p_tw_nobin;
buf->b_p_wm = p_wm;
+ COPY_OPT_SCTX(buf, BV_WM);
buf->b_p_wm_nopaste = p_wm_nopaste;
buf->b_p_wm_nobin = p_wm_nobin;
buf->b_p_bin = p_bin;
+ COPY_OPT_SCTX(buf, BV_BIN);
buf->b_p_bomb = p_bomb;
+ COPY_OPT_SCTX(buf, BV_BOMB);
buf->b_p_et = p_et;
+ COPY_OPT_SCTX(buf, BV_ET);
buf->b_p_fixeol = p_fixeol;
+ COPY_OPT_SCTX(buf, BV_FIXEOL);
buf->b_p_et_nobin = p_et_nobin;
buf->b_p_et_nopaste = p_et_nopaste;
buf->b_p_ml = p_ml;
+ COPY_OPT_SCTX(buf, BV_ML);
buf->b_p_ml_nobin = p_ml_nobin;
buf->b_p_inf = p_inf;
- buf->b_p_swf = cmdmod.noswapfile ? false : p_swf;
+ COPY_OPT_SCTX(buf, BV_INF);
+ if (cmdmod.noswapfile) {
+ buf->b_p_swf = false;
+ } else {
+ buf->b_p_swf = p_swf;
+ COPY_OPT_SCTX(buf, BV_SWF);
+ }
buf->b_p_cpt = vim_strsave(p_cpt);
+ COPY_OPT_SCTX(buf, BV_CPT);
#ifdef BACKSLASH_IN_FILENAME
buf->b_p_csl = vim_strsave(p_csl);
+ COPY_OPT_SCTX(buf, BV_CSL);
#endif
buf->b_p_cfu = vim_strsave(p_cfu);
+ COPY_OPT_SCTX(buf, BV_CFU);
buf->b_p_ofu = vim_strsave(p_ofu);
+ COPY_OPT_SCTX(buf, BV_OFU);
buf->b_p_tfu = vim_strsave(p_tfu);
+ COPY_OPT_SCTX(buf, BV_TFU);
buf->b_p_sts = p_sts;
+ COPY_OPT_SCTX(buf, BV_STS);
buf->b_p_sts_nopaste = p_sts_nopaste;
buf->b_p_vsts = vim_strsave(p_vsts);
+ COPY_OPT_SCTX(buf, BV_VSTS);
if (p_vsts && p_vsts != empty_option) {
- tabstop_set(p_vsts, &buf->b_p_vsts_array);
+ (void)tabstop_set(p_vsts, &buf->b_p_vsts_array);
} else {
- buf->b_p_vsts_array = 0;
+ buf->b_p_vsts_array = NULL;
}
buf->b_p_vsts_nopaste = p_vsts_nopaste
? vim_strsave(p_vsts_nopaste)
: NULL;
buf->b_p_com = vim_strsave(p_com);
+ COPY_OPT_SCTX(buf, BV_COM);
buf->b_p_cms = vim_strsave(p_cms);
+ COPY_OPT_SCTX(buf, BV_CMS);
buf->b_p_fo = vim_strsave(p_fo);
+ COPY_OPT_SCTX(buf, BV_FO);
buf->b_p_flp = vim_strsave(p_flp);
+ COPY_OPT_SCTX(buf, BV_FLP);
buf->b_p_nf = vim_strsave(p_nf);
+ COPY_OPT_SCTX(buf, BV_NF);
buf->b_p_mps = vim_strsave(p_mps);
+ COPY_OPT_SCTX(buf, BV_MPS);
buf->b_p_si = p_si;
+ COPY_OPT_SCTX(buf, BV_SI);
buf->b_p_channel = 0;
buf->b_p_ci = p_ci;
+ COPY_OPT_SCTX(buf, BV_CI);
buf->b_p_cin = p_cin;
+ COPY_OPT_SCTX(buf, BV_CIN);
buf->b_p_cink = vim_strsave(p_cink);
+ COPY_OPT_SCTX(buf, BV_CINK);
buf->b_p_cino = vim_strsave(p_cino);
+ COPY_OPT_SCTX(buf, BV_CINO);
// Don't copy 'filetype', it must be detected
buf->b_p_ft = empty_option;
buf->b_p_pi = p_pi;
+ COPY_OPT_SCTX(buf, BV_PI);
buf->b_p_cinw = vim_strsave(p_cinw);
+ COPY_OPT_SCTX(buf, BV_CINW);
buf->b_p_lisp = p_lisp;
+ COPY_OPT_SCTX(buf, BV_LISP);
// Don't copy 'syntax', it must be set
buf->b_p_syn = empty_option;
buf->b_p_smc = p_smc;
+ COPY_OPT_SCTX(buf, BV_SMC);
buf->b_s.b_syn_isk = empty_option;
buf->b_s.b_p_spc = vim_strsave(p_spc);
+ COPY_OPT_SCTX(buf, BV_SPC);
(void)compile_cap_prog(&buf->b_s);
buf->b_s.b_p_spf = vim_strsave(p_spf);
+ COPY_OPT_SCTX(buf, BV_SPF);
buf->b_s.b_p_spl = vim_strsave(p_spl);
+ COPY_OPT_SCTX(buf, BV_SPL);
buf->b_s.b_p_spo = vim_strsave(p_spo);
+ COPY_OPT_SCTX(buf, BV_SPO);
buf->b_p_inde = vim_strsave(p_inde);
+ COPY_OPT_SCTX(buf, BV_INDE);
buf->b_p_indk = vim_strsave(p_indk);
+ COPY_OPT_SCTX(buf, BV_INDK);
buf->b_p_fp = empty_option;
buf->b_p_fex = vim_strsave(p_fex);
+ COPY_OPT_SCTX(buf, BV_FEX);
buf->b_p_sua = vim_strsave(p_sua);
+ COPY_OPT_SCTX(buf, BV_SUA);
buf->b_p_keymap = vim_strsave(p_keymap);
+ COPY_OPT_SCTX(buf, BV_KMAP);
buf->b_kmap_state |= KEYMAP_INIT;
// This isn't really an option, but copying the langmap and IME
// state from the current buffer is better than resetting it.
buf->b_p_iminsert = p_iminsert;
+ COPY_OPT_SCTX(buf, BV_IMI);
buf->b_p_imsearch = p_imsearch;
+ COPY_OPT_SCTX(buf, BV_IMS);
// options that are normally global but also have a local value
// are not copied, start using the global value
@@ -6431,11 +6545,14 @@ void buf_copy_options(buf_T *buf, int flags)
buf->b_p_def = empty_option;
buf->b_p_inc = empty_option;
buf->b_p_inex = vim_strsave(p_inex);
+ COPY_OPT_SCTX(buf, BV_INEX);
buf->b_p_dict = empty_option;
buf->b_p_tsr = empty_option;
buf->b_p_tsrfu = empty_option;
buf->b_p_qe = vim_strsave(p_qe);
+ COPY_OPT_SCTX(buf, BV_QE);
buf->b_p_udf = p_udf;
+ COPY_OPT_SCTX(buf, BV_UDF);
buf->b_p_lw = empty_option;
buf->b_p_menc = empty_option;
@@ -6448,17 +6565,20 @@ void buf_copy_options(buf_T *buf, int flags)
if (dont_do_help) {
buf->b_p_isk = save_p_isk;
if (p_vts && p_vts != empty_option && !buf->b_p_vts_array) {
- tabstop_set(p_vts, &buf->b_p_vts_array);
+ (void)tabstop_set(p_vts, &buf->b_p_vts_array);
} else {
buf->b_p_vts_array = NULL;
}
} else {
buf->b_p_isk = vim_strsave(p_isk);
+ COPY_OPT_SCTX(buf, BV_ISK);
did_isk = true;
buf->b_p_ts = p_ts;
+ COPY_OPT_SCTX(buf, BV_TS);
buf->b_p_vts = vim_strsave(p_vts);
+ COPY_OPT_SCTX(buf, BV_VTS);
if (p_vts && p_vts != empty_option && !buf->b_p_vts_array) {
- tabstop_set(p_vts, &buf->b_p_vts_array);
+ (void)tabstop_set(p_vts, &buf->b_p_vts_array);
} else {
buf->b_p_vts_array = NULL;
}
@@ -6467,6 +6587,7 @@ void buf_copy_options(buf_T *buf, int flags)
clear_string_option(&buf->b_p_bt);
}
buf->b_p_ma = p_ma;
+ COPY_OPT_SCTX(buf, BV_MA);
}
}
@@ -7107,10 +7228,7 @@ static void paste_option_changed(void)
free_string_option(buf->b_p_vsts);
}
buf->b_p_vsts = empty_option;
- if (buf->b_p_vsts_array) {
- xfree(buf->b_p_vsts_array);
- }
- buf->b_p_vsts_array = 0;
+ XFREE_CLEAR(buf->b_p_vsts_array);
}
// set global options
@@ -7147,13 +7265,11 @@ static void paste_option_changed(void)
buf->b_p_vsts = buf->b_p_vsts_nopaste
? vim_strsave(buf->b_p_vsts_nopaste)
: empty_option;
- if (buf->b_p_vsts_array) {
- xfree(buf->b_p_vsts_array);
- }
+ xfree(buf->b_p_vsts_array);
if (buf->b_p_vsts && buf->b_p_vsts != empty_option) {
- tabstop_set(buf->b_p_vsts, &buf->b_p_vsts_array);
+ (void)tabstop_set(buf->b_p_vsts, &buf->b_p_vsts_array);
} else {
- buf->b_p_vsts_array = 0;
+ buf->b_p_vsts_array = NULL;
}
}
@@ -7469,6 +7585,7 @@ int check_ff_value(char_u *p)
// Set the integer values corresponding to the string setting of 'vartabstop'.
// "array" will be set, caller must free it if needed.
+// Return false for an error.
bool tabstop_set(char_u *var, long **array)
{
long valcount = 1;
@@ -7488,7 +7605,7 @@ bool tabstop_set(char_u *var, long **array)
if (cp != end) {
emsg(_(e_positive));
} else {
- emsg(_(e_invarg));
+ semsg(_(e_invarg2), cp);
}
return false;
}
@@ -7501,7 +7618,7 @@ bool tabstop_set(char_u *var, long **array)
valcount++;
continue;
}
- emsg(_(e_invarg));
+ semsg(_(e_invarg2), var);
return false;
}
@@ -7510,7 +7627,15 @@ bool tabstop_set(char_u *var, long **array)
t = 1;
for (cp = var; *cp != NUL;) {
- (*array)[t++] = atoi((char *)cp);
+ int n = atoi((char *)cp);
+
+ // Catch negative values, overflow and ridiculous big values.
+ if (n <= 0 || n > TABSTOP_MAX) {
+ semsg(_(e_invarg2), cp);
+ XFREE_CLEAR(*array);
+ return false;
+ }
+ (*array)[t++] = n;
while (*cp != NUL && *cp != ',') {
cp++;
}
@@ -7815,6 +7940,12 @@ unsigned int get_bkc_value(buf_T *buf)
return buf->b_bkc_flags ? buf->b_bkc_flags : bkc_flags;
}
+/// Get the local or global value of the 'virtualedit' flags.
+unsigned int get_ve_flags(void)
+{
+ return (curwin->w_ve_flags ? curwin->w_ve_flags : ve_flags) & ~(VE_NONE | VE_NONEU);
+}
+
/// Get the local or global value of 'showbreak'.
///
/// @param win If not NULL, the window to get the local option from; global
@@ -7994,7 +8125,6 @@ int win_signcol_count(win_T *wp)
/// Return the number of requested sign columns, based on user / configuration.
int win_signcol_configured(win_T *wp, int *is_fixed)
{
- int minimum = 0, maximum = 1, needed_signcols;
const char *scl = (const char *)wp->w_p_scl;
if (is_fixed) {
@@ -8007,7 +8137,6 @@ int win_signcol_configured(win_T *wp, int *is_fixed)
&& (wp->w_p_nu || wp->w_p_rnu)))) {
return 0;
}
- needed_signcols = buf_signcols(wp->w_buffer);
// yes or yes
if (!strncmp(scl, "yes:", 4)) {
@@ -8023,6 +8152,8 @@ int win_signcol_configured(win_T *wp, int *is_fixed)
*is_fixed = 0;
}
+ int minimum = 0, maximum = 1;
+
if (!strncmp(scl, "auto:", 5)) {
// Variable depending on a configuration
maximum = scl[5] - '0';
@@ -8033,7 +8164,8 @@ int win_signcol_configured(win_T *wp, int *is_fixed)
}
}
- int ret = MAX(minimum, MIN(maximum, needed_signcols));
+ int needed_signcols = buf_signcols(wp->w_buffer, maximum);
+ int ret = MAX(minimum, needed_signcols);
assert(ret <= SIGN_SHOW_MAX);
return ret;
}
diff --git a/src/nvim/option.h b/src/nvim/option.h
index f7dbaafeec..9321dd5454 100644
--- a/src/nvim/option.h
+++ b/src/nvim/option.h
@@ -13,16 +13,16 @@
/// When OPT_GLOBAL and OPT_LOCAL are both missing, set both local and global
/// values, get local value.
typedef enum {
- OPT_FREE = 1, ///< Free old value if it was allocated.
- OPT_GLOBAL = 2, ///< Use global value.
- OPT_LOCAL = 4, ///< Use local value.
- OPT_MODELINE = 8, ///< Option in modeline.
- OPT_WINONLY = 16, ///< Only set window-local options.
- OPT_NOWIN = 32, ///< Don’t set window-local options.
- OPT_ONECOLUMN = 64, ///< list options one per line
- OPT_NO_REDRAW = 128, ///< ignore redraw flags on option
- OPT_SKIPRTP = 256, ///< "skiprtp" in 'sessionoptions'
- OPT_CLEAR = 512, ///< Clear local value of an option.
+ OPT_FREE = 0x01, ///< Free old value if it was allocated.
+ OPT_GLOBAL = 0x02, ///< Use global value.
+ OPT_LOCAL = 0x04, ///< Use local value.
+ OPT_MODELINE = 0x08, ///< Option in modeline.
+ OPT_WINONLY = 0x10, ///< Only set window-local options.
+ OPT_NOWIN = 0x20, ///< Don’t set window-local options.
+ OPT_ONECOLUMN = 0x40, ///< list options one per line
+ OPT_NO_REDRAW = 0x80, ///< ignore redraw flags on option
+ OPT_SKIPRTP = 0x100, ///< "skiprtp" in 'sessionoptions'
+ OPT_CLEAR = 0x200, ///< Clear local value of an option.
} OptionFlags;
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index 09c3bf3800..d88cd6b9b9 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -705,12 +705,14 @@ EXTERN int p_vb; ///< 'visualbell'
EXTERN char_u *p_ve; ///< 'virtualedit'
EXTERN unsigned ve_flags;
#ifdef IN_OPTION_C
-static char *(p_ve_values[]) = { "block", "insert", "all", "onemore", NULL };
+static char *(p_ve_values[]) = { "block", "insert", "all", "onemore", "none", "NONE", NULL };
#endif
-#define VE_BLOCK 5 // includes "all"
-#define VE_INSERT 6 // includes "all"
-#define VE_ALL 4
-#define VE_ONEMORE 8
+#define VE_BLOCK 5U // includes "all"
+#define VE_INSERT 6U // includes "all"
+#define VE_ALL 4U
+#define VE_ONEMORE 8U
+#define VE_NONE 16U // "none"
+#define VE_NONEU 32U // "NONE"
EXTERN long p_verbose; // 'verbose'
#ifdef IN_OPTION_C
char_u *p_vfile = (char_u *)""; // used before options are initialized
@@ -869,6 +871,7 @@ enum {
WV_LBR,
WV_NU,
WV_RNU,
+ WV_VE,
WV_NUW,
WV_PVW,
WV_RL,
@@ -900,6 +903,8 @@ enum {
#define SB_MAX 100000 // Maximum 'scrollback' value.
+#define TABSTOP_MAX 9999
+
/// Stores an identifier of a script or channel that last set an option.
typedef struct {
sctx_T script_ctx; /// script context where the option was last set
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 28b4eb9fe2..e665ffd346 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -411,7 +411,7 @@ return {
},
{
full_name='compatible', abbreviation='cp',
- short_desc=N_("No description"),
+ short_desc=N_("No description"),
type='bool', scope={'global'},
redraw={'all_windows'},
varname='p_force_off',
@@ -665,14 +665,14 @@ return {
},
{
full_name='edcompatible', abbreviation='ed',
- short_desc=N_("No description"),
+ short_desc=N_("No description"),
type='bool', scope={'global'},
varname='p_force_off',
defaults={if_true=false}
},
{
full_name='emoji', abbreviation='emo',
- short_desc=N_("No description"),
+ short_desc=N_("No description"),
type='bool', scope={'global'},
redraw={'all_windows', 'ui_option'},
varname='p_emoji',
@@ -1184,7 +1184,7 @@ return {
},
{
full_name='inccommand', abbreviation='icm',
- short_desc=N_("Live preview of substitution"),
+ short_desc=N_("Live preview of substitution"),
type='string', scope={'global'},
redraw={'all_windows'},
varname='p_icm',
@@ -1846,7 +1846,7 @@ return {
type='number', scope={'global'},
secure=true,
varname='p_pyx',
- defaults={if_true=0}
+ defaults={if_true=3}
},
{
full_name='quickfixtextfunc', abbreviation='qftf',
@@ -2499,7 +2499,7 @@ return {
},
{
full_name='termencoding', abbreviation='tenc',
- short_desc=N_("Terminal encodig"),
+ short_desc=N_("Terminal encoding"),
type='string', scope={'global'},
defaults={if_true=""}
},
@@ -2622,7 +2622,7 @@ return {
},
{
full_name='ttyfast', abbreviation='tf',
- short_desc=N_("No description"),
+ short_desc=N_("No description"),
type='bool', scope={'global'},
no_mkrc=true,
varname='p_force_on',
@@ -2736,7 +2736,7 @@ return {
{
full_name='virtualedit', abbreviation='ve',
short_desc=N_("when to use virtual editing"),
- type='string', list='onecomma', scope={'global'},
+ type='string', list='onecomma', scope={'global', 'window'},
deny_duplicates=true,
redraw={'curswant'},
varname='p_ve',
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
index e9f44d2775..e9868d6b61 100644
--- a/src/nvim/os/env.c
+++ b/src/nvim/os/env.c
@@ -1111,10 +1111,9 @@ size_t home_replace(const buf_T *const buf, const char_u *src, char_u *const dst
*dst_p++ = '~';
}
- // If it's just the home directory, add "/".
- if (!vim_ispathsep(src[0]) && --dstlen > 0) {
- *dst_p++ = '/';
- }
+ // Do not add directory separator into dst, because dst is
+ // expected to just return the directory name without the
+ // directory separator '/'.
break;
}
if (p == homedir_env_mod) {
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index 3790eba212..54cfaee80a 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -234,9 +234,9 @@ size_t input_enqueue(String keys)
while (rbuffer_space(input_buffer) >= 19 && ptr < end) {
// A "<x>" form occupies at least 1 characters, and produces up
// to 19 characters (1 + 5 * 3 for the char and 3 for a modifier).
- // In the case of K_SPECIAL(0x80) or CSI(0x9B), 3 bytes are escaped and
- // needed, but since the keys are UTF-8, so the first byte cannot be
- // K_SPECIAL(0x80) or CSI(0x9B).
+ // In the case of K_SPECIAL(0x80), 3 bytes are escaped and needed,
+ // but since the keys are UTF-8, so the first byte cannot be
+ // K_SPECIAL(0x80).
uint8_t buf[19] = { 0 };
unsigned int new_size
= trans_special((const uint8_t **)&ptr, (size_t)(end - ptr), buf, true,
@@ -263,12 +263,8 @@ size_t input_enqueue(String keys)
continue;
}
- // copy the character, escaping CSI and K_SPECIAL
- if ((uint8_t)*ptr == CSI) {
- rbuffer_write(input_buffer, (char *)&(uint8_t){ K_SPECIAL }, 1);
- rbuffer_write(input_buffer, (char *)&(uint8_t){ KS_EXTRA }, 1);
- rbuffer_write(input_buffer, (char *)&(uint8_t){ KE_CSI }, 1);
- } else if ((uint8_t)*ptr == K_SPECIAL) {
+ // copy the character, escaping K_SPECIAL
+ if ((uint8_t)(*ptr) == K_SPECIAL) {
rbuffer_write(input_buffer, (char *)&(uint8_t){ K_SPECIAL }, 1);
rbuffer_write(input_buffer, (char *)&(uint8_t){ KS_SPECIAL }, 1);
rbuffer_write(input_buffer, (char *)&(uint8_t){ KE_FILLER }, 1);
diff --git a/src/nvim/path.c b/src/nvim/path.c
index 674d67e21a..7f7f941e26 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -269,16 +269,17 @@ int vim_ispathlistsep(int c)
#endif
}
-/*
- * Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname"
- * It's done in-place.
- */
-char_u *shorten_dir(char_u *str)
+/// Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname"
+/// "trim_len" specifies how many characters to keep for each directory.
+/// Must be 1 or more.
+/// It's done in-place.
+void shorten_dir_len(char_u *str, int trim_len)
{
char_u *tail = path_tail(str);
char_u *d = str;
bool skip = false;
- for (char_u *s = str;; ++s) {
+ int dirchunk_len = 0;
+ for (char_u *s = str;; s++) {
if (s >= tail) { // copy the whole tail
*d++ = *s;
if (*s == NUL) {
@@ -287,10 +288,16 @@ char_u *shorten_dir(char_u *str)
} else if (vim_ispathsep(*s)) { // copy '/' and next char
*d++ = *s;
skip = false;
+ dirchunk_len = 0;
} else if (!skip) {
*d++ = *s; // copy next char
if (*s != '~' && *s != '.') { // and leading "~" and "."
- skip = true;
+ dirchunk_len++; // only count word chars for the size
+ // keep copying chars until we have our preferred length (or
+ // until the above if/else branches move us along)
+ if (dirchunk_len >= trim_len) {
+ skip = true;
+ }
}
int l = utfc_ptr2len(s);
while (--l > 0) {
@@ -298,7 +305,13 @@ char_u *shorten_dir(char_u *str)
}
}
}
- return str;
+}
+
+/// Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname"
+/// It's done in-place.
+void shorten_dir(char_u *str)
+{
+ shorten_dir_len(str, 1);
}
/*
@@ -1508,7 +1521,7 @@ void simplify_filename(char_u *filename)
p = filename;
#ifdef BACKSLASH_IN_FILENAME
- if (p[1] == ':') { // skip "x:"
+ if (p[0] != NUL && p[1] == ':') { // skip "x:"
p += 2;
}
#endif
@@ -1682,6 +1695,10 @@ char_u *find_file_name_in_path(char_u *ptr, size_t len, int options, long count,
char_u *file_name;
char_u *tofree = NULL;
+ if (len == 0) {
+ return NULL;
+ }
+
if ((options & FNAME_INCL) && *curbuf->b_p_inex != NUL) {
tofree = (char_u *)eval_includeexpr((char *)ptr, len);
if (tofree != NULL) {
@@ -1743,14 +1760,32 @@ int path_is_url(const char *p)
return 0;
}
-/// Check if "fname" starts with "name://". Return URL_SLASH if it does.
+/// Check if "fname" starts with "name://" or "name:\\".
///
/// @param fname is the filename to test
-/// @return URL_BACKSLASH for "name:\\", zero otherwise.
+/// @return URL_SLASH for "name://", URL_BACKSLASH for "name:\\", zero otherwise.
int path_with_url(const char *fname)
{
const char *p;
- for (p = fname; isalpha(*p); p++) {}
+
+ // We accept alphabetic characters and a dash in scheme part.
+ // RFC 3986 allows for more, but it increases the risk of matching
+ // non-URL text.
+
+ // first character must be alpha
+ if (!isalpha(*fname)) {
+ return 0;
+ }
+
+ // check body: alpha or dash
+ for (p = fname; (isalpha(*p) || (*p == '-')); p++) {}
+
+ // check last char is not a dash
+ if (p[-1] == '-') {
+ return 0;
+ }
+
+ // "://" or ":\\" must follow
return path_is_url(p);
}
@@ -2367,9 +2402,11 @@ static int path_to_absolute(const char_u *fname, char_u *buf, size_t len, int fo
int path_is_absolute(const char_u *fname)
{
#ifdef WIN32
+ if (*fname == NUL) {
+ return false;
+ }
// A name like "d:/foo" and "//server/share" is absolute
- return ((isalpha(fname[0]) && fname[1] == ':'
- && vim_ispathsep_nocolon(fname[2]))
+ return ((isalpha(fname[0]) && fname[1] == ':' && vim_ispathsep_nocolon(fname[2]))
|| (vim_ispathsep_nocolon(fname[0]) && fname[0] == fname[1]));
#else
// UNIX: This just checks if the file name starts with '/' or '~'.
diff --git a/src/nvim/po/ja.euc-jp.po b/src/nvim/po/ja.euc-jp.po
index 5dda7c59f5..9633bec9f2 100644
--- a/src/nvim/po/ja.euc-jp.po
+++ b/src/nvim/po/ja.euc-jp.po
@@ -1644,15 +1644,6 @@ msgstr " [w]"
msgid " written"
msgstr " "
-msgid "E205: Patchmode: can't save original file"
-msgstr "E205: patchmode: ܥե¸Ǥޤ"
-
-msgid "E206: patchmode: can't touch empty original file"
-msgstr "E206: patchmode: θܥեtouchǤޤ"
-
-msgid "E207: Can't delete backup file"
-msgstr "E207: Хååץեäޤ"
-
msgid ""
"\n"
"WARNING: Original file may be lost or damaged\n"
diff --git a/src/nvim/po/ja.po b/src/nvim/po/ja.po
index a169bd3589..c363c00fa6 100644
--- a/src/nvim/po/ja.po
+++ b/src/nvim/po/ja.po
@@ -1644,15 +1644,6 @@ msgstr " [w]"
msgid " written"
msgstr " 書込み"
-msgid "E205: Patchmode: can't save original file"
-msgstr "E205: patchmode: 原本ファイルを保存できません"
-
-msgid "E206: patchmode: can't touch empty original file"
-msgstr "E206: patchmode: 空の原本ファイルをtouchできません"
-
-msgid "E207: Can't delete backup file"
-msgstr "E207: バックアップファイルを消せません"
-
msgid ""
"\n"
"WARNING: Original file may be lost or damaged\n"
diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c
index da2ada791f..d7726409b5 100644
--- a/src/nvim/popupmnu.c
+++ b/src/nvim/popupmnu.c
@@ -291,7 +291,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
} else {
assert(Columns - pum_col - pum_scrollbar >= INT_MIN
&& Columns - pum_col - pum_scrollbar <= INT_MAX);
- pum_width = (int)(Columns - pum_col - pum_scrollbar);
+ pum_width = Columns - pum_col - pum_scrollbar;
}
if ((pum_width > max_width + pum_kind_width + pum_extra_width + 1)
@@ -352,12 +352,12 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
// not enough room, will use what we have
if (pum_rl) {
assert(Columns - 1 >= INT_MIN);
- pum_col = (int)(Columns - 1);
+ pum_col = Columns - 1;
} else {
pum_col = 0;
}
assert(Columns - 1 >= INT_MIN);
- pum_width = (int)(Columns - 1);
+ pum_width = Columns - 1;
} else {
if (max_width > p_pw) {
// truncate
@@ -369,7 +369,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
} else {
assert(Columns - max_width >= INT_MIN
&& Columns - max_width <= INT_MAX);
- pum_col = (int)(Columns - max_width);
+ pum_col = Columns - max_width;
}
pum_width = max_width - pum_scrollbar;
}
diff --git a/src/nvim/pos.h b/src/nvim/pos.h
index d17e27906e..51991ed314 100644
--- a/src/nvim/pos.h
+++ b/src/nvim/pos.h
@@ -16,7 +16,9 @@ typedef int colnr_T;
/// Maximal (invalid) line number
enum { MAXLNUM = 0x7fffffff, };
/// Maximal column number
-enum { MAXCOL = INT_MAX, };
+/// MAXCOL used to be INT_MAX, but with 64 bit ints that results in running
+/// out of memory when trying to allocate a very long line.
+enum { MAXCOL = 0x7fffffff, };
// Minimum line number
enum { MINLNUM = 1, };
// minimum column number
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 0196e05455..7e29aed51b 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -131,7 +131,7 @@ struct qf_info_S {
static qf_info_T ql_info; // global quickfix list
static unsigned last_qf_id = 0; // Last Used quickfix list id
-#define FMT_PATTERNS 11 // maximum number of % recognized
+#define FMT_PATTERNS 13 // maximum number of % recognized
// Structure used to hold the info of one part of 'errorformat'
typedef struct efm_S efm_T;
@@ -209,6 +209,17 @@ typedef struct {
bool valid;
} qffields_T;
+/// :vimgrep command arguments
+typedef struct vgr_args_S {
+ long tomatch; ///< maximum number of matches to find
+ char_u *spat; ///< search pattern
+ int flags; ///< search modifier
+ char_u **fnames; ///< list of files to search
+ int fcount; ///< number of files
+ regmmatch_T regmatch; ///< compiled search pattern
+ char_u *qf_title; ///< quickfix list title
+} vgr_args_T;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "quickfix.c.generated.h"
#endif
@@ -321,22 +332,27 @@ int qf_init(win_T *wp, const char_u *restrict efile, char_u *restrict errorforma
// Maximum number of bytes allowed per line while reading an errorfile.
static const size_t LINE_MAXLEN = 4096;
+/// Patterns used. Keep in sync with qf_parse_fmt[].
static struct fmtpattern {
char_u convchar;
char *pattern;
} fmt_pat[FMT_PATTERNS] =
{
- { 'f', ".\\+" }, // only used when at end
- { 'n', "\\d\\+" },
- { 'l', "\\d\\+" },
- { 'c', "\\d\\+" },
- { 't', "." },
- { 'm', ".\\+" },
- { 'r', ".*" },
- { 'p', "[- .]*"}, // NOLINT(whitespace/tab)
- { 'v', "\\d\\+" },
- { 's', ".\\+" },
- { 'o', ".\\+" }
+ { 'f', ".\\+" }, // only used when at end
+ { 'n', "\\d\\+" }, // 1
+ { 'l', "\\d\\+" }, // 2
+ { 'e', "\\d\\+" }, // 3
+ { 'c', "\\d\\+" }, // 4
+ { 'k', "\\d\\+" }, // 5
+ { 't', "." }, // 6
+#define FMT_PATTERN_M 7
+ { 'm', ".\\+" }, // 7
+#define FMT_PATTERN_R 8
+ { 'r', ".*" }, // 8
+ { 'p', "[- \t.]*" }, // 9
+ { 'v', "\\d\\+" }, // 10
+ { 's', ".\\+" }, // 11
+ { 'o', ".\\+" } // 12
};
/// Convert an errorformat pattern to a regular expression pattern.
@@ -352,9 +368,9 @@ static char_u *efmpat_to_regpat(const char_u *efmpat, char_u *regpat, efm_T *efm
semsg(_("E372: Too many %%%c in format string"), *efmpat);
return NULL;
}
- if ((idx && idx < 6
+ if ((idx && idx < FMT_PATTERN_R
&& vim_strchr((char_u *)"DXOPQ", efminfo->prefix) != NULL)
- || (idx == 6
+ || (idx == FMT_PATTERN_R
&& vim_strchr((char_u *)"OPQ", efminfo->prefix) == NULL)) {
semsg(_("E373: Unexpected %%%c in format string"), *efmpat);
return NULL;
@@ -1277,7 +1293,7 @@ static int qf_parse_fmt_n(regmatch_T *rmp, int midx, qffields_T *fields)
return QF_OK;
}
-/// Parse the match for line number (%l') pattern in regmatch.
+/// 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)
{
@@ -1288,6 +1304,17 @@ static int qf_parse_fmt_l(regmatch_T *rmp, int midx, qffields_T *fields)
return QF_OK;
}
+/// Parse the match for end line number ('%e') pattern in regmatch.
+/// Return the matched value in "fields->end_lnum".
+static int qf_parse_fmt_e(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ if (rmp->startp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ fields->end_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)
@@ -1299,6 +1326,17 @@ static int qf_parse_fmt_c(regmatch_T *rmp, int midx, qffields_T *fields)
return QF_OK;
}
+/// Parse the match for end line number ('%e') pattern in regmatch.
+/// Return the matched value in "fields->end_lnum".
+static int qf_parse_fmt_k(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ if (rmp->startp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ fields->end_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)
@@ -1431,14 +1469,17 @@ static int qf_parse_fmt_o(regmatch_T *rmp, int midx, qffields_T *fields)
/// 'errorformat' format pattern parser functions.
/// The '%f' and '%r' formats are parsed differently from other formats.
/// See qf_parse_match() for details.
+/// Keep in sync with fmt_pat[].
static int (*qf_parse_fmt[FMT_PATTERNS])(regmatch_T *, int, qffields_T *) = {
- NULL,
+ NULL, // %f
qf_parse_fmt_n,
qf_parse_fmt_l,
+ qf_parse_fmt_e,
qf_parse_fmt_c,
+ qf_parse_fmt_k,
qf_parse_fmt_t,
qf_parse_fmt_m,
- NULL,
+ NULL, // %r
qf_parse_fmt_p,
qf_parse_fmt_v,
qf_parse_fmt_s,
@@ -1474,13 +1515,13 @@ static int qf_parse_match(char_u *linebuf, size_t linelen, efm_T *fmt_ptr, regma
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) {
+ } else if (i == FMT_PATTERN_M) {
if (fmt_ptr->flags == '+' && !qf_multiscan) { // %+
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
+ } else if (i == FMT_PATTERN_R && midx > 0) { // %r
status = qf_parse_fmt_r(regmatch, midx, tail);
} else if (midx > 0) { // others
status = (qf_parse_fmt[i])(regmatch, midx, fields);
@@ -1625,10 +1666,16 @@ static int qf_parse_multiline_pfx(int idx, qf_list_T *qfl, qffields_T *fields)
if (!qfprev->qf_lnum) {
qfprev->qf_lnum = fields->lnum;
}
+ if (!qfprev->qf_end_lnum) {
+ qfprev->qf_end_lnum = fields->end_lnum;
+ }
if (!qfprev->qf_col) {
qfprev->qf_col = fields->col;
qfprev->qf_viscol = fields->use_viscol;
}
+ if (!qfprev->qf_end_col) {
+ qfprev->qf_end_col = fields->end_col;
+ }
if (!qfprev->qf_fnum) {
qfprev->qf_fnum = qf_get_fnum(qfl, qfl->qf_directory,
*fields->namebuf || qfl->qf_directory
@@ -2975,7 +3022,7 @@ static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit, boo
if (retval != OK) {
if (opened_window) {
- win_close(curwin, true); // Close opened window
+ win_close(curwin, true, false); // Close opened window
}
if (qf_ptr != NULL && qf_ptr->qf_fnum != 0) {
// Couldn't open file, so put index back where it was. This could
@@ -3530,7 +3577,7 @@ void ex_cclose(exarg_T *eap)
// Find existing quickfix window and close it.
win = qf_find_win(qi);
if (win != NULL) {
- win_close(win, false);
+ win_close(win, false, false);
}
}
@@ -3604,7 +3651,7 @@ static int qf_open_new_cwindow(qf_info_T *qi, int height)
// win_split, so add a check to ensure that the win is still here
if (IS_LL_STACK(qi) && !win_valid(win)) {
// close the window that was supposed to be for the loclist
- win_close(curwin, false);
+ win_close(curwin, false, false);
return FAIL;
}
@@ -4849,11 +4896,12 @@ static qfline_T *qf_find_closest_entry(qf_list_T *qfl, int bnr, const pos_T *pos
/// Get the nth quickfix entry below the specified entry. Searches forward in
/// the list. If linewise is true, then treat multiple entries on a single line
/// as one.
-static void qf_get_nth_below_entry(qfline_T *entry, linenr_T n, bool linewise, int *errornr)
+static void qf_get_nth_below_entry(qfline_T *entry_arg, linenr_T n, bool linewise, int *errornr)
FUNC_ATTR_NONNULL_ALL
{
+ qfline_T *entry = entry_arg;
+
while (n-- > 0 && !got_int) {
- // qfline_T *first_entry = entry;
int first_errornr = *errornr;
if (linewise) {
@@ -4864,9 +4912,6 @@ static void qf_get_nth_below_entry(qfline_T *entry, linenr_T n, bool linewise, i
if (entry->qf_next == NULL
|| entry->qf_next->qf_fnum != entry->qf_fnum) {
if (linewise) {
- // If multiple entries are on the same line, then use the first
- // entry
- // entry = first_entry;
*errornr = first_errornr;
}
break;
@@ -5194,49 +5239,93 @@ static bool vgr_qflist_valid(win_T *wp, qf_info_T *qi, unsigned qfid, char_u *ti
/// Search for a pattern in all the lines in a buffer and add the matching lines
/// to a quickfix list.
-static bool vgr_match_buflines(qf_list_T *qfl, char_u *fname, buf_T *buf, regmmatch_T *regmatch,
- long *tomatch, int duplicate_name, int flags)
- FUNC_ATTR_NONNULL_ARG(1, 3, 4, 5)
+static bool vgr_match_buflines(qf_list_T *qfl, char_u *fname, buf_T *buf, char_u *spat,
+ regmmatch_T *regmatch, long *tomatch, int duplicate_name, int flags)
+ FUNC_ATTR_NONNULL_ARG(1, 3, 4, 5, 6)
{
bool found_match = false;
for (long lnum = 1; lnum <= buf->b_ml.ml_line_count && *tomatch > 0; lnum++) {
colnr_T col = 0;
- while (vim_regexec_multi(regmatch, curwin, buf, lnum, col, NULL,
- NULL) > 0) {
- // Pass the buffer number so that it gets used even for a
- // dummy buffer, unless duplicate_name is set, then the
- // buffer will be wiped out below.
- if (qf_add_entry(qfl,
- NULL, // dir
- fname,
- NULL,
- duplicate_name ? 0 : buf->b_fnum,
- ml_get_buf(buf, regmatch->startpos[0].lnum + lnum,
- false),
- regmatch->startpos[0].lnum + lnum,
- regmatch->endpos[0].lnum + lnum,
- regmatch->startpos[0].col + 1,
- regmatch->endpos[0].col + 1,
- false, // vis_col
- NULL, // search pattern
- 0, // nr
- 0, // type
- true) // valid
- == QF_FAIL) {
- got_int = true;
- break;
- }
- found_match = true;
- if (--*tomatch == 0) {
- break;
- }
- if ((flags & VGR_GLOBAL) == 0 || regmatch->endpos[0].lnum > 0) {
- break;
+ if (!(flags & VGR_FUZZY)) {
+ // Regular expression match
+ while (vim_regexec_multi(regmatch, curwin, buf, lnum, col, NULL, NULL) > 0) {
+ // Pass the buffer number so that it gets used even for a
+ // dummy buffer, unless duplicate_name is set, then the
+ // buffer will be wiped out below.
+ if (qf_add_entry(qfl,
+ NULL, // dir
+ fname,
+ NULL,
+ duplicate_name ? 0 : buf->b_fnum,
+ ml_get_buf(buf, regmatch->startpos[0].lnum + lnum, false),
+ regmatch->startpos[0].lnum + lnum,
+ regmatch->endpos[0].lnum + lnum,
+ regmatch->startpos[0].col + 1,
+ regmatch->endpos[0].col + 1,
+ false, // vis_col
+ NULL, // search pattern
+ 0, // nr
+ 0, // type
+ true) // valid
+ == QF_FAIL) {
+ got_int = true;
+ break;
+ }
+ found_match = true;
+ if (--*tomatch == 0) {
+ break;
+ }
+ if ((flags & VGR_GLOBAL) == 0 || regmatch->endpos[0].lnum > 0) {
+ break;
+ }
+ col = regmatch->endpos[0].col + (col == regmatch->endpos[0].col);
+ if (col > (colnr_T)STRLEN(ml_get_buf(buf, lnum, false))) {
+ break;
+ }
}
- col = regmatch->endpos[0].col + (col == regmatch->endpos[0].col);
- if (col > (colnr_T)STRLEN(ml_get_buf(buf, lnum, false))) {
- break;
+ } else {
+ const size_t pat_len = STRLEN(spat);
+ char_u *const str = ml_get_buf(buf, lnum, false);
+ int score;
+ uint32_t matches[MAX_FUZZY_MATCHES];
+ const size_t sz = sizeof(matches) / sizeof(matches[0]);
+
+ // Fuzzy string match
+ while (fuzzy_match(str + col, spat, false, &score, matches, (int)sz) > 0) {
+ // Pass the buffer number so that it gets used even for a
+ // dummy buffer, unless duplicate_name is set, then the
+ // buffer will be wiped out below.
+ if (qf_add_entry(qfl,
+ NULL, // dir
+ fname,
+ NULL,
+ duplicate_name ? 0 : buf->b_fnum,
+ str,
+ lnum,
+ 0,
+ (colnr_T)matches[0] + col + 1,
+ 0,
+ false, // vis_col
+ NULL, // search pattern
+ 0, // nr
+ 0, // type
+ true) // valid
+ == QF_FAIL) {
+ got_int = true;
+ break;
+ }
+ found_match = true;
+ if (--*tomatch == 0) {
+ break;
+ }
+ if ((flags & VGR_GLOBAL) == 0) {
+ break;
+ }
+ col = (colnr_T)matches[pat_len - 1] + col + 1;
+ if (col > (colnr_T)STRLEN(str)) {
+ break;
+ }
}
}
line_breakcheck();
@@ -5249,7 +5338,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char_u *fname, buf_T *buf, regmma
}
/// Jump to the first match and update the directory.
-static void vgr_jump_to_match(qf_info_T *qi, int forceit, int *redraw_for_dummy,
+static void vgr_jump_to_match(qf_info_T *qi, int forceit, bool *redraw_for_dummy,
buf_T *first_match_buf, char_u *target_dir)
{
buf_T *buf = curbuf;
@@ -5284,104 +5373,72 @@ static bool existing_swapfile(const buf_T *buf)
return false;
}
-// ":vimgrep {pattern} file(s)"
-// ":vimgrepadd {pattern} file(s)"
-// ":lvimgrep {pattern} file(s)"
-// ":lvimgrepadd {pattern} file(s)"
-void ex_vimgrep(exarg_T *eap)
+/// Process :vimgrep command arguments. The command syntax is:
+///
+/// :{count}vimgrep /{pattern}/[g][j] {file} ...
+static int vgr_process_args(exarg_T *eap, vgr_args_T *args)
{
- regmmatch_T regmatch;
- int fcount;
- char_u **fnames;
- char_u *fname;
- char_u *s;
- char_u *p;
- int fi;
- qf_list_T *qfl;
- win_T *wp = NULL;
- buf_T *buf;
- int duplicate_name = FALSE;
- int using_dummy;
- int redraw_for_dummy = FALSE;
- int found_match;
- buf_T *first_match_buf = NULL;
- time_t seconds = 0;
- aco_save_T aco;
- int flags = 0;
- long tomatch;
- char_u *dirname_start = NULL;
- char_u *dirname_now = NULL;
- char_u *target_dir = NULL;
- char_u *au_name = NULL;
-
- au_name = vgr_get_auname(eap->cmdidx);
- if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name,
- curbuf->b_fname, true, curbuf)) {
- if (aborting()) {
- return;
- }
- }
+ memset(args, 0, sizeof(*args));
- qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp);
+ args->regmatch.regprog = NULL;
+ args->qf_title = vim_strsave(qf_cmdtitle(*eap->cmdlinep));
if (eap->addr_count > 0) {
- tomatch = eap->line2;
+ args->tomatch = eap->line2;
} else {
- tomatch = MAXLNUM;
+ args->tomatch = MAXLNUM;
}
// 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);
+ char_u *p = skip_vimgrep_pat(eap->arg, &args->spat, &args->flags);
if (p == NULL) {
emsg(_(e_invalpat));
- goto theend;
+ return FAIL;
}
- vgr_init_regmatch(&regmatch, s);
- if (regmatch.regprog == NULL) {
- goto theend;
+ vgr_init_regmatch(&args->regmatch, args->spat);
+ if (args->regmatch.regprog == NULL) {
+ return FAIL;
}
p = skipwhite(p);
if (*p == NUL) {
emsg(_("E683: File name missing or invalid pattern"));
- goto theend;
- }
-
- if ((eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd
- && eap->cmdidx != CMD_vimgrepadd && eap->cmdidx != CMD_lvimgrepadd)
- || qf_stack_empty(qi)) {
- // make place for a new list
- qf_new_list(qi, title);
+ return FAIL;
}
// Parse the list of arguments, wildcards have already been expanded.
- if (get_arglist_exp(p, &fcount, &fnames, true) == FAIL) {
- goto theend;
+ if (get_arglist_exp(p, &args->fcount, &args->fnames, true) == FAIL) {
+ return FAIL;
}
- if (fcount == 0) {
+ if (args->fcount == 0) {
emsg(_(e_nomatch));
- goto theend;
+ return FAIL;
}
- dirname_start = xmalloc(MAXPATHL);
- dirname_now = xmalloc(MAXPATHL);
+ return OK;
+}
+
+/// Search for a pattern in a list of files and populate the quickfix list with
+/// the matches.
+static int vgr_process_files(win_T *wp, qf_info_T *qi, vgr_args_T *cmd_args,
+ bool *redraw_for_dummy, buf_T **first_match_buf,
+ char_u **target_dir)
+{
+ int status = FAIL;
+ unsigned save_qfid = qf_get_curlist(qi)->qf_id;
+ bool duplicate_name = false;
+
+ char_u *dirname_start = xmalloc(MAXPATHL);
+ char_u *dirname_now = xmalloc(MAXPATHL);
// 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 = 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]);
+ time_t seconds = (time_t)0;
+ for (int fi = 0; fi < cmd_args->fcount && !got_int && cmd_args->tomatch > 0; fi++) {
+ char_u *fname = path_try_shorten_fname(cmd_args->fnames[fi]);
if (time(NULL) > seconds) {
// Display the file name every second or so, show the user we are
// working on it.
@@ -5389,13 +5446,13 @@ void ex_vimgrep(exarg_T *eap)
vgr_display_fname(fname);
}
- buf = buflist_findname_exp(fnames[fi]);
+ buf_T *buf = buflist_findname_exp(cmd_args->fnames[fi]);
+ bool using_dummy;
if (buf == NULL || buf->b_ml.ml_mfp == NULL) {
// Remember that a buffer with this name already exists.
duplicate_name = (buf != NULL);
- using_dummy = TRUE;
- redraw_for_dummy = TRUE;
-
+ using_dummy = true;
+ *redraw_for_dummy = true;
buf = vgr_load_dummy_buf(fname, dirname_start, dirname_now);
} else {
// Use existing, loaded buffer.
@@ -5404,11 +5461,10 @@ void ex_vimgrep(exarg_T *eap)
// Check whether the quickfix list is still valid. When loading a
// 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();
+ if (!vgr_qflist_valid(wp, qi, save_qfid, cmd_args->qf_title)) {
goto theend;
}
+
save_qfid = qf_get_curlist(qi)->qf_id;
if (buf == NULL) {
@@ -5418,13 +5474,18 @@ void ex_vimgrep(exarg_T *eap)
} else {
// Try for a match in all lines of the buffer.
// For ":1vimgrep" look for first match only.
- found_match = vgr_match_buflines(qf_get_curlist(qi),
- fname, buf, &regmatch, &tomatch,
- duplicate_name, flags);
+ bool found_match = vgr_match_buflines(qf_get_curlist(qi),
+ fname,
+ buf,
+ cmd_args->spat,
+ &cmd_args->regmatch,
+ &cmd_args->tomatch,
+ duplicate_name,
+ cmd_args->flags);
if (using_dummy) {
- if (found_match && first_match_buf == NULL) {
- first_match_buf = buf;
+ if (found_match && *first_match_buf == NULL) {
+ *first_match_buf = buf;
}
if (duplicate_name) {
// Never keep a dummy buffer if there is another buffer
@@ -5444,8 +5505,8 @@ void ex_vimgrep(exarg_T *eap)
if (!found_match) {
wipe_dummy_buffer(buf, dirname_start);
buf = NULL;
- } else if (buf != first_match_buf
- || (flags & VGR_NOJUMP)
+ } else if (buf != *first_match_buf
+ || (cmd_args->flags & VGR_NOJUMP)
|| existing_swapfile(buf)) {
unload_dummy_buffer(buf, dirname_start);
// Keeping the buffer, remove the dummy flag.
@@ -5460,16 +5521,17 @@ void ex_vimgrep(exarg_T *eap)
// If the buffer is still loaded we need to use the
// directory we jumped to below.
- if (buf == first_match_buf
- && target_dir == NULL
+ if (buf == *first_match_buf
+ && *target_dir == NULL
&& STRCMP(dirname_start, dirname_now) != 0) {
- target_dir = vim_strsave(dirname_now);
+ *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!
+ aco_save_T aco;
aucmd_prepbuf(&aco, buf);
apply_autocmds(EVENT_FILETYPE, buf->b_p_ft, buf->b_fname, true, buf);
do_modelines(OPT_NOWIN);
@@ -5479,9 +5541,58 @@ void ex_vimgrep(exarg_T *eap)
}
}
- FreeWild(fcount, fnames);
+ status = OK;
- qfl = qf_get_curlist(qi);
+theend:
+ xfree(dirname_now);
+ xfree(dirname_start);
+ return status;
+}
+
+/// ":vimgrep {pattern} file(s)"
+/// ":vimgrepadd {pattern} file(s)"
+/// ":lvimgrep {pattern} file(s)"
+/// ":lvimgrepadd {pattern} file(s)"
+void ex_vimgrep(exarg_T *eap)
+{
+ char_u *au_name = vgr_get_auname(eap->cmdidx);
+ if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name,
+ curbuf->b_fname, true, curbuf)) {
+ if (aborting()) {
+ return;
+ }
+ }
+
+ win_T *wp = NULL;
+ qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp);
+ char_u *target_dir = NULL;
+ vgr_args_T args;
+ if (vgr_process_args(eap, &args) == FAIL) {
+ goto theend;
+ }
+
+ if ((eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd
+ && eap->cmdidx != CMD_vimgrepadd && eap->cmdidx != CMD_lvimgrepadd)
+ || qf_stack_empty(qi)) {
+ // make place for a new list
+ qf_new_list(qi, args.qf_title);
+ }
+
+ incr_quickfix_busy();
+
+ bool redraw_for_dummy = false;
+ buf_T *first_match_buf = NULL;
+ int status = vgr_process_files(wp, qi, &args, &redraw_for_dummy, &first_match_buf, &target_dir);
+
+ if (status != OK) {
+ FreeWild(args.fcount, args.fnames);
+ decr_quickfix_busy();
+ goto theend;
+ }
+
+ FreeWild(args.fcount, args.fnames);
+
+ qf_list_T *qfl = qf_get_curlist(qi);
qfl->qf_nonevalid = false;
qfl->qf_ptr = qfl->qf_start;
qfl->qf_index = 1;
@@ -5489,26 +5600,28 @@ void ex_vimgrep(exarg_T *eap)
qf_update_buffer(qi, NULL);
+ // 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) {
apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, true, curbuf);
}
// The QuickFixCmdPost autocmd may free the quickfix list. Check the list
// is still valid.
- if (!qflist_valid(wp, save_qfid)
- || 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 (!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);
+ if ((args.flags & VGR_NOJUMP) == 0) {
+ vgr_jump_to_match(qi, eap->forceit, &redraw_for_dummy, first_match_buf, target_dir);
}
} else {
- semsg(_(e_nomatch2), s);
+ semsg(_(e_nomatch2), args.spat);
}
decr_quickfix_busy();
@@ -5520,11 +5633,9 @@ void ex_vimgrep(exarg_T *eap)
}
theend:
- xfree(title);
- xfree(dirname_now);
- xfree(dirname_start);
+ xfree(args.qf_title);
xfree(target_dir);
- vim_regfree(regmatch.regprog);
+ vim_regfree(args.regmatch.regprog);
}
// Restore current working directory to "dirname_start" if they differ, taking
@@ -5656,7 +5767,7 @@ static void wipe_dummy_buffer(buf_T *buf, char_u *dirname_start)
if (firstwin->w_next != NULL) {
for (win_T *wp = firstwin; wp != NULL; wp = wp->w_next) {
if (wp->w_buffer == buf) {
- if (win_close(wp, false) == OK) {
+ if (win_close(wp, false, false) == OK) {
did_one = true;
}
break;
diff --git a/src/nvim/quickfix.h b/src/nvim/quickfix.h
index f5178e332a..0da43e436c 100644
--- a/src/nvim/quickfix.h
+++ b/src/nvim/quickfix.h
@@ -7,6 +7,7 @@
// flags for skip_vimgrep_pat()
#define VGR_GLOBAL 1
#define VGR_NOJUMP 2
+#define VGR_FUZZY 4
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "quickfix.h.generated.h"
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index 45e580dbee..6a6c915094 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -2901,18 +2901,14 @@ static int peekchr(void)
{
int c = regparse[1];
- if (c == NUL)
- curchr = '\\'; /* trailing '\' */
- else if (
- c <= '~' && META_flags[c]
- ) {
- /*
- * META contains everything that may be magic sometimes,
- * except ^ and $ ("\^" and "\$" are only magic after
- * "\V"). We now fetch the next character and toggle its
- * magicness. Therefore, \ is so meta-magic that it is
- * not in META.
- */
+ if (c == NUL) {
+ curchr = '\\'; // trailing '\'
+ } else if (c <= '~' && META_flags[c]) {
+ // META contains everything that may be magic sometimes,
+ // except ^ and $ ("\^" and "\$" are only magic after
+ // "\V"). We now fetch the next character and toggle its
+ // magicness. Therefore, \ is so meta-magic that it is
+ // not in META.
curchr = -1;
prev_at_start = at_start;
at_start = false; // be able to say "/\*ptr"
@@ -3232,7 +3228,7 @@ typedef struct {
// The current match-position is remembered with these variables:
linenr_T lnum; ///< line number, relative to first line
char_u *line; ///< start of current line
- char_u *input; ///< current input, points into "regline"
+ char_u *input; ///< current input, points into "line"
int need_clear_subexpr; ///< subexpressions still need to be cleared
int need_clear_zsubexpr; ///< extmatch subexpressions still need to be
@@ -6538,11 +6534,16 @@ char_u *regtilde(char_u *source, int magic)
}
}
- xfree(reg_prev_sub);
- if (newsub != source) /* newsub was allocated, just keep it */
- reg_prev_sub = newsub;
- else /* no ~ found, need to save newsub */
- reg_prev_sub = vim_strsave(newsub);
+ // Only change reg_prev_sub when not previewing.
+ if (!(State & CMDPREVIEW)) {
+ xfree(reg_prev_sub);
+ if (newsub != source) { // newsub was allocated, just keep it
+ reg_prev_sub = newsub;
+ } else { // no ~ found, need to save newsub
+ reg_prev_sub = vim_strsave(newsub);
+ }
+ }
+
return newsub;
}
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
index cafffc0319..133858f113 100644
--- a/src/nvim/regexp_nfa.c
+++ b/src/nvim/regexp_nfa.c
@@ -6071,8 +6071,15 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
case NFA_MARK_GT:
case NFA_MARK_LT:
{
+ size_t col = rex.input - rex.line;
pos_T *pos = getmark_buf(rex.reg_buf, t->state->val, false);
+ // Line may have been freed, get it again.
+ if (REG_MULTI) {
+ rex.line = reg_getline(rex.lnum);
+ rex.input = rex.line + col;
+ }
+
// Compare the mark position to the match position, if the mark
// exists and mark is set in reg_buf.
if (pos != NULL && pos->lnum > 0) {
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index d4688f7782..15fb6901cc 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -794,7 +794,7 @@ static void win_update(win_T *wp, Providers *providers)
// If we can compute a change in the automatic sizing of the sign column
// under 'signcolumn=auto:X' and signs currently placed in the buffer, better
// figuring it out here so we can redraw the entire screen for it.
- buf_signcols(buf);
+ win_signcol_count(wp);
type = wp->w_redr_type;
@@ -1215,19 +1215,40 @@ static void win_update(win_T *wp, Providers *providers)
*/
if (VIsual_mode == Ctrl_V) {
colnr_T fromc, toc;
- int save_ve_flags = ve_flags;
+ unsigned int save_ve_flags = curwin->w_ve_flags;
if (curwin->w_p_lbr) {
- ve_flags = VE_ALL;
+ curwin->w_ve_flags = VE_ALL;
}
getvcols(wp, &VIsual, &curwin->w_cursor, &fromc, &toc);
- ve_flags = save_ve_flags;
toc++;
+ curwin->w_ve_flags = save_ve_flags;
// Highlight to the end of the line, unless 'virtualedit' has
// "block".
- if (curwin->w_curswant == MAXCOL && !(ve_flags & VE_BLOCK)) {
- toc = MAXCOL;
+ if (curwin->w_curswant == MAXCOL) {
+ if (get_ve_flags() & VE_BLOCK) {
+ pos_T pos;
+ int cursor_above = curwin->w_cursor.lnum < VIsual.lnum;
+
+ // Need to find the longest line.
+ toc = 0;
+ pos.coladd = 0;
+ for (pos.lnum = curwin->w_cursor.lnum;
+ cursor_above ? pos.lnum <= VIsual.lnum : pos.lnum >= VIsual.lnum;
+ pos.lnum += cursor_above ? 1 : -1) {
+ colnr_T t;
+
+ pos.col = STRLEN(ml_get_buf(wp->w_buffer, pos.lnum, false));
+ getvvcol(wp, &pos, NULL, NULL, &t);
+ if (toc < t) {
+ toc = t;
+ }
+ }
+ toc++;
+ } else {
+ toc = MAXCOL;
+ }
}
if (fromc != wp->w_old_cursor_fcol
@@ -1960,7 +1981,7 @@ static size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_
level = foldinfo.fi_level;
// If the column is too narrow, we start at the lowest level that
- // fits and use numbers to indicated the depth.
+ // fits and use numbers to indicate the depth.
first_level = level - fdc - closed + 1;
if (first_level < 1) {
first_level = 1;
@@ -3720,10 +3741,13 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
tab_len += n_extra - tab_len;
}
- // if n_extra > 0, it gives the number of chars
+ // If n_extra > 0, it gives the number of chars
// to use for a tab, else we need to calculate the width
- // for a tab
+ // for a tab.
int len = (tab_len * utf_char2len(wp->w_p_lcs_chars.tab2));
+ if (wp->w_p_lcs_chars.tab3) {
+ len += utf_char2len(wp->w_p_lcs_chars.tab3);
+ }
if (n_extra > 0) {
len += n_extra - tab_len;
}
@@ -3740,13 +3764,11 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
}
int lcs = wp->w_p_lcs_chars.tab2;
- // if tab3 is given, need to change the char
- // for tab
+ // if tab3 is given, use it for the last char
if (wp->w_p_lcs_chars.tab3 && i == tab_len - 1) {
lcs = wp->w_p_lcs_chars.tab3;
}
- utf_char2bytes(lcs, p);
- p += utf_char2len(lcs);
+ p += utf_char2bytes(lcs, p);
n_extra += utf_char2len(lcs) - (saved_nextra > 0 ? 1 : 0);
}
p_extra = p_extra_free;
@@ -4130,7 +4152,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
if (((wp->w_p_cuc
&& (int)wp->w_virtcol >= VCOL_HLC - eol_hl_off
&& (int)wp->w_virtcol <
- grid->Columns * (row - startrow + 1) + v
+ (long)grid->Columns * (row - startrow + 1) + v
&& lnum != wp->w_cursor.lnum)
|| draw_color_col || line_attr_lowprio || line_attr
|| diff_hlf != (hlf_T)0 || has_virttext)) {
@@ -4232,6 +4254,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
// Show "extends" character from 'listchars' if beyond the line end and
// 'list' is set.
if (wp->w_p_lcs_chars.ext != NUL
+ && draw_state == WL_LINE
&& wp->w_p_list
&& !wp->w_p_wrap
&& filler_todo <= 0
@@ -4427,7 +4450,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
*/
if ((wp->w_p_rl ? (col < 0) : (col >= grid->Columns))
&& foldinfo.fi_lines == 0
- && (*ptr != NUL
+ && (draw_state != WL_LINE
+ || *ptr != NUL
|| filler_todo > 0
|| (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL
&& p_extra != at_end_str)
@@ -6762,7 +6786,7 @@ void grid_clear_line(ScreenGrid *grid, unsigned off, int width, bool valid)
void grid_invalidate(ScreenGrid *grid)
{
- (void)memset(grid->attrs, -1, grid->Rows * grid->Columns * sizeof(sattr_T));
+ (void)memset(grid->attrs, -1, sizeof(sattr_T) * grid->Rows * grid->Columns);
}
bool grid_invalid_row(ScreenGrid *grid, int row)
@@ -6891,8 +6915,6 @@ void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col,
if (!grid->throttled) {
ui_call_grid_scroll(grid->handle, row, end, col, col+width, -line_count, 0);
}
-
- return;
}
/// delete lines on the screen and move lines up.
@@ -6943,8 +6965,6 @@ void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col,
if (!grid->throttled) {
ui_call_grid_scroll(grid->handle, row, end, col, col+width, line_count, 0);
}
-
- return;
}
@@ -7326,7 +7346,7 @@ void draw_tabline(void)
if (room > 0) {
// Get buffer name in NameBuff[]
get_trans_bufname(cwp->w_buffer);
- (void)shorten_dir(NameBuff);
+ shorten_dir(NameBuff);
len = vim_strsize(NameBuff);
p = NameBuff;
while (len > room) {
diff --git a/src/nvim/search.c b/src/nvim/search.c
index 906c9a6f47..682fa417a9 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -26,6 +26,7 @@
#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/indent.h"
+#include "nvim/indent_c.h"
#include "nvim/main.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
@@ -2313,12 +2314,9 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel)
return (pos_T *)NULL; // never found it
}
-/*
- * Check if line[] contains a / / comment.
- * Return MAXCOL if not, otherwise return the column.
- * TODO: skip strings.
- */
-static int check_linecomment(const char_u *line)
+/// Check if line[] contains a / / comment.
+/// @returns MAXCOL if not, otherwise return the column.
+int check_linecomment(const char_u *line)
{
const char_u *p = line; // scan from start
// skip Lispish one-line comments
@@ -2338,7 +2336,8 @@ static int check_linecomment(const char_u *line)
in_str = true;
}
} else if (!in_str && ((p - line) < 2
- || (*(p - 1) != '\\' && *(p - 2) != '#'))) {
+ || (*(p - 1) != '\\' && *(p - 2) != '#'))
+ && !is_pos_in_string(line, (colnr_T)(p - line))) {
break; // found!
}
p++;
@@ -2348,9 +2347,11 @@ static int check_linecomment(const char_u *line)
}
} else {
while ((p = vim_strchr(p, '/')) != NULL) {
- // accept a double /, unless it's preceded with * and followed by *,
- // because * / / * is an end and start of a C comment
- if (p[1] == '/' && (p == line || p[-1] != '*' || p[2] != '*')) {
+ // Accept a double /, unless it's preceded with * and followed by *,
+ // because * / / * is an end and start of a C comment.
+ // Only accept the position if it is not inside a string.
+ if (p[1] == '/' && (p == line || p[-1] != '*' || p[2] != '*')
+ && !is_pos_in_string(line, (colnr_T)(p - line))) {
break;
}
++p;
@@ -4763,6 +4764,536 @@ the_end:
restore_last_search_pattern();
}
+/// Fuzzy string matching
+///
+/// Ported from the lib_fts library authored by Forrest Smith.
+/// https://github.com/forrestthewoods/lib_fts/tree/master/code
+///
+/// The following blog describes the fuzzy matching algorithm:
+/// https://www.forrestthewoods.com/blog/reverse_engineering_sublime_texts_fuzzy_match/
+///
+/// Each matching string is assigned a score. The following factors are checked:
+/// - Matched letter
+/// - Unmatched letter
+/// - Consecutively matched letters
+/// - Proximity to start
+/// - Letter following a separator (space, underscore)
+/// - Uppercase letter following lowercase (aka CamelCase)
+///
+/// Matched letters are good. Unmatched letters are bad. Matching near the start
+/// is good. Matching the first letter in the middle of a phrase is good.
+/// Matching the uppercase letters in camel case entries is good.
+///
+/// The score assigned for each factor is explained below.
+/// File paths are different from file names. File extensions may be ignorable.
+/// Single words care about consecutive matches but not separators or camel
+/// case.
+/// Score starts at 100
+/// Matched letter: +0 points
+/// Unmatched letter: -1 point
+/// Consecutive match bonus: +15 points
+/// First letter bonus: +15 points
+/// Separator bonus: +30 points
+/// Camel case bonus: +30 points
+/// Unmatched leading letter: -5 points (max: -15)
+///
+/// There is some nuance to this. Scores don’t have an intrinsic meaning. The
+/// score range isn’t 0 to 100. It’s roughly [50, 150]. Longer words have a
+/// lower minimum score due to unmatched letter penalty. Longer search patterns
+/// have a higher maximum score due to match bonuses.
+///
+/// Separator and camel case bonus is worth a LOT. Consecutive matches are worth
+/// quite a bit.
+///
+/// There is a penalty if you DON’T match the first three letters. Which
+/// effectively rewards matching near the start. However there’s no difference
+/// in matching between the middle and end.
+///
+/// There is not an explicit bonus for an exact match. Unmatched letters receive
+/// a penalty. So shorter strings and closer matches are worth more.
+typedef struct {
+ int idx; ///< used for stable sort
+ listitem_T *item;
+ int score;
+ list_T *lmatchpos;
+} fuzzyItem_T;
+
+/// bonus for adjacent matches; this is higher than SEPARATOR_BONUS so that
+/// matching a whole word is preferred.
+#define SEQUENTIAL_BONUS 40
+/// bonus if match occurs after a path separator
+#define PATH_SEPARATOR_BONUS 30
+/// bonus if match occurs after a word separator
+#define WORD_SEPARATOR_BONUS 25
+/// bonus if match is uppercase and prev is lower
+#define CAMEL_BONUS 30
+/// bonus if the first letter is matched
+#define FIRST_LETTER_BONUS 15
+/// penalty applied for every letter in str before the first match
+#define LEADING_LETTER_PENALTY -5
+/// maximum penalty for leading letters
+#define MAX_LEADING_LETTER_PENALTY -15
+/// penalty for every letter that doesn't match
+#define UNMATCHED_LETTER_PENALTY -1
+/// penalty for gap in matching positions (-2 * k)
+#define GAP_PENALTY -2
+/// Score for a string that doesn't fuzzy match the pattern
+#define SCORE_NONE -9999
+
+#define FUZZY_MATCH_RECURSION_LIMIT 10
+
+/// Compute a score for a fuzzy matched string. The matching character locations
+/// are in 'matches'.
+static int fuzzy_match_compute_score(const char_u *const str, const int strSz,
+ const uint32_t *const matches, const int numMatches)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ assert(numMatches > 0); // suppress clang "result of operation is garbage"
+ // Initialize score
+ int score = 100;
+
+ // Apply leading letter penalty
+ int penalty = LEADING_LETTER_PENALTY * matches[0];
+ if (penalty < MAX_LEADING_LETTER_PENALTY) {
+ penalty = MAX_LEADING_LETTER_PENALTY;
+ }
+ score += penalty;
+
+ // Apply unmatched penalty
+ const int unmatched = strSz - numMatches;
+ score += UNMATCHED_LETTER_PENALTY * unmatched;
+
+ // Apply ordering bonuses
+ for (int i = 0; i < numMatches; i++) {
+ const uint32_t currIdx = matches[i];
+
+ if (i > 0) {
+ const uint32_t prevIdx = matches[i - 1];
+
+ // Sequential
+ if (currIdx == prevIdx + 1) {
+ score += SEQUENTIAL_BONUS;
+ } else {
+ score += GAP_PENALTY * (currIdx - prevIdx);
+ }
+ }
+
+ // Check for bonuses based on neighbor character value
+ if (currIdx > 0) {
+ // Camel case
+ const char_u *p = str;
+ int neighbor;
+
+ for (uint32_t sidx = 0; sidx < currIdx; sidx++) {
+ neighbor = utf_ptr2char(p);
+ MB_PTR_ADV(p);
+ }
+ const int curr = utf_ptr2char(p);
+
+ if (mb_islower(neighbor) && mb_isupper(curr)) {
+ score += CAMEL_BONUS;
+ }
+
+ // Bonus if the match follows a separator character
+ if (neighbor == '/' || neighbor == '\\') {
+ score += PATH_SEPARATOR_BONUS;
+ } else if (neighbor == ' ' || neighbor == '_') {
+ score += WORD_SEPARATOR_BONUS;
+ }
+ } else {
+ // First letter
+ score += FIRST_LETTER_BONUS;
+ }
+ }
+ return score;
+}
+
+/// Perform a recursive search for fuzzy matching 'fuzpat' in 'str'.
+/// @return the number of matching characters.
+static int fuzzy_match_recursive(const char_u *fuzpat, const char_u *str, uint32_t strIdx,
+ int *const outScore, const char_u *const strBegin,
+ const int strLen, const uint32_t *const srcMatches,
+ uint32_t *const matches, const int maxMatches, int nextMatch,
+ int *const recursionCount)
+ FUNC_ATTR_NONNULL_ARG(1, 2, 4, 5, 8, 11) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ // Recursion params
+ bool recursiveMatch = false;
+ uint32_t bestRecursiveMatches[MAX_FUZZY_MATCHES];
+ int bestRecursiveScore = 0;
+
+ // Count recursions
+ (*recursionCount)++;
+ if (*recursionCount >= FUZZY_MATCH_RECURSION_LIMIT) {
+ return 0;
+ }
+
+ // Detect end of strings
+ if (*fuzpat == NUL || *str == NUL) {
+ return 0;
+ }
+
+ // Loop through fuzpat and str looking for a match
+ bool first_match = true;
+ while (*fuzpat != NUL && *str != NUL) {
+ const int c1 = utf_ptr2char(fuzpat);
+ const int c2 = utf_ptr2char(str);
+
+ // Found match
+ if (mb_tolower(c1) == mb_tolower(c2)) {
+ // Supplied matches buffer was too short
+ if (nextMatch >= maxMatches) {
+ return 0;
+ }
+
+ // "Copy-on-Write" srcMatches into matches
+ if (first_match && srcMatches != NULL) {
+ memcpy(matches, srcMatches, nextMatch * sizeof(srcMatches[0]));
+ first_match = false;
+ }
+
+ // Recursive call that "skips" this match
+ uint32_t recursiveMatches[MAX_FUZZY_MATCHES];
+ int recursiveScore = 0;
+ const char_u *const next_char = str + utfc_ptr2len(str);
+ if (fuzzy_match_recursive(fuzpat, next_char, strIdx + 1, &recursiveScore, strBegin, strLen,
+ matches, recursiveMatches,
+ sizeof(recursiveMatches) / sizeof(recursiveMatches[0]), nextMatch,
+ recursionCount)) {
+ // Pick best recursive score
+ if (!recursiveMatch || recursiveScore > bestRecursiveScore) {
+ memcpy(bestRecursiveMatches, recursiveMatches,
+ MAX_FUZZY_MATCHES * sizeof(recursiveMatches[0]));
+ bestRecursiveScore = recursiveScore;
+ }
+ recursiveMatch = true;
+ }
+
+ // Advance
+ matches[nextMatch++] = strIdx;
+ MB_PTR_ADV(fuzpat);
+ }
+ MB_PTR_ADV(str);
+ strIdx++;
+ }
+
+ // Determine if full fuzpat was matched
+ const bool matched = *fuzpat == NUL;
+
+ // Calculate score
+ if (matched) {
+ *outScore = fuzzy_match_compute_score(strBegin, strLen, matches, nextMatch);
+ }
+
+ // Return best result
+ if (recursiveMatch && (!matched || bestRecursiveScore > *outScore)) {
+ // Recursive score is better than "this"
+ memcpy(matches, bestRecursiveMatches, maxMatches * sizeof(matches[0]));
+ *outScore = bestRecursiveScore;
+ return nextMatch;
+ } else if (matched) {
+ return nextMatch; // "this" score is better than recursive
+ }
+
+ return 0; // no match
+}
+
+/// fuzzy_match()
+///
+/// Performs exhaustive search via recursion to find all possible matches and
+/// match with highest score.
+/// Scores values have no intrinsic meaning. Possible score range is not
+/// normalized and varies with pattern.
+/// Recursion is limited internally (default=10) to prevent degenerate cases
+/// (pat_arg="aaaaaa" str="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").
+/// Uses char_u for match indices. Therefore patterns are limited to
+/// MAX_FUZZY_MATCHES characters.
+///
+/// @return true if 'pat_arg' matches 'str'. Also returns the match score in
+/// 'outScore' and the matching character positions in 'matches'.
+bool fuzzy_match(char_u *const str, const char_u *const pat_arg, const bool matchseq,
+ int *const outScore, uint32_t *const matches, const int maxMatches)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ const int len = mb_charlen(str);
+ bool complete = false;
+ int numMatches = 0;
+
+ *outScore = 0;
+
+ char_u *const save_pat = vim_strsave(pat_arg);
+ char_u *pat = save_pat;
+ char_u *p = pat;
+
+ // Try matching each word in 'pat_arg' in 'str'
+ while (true) {
+ if (matchseq) {
+ complete = true;
+ } else {
+ // Extract one word from the pattern (separated by space)
+ p = skipwhite(p);
+ if (*p == NUL) {
+ break;
+ }
+ pat = p;
+ while (*p != NUL && !ascii_iswhite(utf_ptr2char(p))) {
+ MB_PTR_ADV(p);
+ }
+ if (*p == NUL) { // processed all the words
+ complete = true;
+ }
+ *p = NUL;
+ }
+
+ int score = 0;
+ int recursionCount = 0;
+ const int matchCount
+ = fuzzy_match_recursive(pat, str, 0, &score, str, len, NULL, matches + numMatches,
+ maxMatches - numMatches, 0, &recursionCount);
+ if (matchCount == 0) {
+ numMatches = 0;
+ break;
+ }
+
+ // Accumulate the match score and the number of matches
+ *outScore += score;
+ numMatches += matchCount;
+
+ if (complete) {
+ break;
+ }
+
+ // try matching the next word
+ p++;
+ }
+
+ xfree(save_pat);
+ return numMatches != 0;
+}
+
+/// Sort the fuzzy matches in the descending order of the match score.
+/// For items with same score, retain the order using the index (stable sort)
+static int fuzzy_match_item_compare(const void *const s1, const void *const s2)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ const int v1 = ((const fuzzyItem_T *)s1)->score;
+ const int v2 = ((const fuzzyItem_T *)s2)->score;
+ const int idx1 = ((const fuzzyItem_T *)s1)->idx;
+ const int idx2 = ((const fuzzyItem_T *)s2)->idx;
+
+ return v1 == v2 ? (idx1 - idx2) : v1 > v2 ? -1 : 1;
+}
+
+/// Fuzzy search the string 'str' in a list of 'items' and return the matching
+/// strings in 'fmatchlist'.
+/// If 'matchseq' is true, then for multi-word search strings, match all the
+/// words in sequence.
+/// If 'items' is a list of strings, then search for 'str' in the list.
+/// If 'items' is a list of dicts, then either use 'key' to lookup the string
+/// for each item or use 'item_cb' Funcref function to get the string.
+/// If 'retmatchpos' is true, then return a list of positions where 'str'
+/// matches for each item.
+static void fuzzy_match_in_list(list_T *const items, char_u *const str, const bool matchseq,
+ const char_u *const key, Callback *const item_cb,
+ const bool retmatchpos, list_T *const fmatchlist)
+ FUNC_ATTR_NONNULL_ARG(2, 5, 7)
+{
+ const long len = tv_list_len(items);
+ if (len == 0) {
+ return;
+ }
+
+ fuzzyItem_T *const ptrs = xcalloc(len, sizeof(fuzzyItem_T));
+ long i = 0;
+ bool found_match = false;
+ uint32_t matches[MAX_FUZZY_MATCHES];
+
+ // For all the string items in items, get the fuzzy matching score
+ TV_LIST_ITER(items, li, {
+ ptrs[i].idx = i;
+ ptrs[i].item = li;
+ ptrs[i].score = SCORE_NONE;
+ char_u *itemstr = NULL;
+ typval_T rettv;
+ rettv.v_type = VAR_UNKNOWN;
+ const typval_T *const tv = TV_LIST_ITEM_TV(li);
+ if (tv->v_type == VAR_STRING) { // list of strings
+ itemstr = tv->vval.v_string;
+ } else if (tv->v_type == VAR_DICT && (key != NULL || item_cb->type != kCallbackNone)) {
+ // For a dict, either use the specified key to lookup the string or
+ // use the specified callback function to get the string.
+ if (key != NULL) {
+ itemstr = (char_u *)tv_dict_get_string(tv->vval.v_dict, (const char *)key, false);
+ } else {
+ typval_T argv[2];
+
+ // Invoke the supplied callback (if any) to get the dict item
+ tv->vval.v_dict->dv_refcount++;
+ argv[0].v_type = VAR_DICT;
+ argv[0].vval.v_dict = tv->vval.v_dict;
+ argv[1].v_type = VAR_UNKNOWN;
+ if (callback_call(item_cb, 1, argv, &rettv)) {
+ if (rettv.v_type == VAR_STRING) {
+ itemstr = rettv.vval.v_string;
+ }
+ }
+ tv_dict_unref(tv->vval.v_dict);
+ }
+ }
+
+ int score;
+ if (itemstr != NULL && fuzzy_match(itemstr, str, matchseq, &score, matches,
+ sizeof(matches) / sizeof(matches[0]))) {
+ // Copy the list of matching positions in itemstr to a list, if
+ // 'retmatchpos' is set.
+ if (retmatchpos) {
+ ptrs[i].lmatchpos = tv_list_alloc(kListLenMayKnow);
+ int j = 0;
+ const char_u *p = str;
+ while (*p != NUL) {
+ if (!ascii_iswhite(utf_ptr2char(p))) {
+ tv_list_append_number(ptrs[i].lmatchpos, matches[j]);
+ j++;
+ }
+ MB_PTR_ADV(p);
+ }
+ }
+ ptrs[i].score = score;
+ found_match = true;
+ }
+ i++;
+ tv_clear(&rettv);
+ });
+
+ if (found_match) {
+ // Sort the list by the descending order of the match score
+ qsort(ptrs, len, sizeof(fuzzyItem_T), fuzzy_match_item_compare);
+
+ // For matchfuzzy(), return a list of matched strings.
+ // ['str1', 'str2', 'str3']
+ // For matchfuzzypos(), return a list with three items.
+ // The first item is a list of matched strings. The second item
+ // is a list of lists where each list item is a list of matched
+ // character positions. The third item is a list of matching scores.
+ // [['str1', 'str2', 'str3'], [[1, 3], [1, 3], [1, 3]]]
+ list_T *l;
+ if (retmatchpos) {
+ const listitem_T *const li = tv_list_find(fmatchlist, 0);
+ assert(li != NULL && TV_LIST_ITEM_TV(li)->vval.v_list != NULL);
+ l = TV_LIST_ITEM_TV(li)->vval.v_list;
+ } else {
+ l = fmatchlist;
+ }
+
+ // Copy the matching strings with a valid score to the return list
+ for (i = 0; i < len; i++) {
+ if (ptrs[i].score == SCORE_NONE) {
+ break;
+ }
+ tv_list_append_tv(l, TV_LIST_ITEM_TV(ptrs[i].item));
+ }
+
+ // next copy the list of matching positions
+ if (retmatchpos) {
+ const listitem_T *li = tv_list_find(fmatchlist, -2);
+ assert(li != NULL && TV_LIST_ITEM_TV(li)->vval.v_list != NULL);
+ l = TV_LIST_ITEM_TV(li)->vval.v_list;
+ for (i = 0; i < len; i++) {
+ if (ptrs[i].score == SCORE_NONE) {
+ break;
+ }
+ tv_list_append_list(l, ptrs[i].lmatchpos);
+ }
+
+ // copy the matching scores
+ li = tv_list_find(fmatchlist, -1);
+ assert(li != NULL && TV_LIST_ITEM_TV(li)->vval.v_list != NULL);
+ l = TV_LIST_ITEM_TV(li)->vval.v_list;
+ for (i = 0; i < len; i++) {
+ if (ptrs[i].score == SCORE_NONE) {
+ break;
+ }
+ tv_list_append_number(l, ptrs[i].score);
+ }
+ }
+ }
+ xfree(ptrs);
+}
+
+/// Do fuzzy matching. Returns the list of matched strings in 'rettv'.
+/// If 'retmatchpos' is true, also returns the matching character positions.
+static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv,
+ const bool retmatchpos)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // validate and get the arguments
+ if (argvars[0].v_type != VAR_LIST || argvars[0].vval.v_list == NULL) {
+ semsg(_(e_listarg), retmatchpos ? "matchfuzzypos()" : "matchfuzzy()");
+ return;
+ }
+ if (argvars[1].v_type != VAR_STRING || argvars[1].vval.v_string == NULL) {
+ semsg(_(e_invarg2), tv_get_string(&argvars[1]));
+ return;
+ }
+
+ Callback cb = CALLBACK_NONE;
+ const char_u *key = NULL;
+ bool matchseq = false;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL) {
+ emsg(_(e_dictreq));
+ return;
+ }
+
+ // To search a dict, either a callback function or a key can be
+ // specified.
+ dict_T *const d = argvars[2].vval.v_dict;
+ const dictitem_T *const di = tv_dict_find(d, "key", -1);
+ if (di != NULL) {
+ if (di->di_tv.v_type != VAR_STRING || di->di_tv.vval.v_string == NULL
+ || *di->di_tv.vval.v_string == NUL) {
+ semsg(_(e_invarg2), tv_get_string(&di->di_tv));
+ return;
+ }
+ key = (const char_u *)tv_get_string(&di->di_tv);
+ } else if (!tv_dict_get_callback(d, "text_cb", -1, &cb)) {
+ semsg(_(e_invargval), "text_cb");
+ return;
+ }
+ if (tv_dict_find(d, "matchseq", -1) != NULL) {
+ matchseq = true;
+ }
+ }
+
+ // get the fuzzy matches
+ tv_list_alloc_ret(rettv, retmatchpos ? 3 : kListLenUnknown);
+ if (retmatchpos) {
+ // For matchfuzzypos(), a list with three items are returned. First
+ // item is a list of matching strings, the second item is a list of
+ // lists with matching positions within each string and the third item
+ // is the list of scores of the matches.
+ tv_list_append_list(rettv->vval.v_list, tv_list_alloc(kListLenUnknown));
+ tv_list_append_list(rettv->vval.v_list, tv_list_alloc(kListLenUnknown));
+ tv_list_append_list(rettv->vval.v_list, tv_list_alloc(kListLenUnknown));
+ }
+
+ fuzzy_match_in_list(argvars[0].vval.v_list, (char_u *)tv_get_string(&argvars[1]), matchseq, key,
+ &cb, retmatchpos, rettv->vval.v_list);
+ callback_free(&cb);
+}
+
+/// "matchfuzzy()" function
+void f_matchfuzzy(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ do_fuzzymatch(argvars, rettv, false);
+}
+
+/// "matchfuzzypos()" function
+void f_matchfuzzypos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ do_fuzzymatch(argvars, rettv, true);
+}
+
/// Find identifiers or defines in included files.
/// If p_ic && (compl_cont_status & CONT_SOL) then ptr must be in lowercase.
///
diff --git a/src/nvim/search.h b/src/nvim/search.h
index 15b8d41f39..53059cc1ea 100644
--- a/src/nvim/search.h
+++ b/src/nvim/search.h
@@ -55,6 +55,9 @@
#define SEARCH_STAT_DEF_MAX_COUNT 99
#define SEARCH_STAT_BUF_LEN 12
+/// Maximum number of characters that can be fuzzy matched
+#define MAX_FUZZY_MATCHES 256
+
/// Structure containing offset definition for the last search pattern
///
/// @note Only offset for the last search pattern is used, not for the last
diff --git a/src/nvim/sign.c b/src/nvim/sign.c
index a308df07d1..8b41781c98 100644
--- a/src/nvim/sign.c
+++ b/src/nvim/sign.c
@@ -1501,28 +1501,28 @@ static void sign_getinfo(sign_T *sp, dict_T *retdict)
if (p == NULL) {
p = "NONE";
}
- tv_dict_add_str(retdict, S_LEN("linehl"), (char *)p);
+ tv_dict_add_str(retdict, S_LEN("linehl"), p);
}
if (sp->sn_text_hl > 0) {
p = get_highlight_name_ext(NULL, sp->sn_text_hl - 1, false);
if (p == NULL) {
p = "NONE";
}
- tv_dict_add_str(retdict, S_LEN("texthl"), (char *)p);
+ tv_dict_add_str(retdict, S_LEN("texthl"), p);
}
if (sp->sn_cul_hl > 0) {
p = get_highlight_name_ext(NULL, sp->sn_cul_hl - 1, false);
if (p == NULL) {
p = "NONE";
}
- tv_dict_add_str(retdict, S_LEN("culhl"), (char *)p);
+ tv_dict_add_str(retdict, S_LEN("culhl"), p);
}
if (sp->sn_num_hl > 0) {
p = get_highlight_name_ext(NULL, sp->sn_num_hl - 1, false);
if (p == NULL) {
p = "NONE";
}
- tv_dict_add_str(retdict, S_LEN("numhl"), (char *)p);
+ tv_dict_add_str(retdict, S_LEN("numhl"), p);
}
}
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index 9429a06e92..1296d410f6 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -4082,7 +4082,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
// char, e.g., "thes," -> "these".
p = fword + sp->ts_fidx;
MB_PTR_BACK(fword, p);
- if (!spell_iswordp(p, curwin)) {
+ if (!spell_iswordp(p, curwin) && *preword != NUL) {
p = preword + STRLEN(preword);
MB_PTR_BACK(preword, p);
if (spell_iswordp(p, curwin)) {
diff --git a/src/nvim/state.c b/src/nvim/state.c
index 68bc76660d..f9a3aaab7f 100644
--- a/src/nvim/state.c
+++ b/src/nvim/state.c
@@ -12,6 +12,7 @@
#include "nvim/lib/kvec.h"
#include "nvim/log.h"
#include "nvim/main.h"
+#include "nvim/option.h"
#include "nvim/option_defs.h"
#include "nvim/os/input.h"
#include "nvim/state.h"
@@ -38,10 +39,16 @@ void state_enter(VimState *s)
int key;
getkey:
- if (char_avail() || using_script() || input_available()) {
- // Don't block for events if there's a character already available for
- // processing. Characters can come from mappings, scripts and other
- // sources, so this scenario is very common.
+ // Expand mappings first by calling vpeekc() directly.
+ // - If vpeekc() returns non-NUL, there is a character already available for processing, so
+ // don't block for events. vgetc() may still block, in case of an incomplete UTF-8 sequence.
+ // - If vpeekc() returns NUL, vgetc() will block, and there are three cases:
+ // - There is no input available.
+ // - All of available input maps to an empty string.
+ // - There is an incomplete mapping.
+ // A blocking wait for a character should only be done in the third case, which is the only
+ // case of the three where typebuf.tb_len > 0 after vpeekc() returns NUL.
+ if (vpeekc() != NUL || typebuf.tb_len > 0) {
key = safe_vgetc();
} else if (!multiqueue_empty(main_loop.events)) {
// Event was made available after the last multiqueue_process_events call
@@ -54,9 +61,11 @@ getkey:
// mapping engine.
(void)os_inchar(NULL, 0, -1, 0, main_loop.events);
// If an event was put into the queue, we send K_EVENT directly.
- key = !multiqueue_empty(main_loop.events)
- ? K_EVENT
- : safe_vgetc();
+ if (!multiqueue_empty(main_loop.events)) {
+ key = K_EVENT;
+ } else {
+ goto getkey;
+ }
}
if (key == K_EVENT) {
@@ -107,15 +116,17 @@ void state_handle_k_event(void)
/// Return true if in the current mode we need to use virtual.
bool virtual_active(void)
{
+ unsigned int cur_ve_flags = get_ve_flags();
+
// While an operator is being executed we return "virtual_op", because
// VIsual_active has already been reset, thus we can't check for "block"
// being used.
if (virtual_op != kNone) {
return virtual_op;
}
- return ve_flags == VE_ALL
- || ((ve_flags & VE_BLOCK) && VIsual_active && VIsual_mode == Ctrl_V)
- || ((ve_flags & VE_INSERT) && (State & INSERT));
+ return cur_ve_flags == VE_ALL
+ || ((cur_ve_flags & VE_BLOCK) && VIsual_active && VIsual_mode == Ctrl_V)
+ || ((cur_ve_flags & VE_INSERT) && (State & INSERT));
}
/// VISUAL, SELECTMODE and OP_PENDING State are never set, they are equal to
@@ -180,7 +191,7 @@ char *get_mode(void)
buf[1] = 'x';
}
}
- } else if (State & CMDLINE) {
+ } else if ((State & CMDLINE) || exmode_active) {
buf[0] = 'c';
if (exmode_active) {
buf[1] = 'v';
diff --git a/src/nvim/strings.c b/src/nvim/strings.c
index e2a8108c45..291138ef23 100644
--- a/src/nvim/strings.c
+++ b/src/nvim/strings.c
@@ -1001,22 +1001,20 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
- str_arg);
}
if (fmt_spec == 'S') {
- if (min_field_width != 0) {
- min_field_width += (strlen(str_arg)
- - mb_string2cells((char_u *)str_arg));
- }
- if (precision) {
- char_u *p1;
- size_t i = 0;
-
- for (p1 = (char_u *)str_arg; *p1;
- p1 += utfc_ptr2len(p1)) {
- i += (size_t)utf_ptr2cells(p1);
- if (i > precision) {
- break;
- }
+ char_u *p1;
+ size_t i;
+
+ for (i = 0, p1 = (char_u *)str_arg; *p1; p1 += utfc_ptr2len(p1)) {
+ size_t cell = (size_t)utf_ptr2cells(p1);
+ if (precision_specified && i + cell > precision) {
+ break;
}
- str_arg_l = (size_t)(p1 - (char_u *)str_arg);
+ i += cell;
+ }
+
+ str_arg_l = (size_t)(p1 - (char_u *)str_arg);
+ if (min_field_width != 0) {
+ min_field_width += str_arg_l - i;
}
}
break;
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index a9447165c2..119f6e811f 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -3112,9 +3112,9 @@ static void syn_cmd_conceal(exarg_T *eap, int syncing)
next = skiptowhite(arg);
if (*arg == NUL) {
if (curwin->w_s->b_syn_conceal) {
- msg(_("syntax conceal on"));
+ msg("syntax conceal on");
} else {
- msg(_("syntax conceal off"));
+ msg("syntax conceal off");
}
} else if (STRNICMP(arg, "on", 2) == 0 && next - arg == 2) {
curwin->w_s->b_syn_conceal = true;
@@ -3141,9 +3141,9 @@ static void syn_cmd_case(exarg_T *eap, int syncing)
next = skiptowhite(arg);
if (*arg == NUL) {
if (curwin->w_s->b_syn_ic) {
- msg(_("syntax case ignore"));
+ msg("syntax case ignore");
} else {
- msg(_("syntax case match"));
+ msg("syntax case match");
}
} else if (STRNICMP(arg, "match", 5) == 0 && next - arg == 5) {
curwin->w_s->b_syn_ic = false;
@@ -3168,9 +3168,9 @@ static void syn_cmd_foldlevel(exarg_T *eap, int syncing)
if (*arg == NUL) {
switch (curwin->w_s->b_syn_foldlevel) {
case SYNFLD_START:
- msg(_("syntax foldlevel start")); break;
+ msg("syntax foldlevel start"); break;
case SYNFLD_MINIMUM:
- msg(_("syntax foldlevel minimum")); break;
+ msg("syntax foldlevel minimum"); break;
default:
break;
}
@@ -3209,11 +3209,11 @@ static void syn_cmd_spell(exarg_T *eap, int syncing)
next = skiptowhite(arg);
if (*arg == NUL) {
if (curwin->w_s->b_syn_spell == SYNSPL_TOP) {
- msg(_("syntax spell toplevel"));
+ msg("syntax spell toplevel");
} else if (curwin->w_s->b_syn_spell == SYNSPL_NOTOP) {
- msg(_("syntax spell notoplevel"));
+ msg("syntax spell notoplevel");
} else {
- msg(_("syntax spell default"));
+ msg("syntax spell default");
}
} else if (STRNICMP(arg, "toplevel", 8) == 0 && next - arg == 8) {
curwin->w_s->b_syn_spell = SYNSPL_TOP;
@@ -3245,7 +3245,7 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing)
if (*arg == NUL) {
msg_puts("\n");
if (curwin->w_s->b_syn_isk != empty_option) {
- msg_puts(_("syntax iskeyword "));
+ msg_puts("syntax iskeyword ");
msg_outtrans(curwin->w_s->b_syn_isk);
} else {
msg_outtrans((char_u *)_("syntax iskeyword not set"));
@@ -6714,6 +6714,90 @@ int lookup_color(const int idx, const bool foreground, TriState *const boldp)
return color;
}
+void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id)
+{
+ int idx = id - 1; // Index is ID minus one.
+
+ bool is_default = attrs.rgb_ae_attr & HL_DEFAULT;
+
+ // Return if "default" was used and the group already has settings
+ if (is_default && hl_has_settings(idx, true)) {
+ return;
+ }
+
+ HlGroup *g = &HL_TABLE()[idx];
+
+ if (link_id > 0) {
+ g->sg_cleared = false;
+ g->sg_link = link_id;
+ g->sg_script_ctx = current_sctx;
+ g->sg_script_ctx.sc_lnum += sourcing_lnum;
+ g->sg_set |= SG_LINK;
+ if (is_default) {
+ g->sg_deflink = link_id;
+ g->sg_deflink_sctx = current_sctx;
+ g->sg_deflink_sctx.sc_lnum += sourcing_lnum;
+ }
+ return;
+ }
+
+ g->sg_cleared = false;
+ g->sg_link = 0;
+ g->sg_gui = attrs.rgb_ae_attr;
+
+ g->sg_rgb_fg = attrs.rgb_fg_color;
+ g->sg_rgb_bg = attrs.rgb_bg_color;
+ g->sg_rgb_sp = attrs.rgb_sp_color;
+
+ struct {
+ char **dest; RgbValue val; Object name;
+ } cattrs[] = {
+ { &g->sg_rgb_fg_name, g->sg_rgb_fg, HAS_KEY(dict->fg) ? dict->fg : dict->foreground },
+ { &g->sg_rgb_bg_name, g->sg_rgb_bg, HAS_KEY(dict->bg) ? dict->bg : dict->background },
+ { &g->sg_rgb_sp_name, g->sg_rgb_sp, HAS_KEY(dict->sp) ? dict->sp : dict->special },
+ { NULL, -1, NIL },
+ };
+
+ for (int j = 0; cattrs[j].dest; j++) {
+ if (cattrs[j].val != -1) {
+ xfree(*cattrs[j].dest);
+ if (cattrs[j].name.type == kObjectTypeString && cattrs[j].name.data.string.size) {
+ *cattrs[j].dest = xstrdup(cattrs[j].name.data.string.data);
+ } else {
+ char hex_name[8];
+ snprintf(hex_name, sizeof(hex_name), "#%06x", cattrs[j].val);
+ *cattrs[j].dest = xstrdup(hex_name);
+ }
+ }
+ }
+
+ g->sg_cterm = attrs.cterm_ae_attr;
+ g->sg_cterm_bg = attrs.cterm_bg_color;
+ g->sg_cterm_fg = attrs.cterm_fg_color;
+ g->sg_cterm_bold = g->sg_cterm & HL_BOLD;
+ g->sg_blend = attrs.hl_blend;
+
+ g->sg_script_ctx = current_sctx;
+ g->sg_script_ctx.sc_lnum += sourcing_lnum;
+
+ // 'Normal' is special
+ if (STRCMP(g->sg_name_u, "NORMAL") == 0) {
+ cterm_normal_fg_color = g->sg_cterm_fg;
+ cterm_normal_bg_color = g->sg_cterm_bg;
+ normal_fg = g->sg_rgb_fg;
+ normal_bg = g->sg_rgb_bg;
+ normal_sp = g->sg_rgb_sp;
+ ui_default_colors_set();
+ } else {
+ g->sg_attr = hl_get_syn_attr(0, id, attrs);
+
+ // a cursor style uses this syn_id, make sure its attribute is updated.
+ if (cursor_mode_uses_syn_id(id)) {
+ ui_mode_info_set();
+ }
+ }
+}
+
/// Handle ":highlight" command
///
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index a10a2a0c32..32d72218c8 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -2960,7 +2960,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help)
} else {
RedrawingDisabled--;
if (postponed_split) { // close the window
- win_close(curwin, false);
+ win_close(curwin, false, false);
postponed_split = 0;
}
}
@@ -3408,7 +3408,7 @@ static void tagstack_push_items(win_T *wp, list_T *l)
if ((di = tv_dict_find(itemdict, "from", -1)) == NULL) {
continue;
}
- if (list2fpos(&di->di_tv, &mark, &fnum, NULL) != OK) {
+ if (list2fpos(&di->di_tv, &mark, &fnum, NULL, false) != OK) {
continue;
}
if ((tagname = (char_u *)tv_dict_get_string(itemdict, "tagname", true))
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index 70a5c7aa08..1c26e46a21 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -172,6 +172,11 @@ void terminal_teardown(void)
pmap_init(ptr_t, &invalidated_terminals);
}
+static void term_output_callback(const char *s, size_t len, void *user_data)
+{
+ terminal_send((Terminal *)user_data, (char *)s, len);
+}
+
// public API {{{
Terminal *terminal_open(buf_T *buf, TerminalOptions opts)
@@ -195,6 +200,7 @@ Terminal *terminal_open(buf_T *buf, TerminalOptions opts)
vterm_screen_set_callbacks(rv->vts, &vterm_screen_callbacks, rv);
vterm_screen_set_damage_merge(rv->vts, VTERM_DAMAGE_SCROLL);
vterm_screen_reset(rv->vts, 1);
+ vterm_output_set_callback(rv->vt, term_output_callback, rv);
// force a initial refresh of the screen to ensure the buffer will always
// have as many lines as screen rows when refresh_scrollback is called
rv->invalid_start = 0;
@@ -636,7 +642,6 @@ void terminal_paste(long count, char_u **y_array, size_t y_size)
return;
}
vterm_keyboard_start_paste(curbuf->terminal->vt);
- terminal_flush_output(curbuf->terminal);
size_t buff_len = STRLEN(y_array[0]);
char_u *buff = xmalloc(buff_len);
for (int i = 0; i < count; i++) { // -V756
@@ -667,14 +672,6 @@ void terminal_paste(long count, char_u **y_array, size_t y_size)
}
xfree(buff);
vterm_keyboard_end_paste(curbuf->terminal->vt);
- terminal_flush_output(curbuf->terminal);
-}
-
-void terminal_flush_output(Terminal *term)
-{
- size_t len = vterm_output_read(term->vt, term->textbuf,
- sizeof(term->textbuf));
- terminal_send(term, term->textbuf, len);
}
void terminal_send_key(Terminal *term, int c)
@@ -693,8 +690,6 @@ void terminal_send_key(Terminal *term, int c)
} else {
vterm_keyboard_unichar(term->vt, (uint32_t)c, mod);
}
-
- terminal_flush_output(term);
}
void terminal_receive(Terminal *term, char *data, size_t len)
@@ -1265,9 +1260,6 @@ static bool send_mouse_event(Terminal *term, int c)
}
mouse_action(term, button, row, col - offset, pressed, 0);
- size_t len = vterm_output_read(term->vt, term->textbuf,
- sizeof(term->textbuf));
- terminal_send(term, term->textbuf, len);
return false;
}
@@ -1299,7 +1291,7 @@ static bool send_mouse_event(Terminal *term, int c)
}
end:
- ins_char_typebuf(c);
+ ins_char_typebuf(c, mod_mask);
return true;
}
diff --git a/src/nvim/testdir/check.vim b/src/nvim/testdir/check.vim
index 14bab33a2f..883f036fe1 100644
--- a/src/nvim/testdir/check.vim
+++ b/src/nvim/testdir/check.vim
@@ -12,9 +12,9 @@ endfunc
" Command to check for the absence of a feature.
command -nargs=1 CheckNotFeature call CheckNotFeature(<f-args>)
func CheckNotFeature(name)
- if !has(a:name, 1)
- throw 'Checking for non-existent feature ' .. a:name
- endif
+ " if !has(a:name, 1)
+ " throw 'Checking for non-existent feature ' .. a:name
+ " endif
if has(a:name)
throw 'Skipped: ' .. a:name .. ' feature present'
endif
@@ -63,6 +63,15 @@ func CheckUnix()
endif
endfunc
+" Command to check for not running on a BSD system.
+" TODO: using this checks should not be needed
+command CheckNotBSD call CheckNotBSD()
+func CheckNotBSD()
+ if has('bsd')
+ throw 'Skipped: does not work on BSD'
+ endif
+endfunc
+
" Command to check that making screendumps is supported.
" Caller must source screendump.vim
command CheckScreendump call CheckScreendump()
@@ -104,6 +113,14 @@ func CheckNotGui()
endif
endfunc
+" Command to check that test is not running as root
+command CheckNotRoot call CheckNotRoot()
+func CheckNotRoot()
+ if IsRoot()
+ throw 'Skipped: cannot run test as root'
+ endif
+endfunc
+
" Command to check that the current language is English
command CheckEnglish call CheckEnglish()
func CheckEnglish()
diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim
index f456ff4250..c2809844ac 100644
--- a/src/nvim/testdir/shared.vim
+++ b/src/nvim/testdir/shared.vim
@@ -343,6 +343,15 @@ func RunVimPiped(before, after, arguments, pipecmd)
return 1
endfunc
+func IsRoot()
+ if !has('unix')
+ return v:false
+ elseif $USER == 'root' || system('id -un') =~ '\<root\>'
+ return v:true
+ endif
+ return v:false
+endfunc
+
" Get all messages but drop the maintainer entry.
func GetMessages()
redir => result
diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim
index cc767a9bcf..c0ac4393c4 100644
--- a/src/nvim/testdir/test_alot.vim
+++ b/src/nvim/testdir/test_alot.vim
@@ -27,6 +27,7 @@ source test_join.vim
source test_jumps.vim
source test_fileformat.vim
source test_filetype.vim
+source test_filetype_lua.vim
source test_lambda.vim
source test_menu.vim
source test_messages.vim
diff --git a/src/nvim/testdir/test_alot_utf8.vim b/src/nvim/testdir/test_alot_utf8.vim
index 70f14320a6..77f5ede4c8 100644
--- a/src/nvim/testdir/test_alot_utf8.vim
+++ b/src/nvim/testdir/test_alot_utf8.vim
@@ -6,7 +6,6 @@
source test_charsearch_utf8.vim
source test_expr_utf8.vim
-source test_matchadd_conceal_utf8.vim
source test_mksession_utf8.vim
source test_regexp_utf8.vim
source test_source_utf8.vim
diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim
index 52f243aaea..28c5948142 100644
--- a/src/nvim/testdir/test_assert.vim
+++ b/src/nvim/testdir/test_assert.vim
@@ -1,5 +1,55 @@
" Test that the methods used for testing work.
+func Test_assert_false()
+ call assert_equal(0, assert_false(0))
+ call assert_equal(0, assert_false(v:false))
+ call assert_equal(0, v:false->assert_false())
+
+ call assert_equal(1, assert_false(123))
+ call assert_match("Expected False but got 123", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, 123->assert_false())
+ call assert_match("Expected False but got 123", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_assert_true()
+ call assert_equal(0, assert_true(1))
+ call assert_equal(0, assert_true(123))
+ call assert_equal(0, assert_true(v:true))
+ call assert_equal(0, v:true->assert_true())
+
+ call assert_equal(1, assert_true(0))
+ call assert_match("Expected True but got 0", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, 0->assert_true())
+ call assert_match("Expected True but got 0", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_assert_equal()
+ let s = 'foo'
+ call assert_equal(0, assert_equal('foo', s))
+ let n = 4
+ call assert_equal(0, assert_equal(4, n))
+ let l = [1, 2, 3]
+ call assert_equal(0, assert_equal([1, 2, 3], l))
+ call assert_equal(v:_null_list, v:_null_list)
+ call assert_equal(v:_null_list, [])
+ call assert_equal([], v:_null_list)
+
+ let s = 'foo'
+ call assert_equal(1, assert_equal('bar', s))
+ call assert_match("Expected 'bar' but got 'foo'", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal('XxxxxxxxxxxxxxxxxxxxxxX', 'XyyyyyyyyyyyyyyyyyyyyyyyyyX')
+ call assert_match("Expected 'X\\\\\\[x occurs 21 times]X' but got 'X\\\\\\[y occurs 25 times]X'", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
func Test_assert_equalfile()
call assert_equal(1, assert_equalfile('abcabc', 'xyzxyz'))
call assert_match("E485: Can't read file abcabc", v:errors[0])
@@ -46,17 +96,129 @@ func Test_assert_equalfile()
call delete('Xtwo')
endfunc
+func Test_assert_notequal()
+ let n = 4
+ call assert_equal(0, assert_notequal('foo', n))
+ let s = 'foo'
+ call assert_equal(0, assert_notequal([1, 2, 3], s))
+
+ call assert_equal(1, assert_notequal('foo', s))
+ call assert_match("Expected not equal to 'foo'", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_assert_report()
+ call assert_equal(1, assert_report('something is wrong'))
+ call assert_match('something is wrong', v:errors[0])
+ call remove(v:errors, 0)
+ call assert_equal(1, 'also wrong'->assert_report())
+ call assert_match('also wrong', v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_assert_exception()
+ try
+ nocommand
+ catch
+ call assert_equal(0, assert_exception('E492:'))
+ endtry
+
+ try
+ nocommand
+ catch
+ try
+ " illegal argument, get NULL for error
+ call assert_equal(1, assert_exception([]))
+ catch
+ call assert_equal(0, assert_exception('E730:'))
+ endtry
+ endtry
+endfunc
+
+func Test_wrong_error_type()
+ let save_verrors = v:errors
+ let v:['errors'] = {'foo': 3}
+ call assert_equal('yes', 'no')
+ let verrors = v:errors
+ let v:errors = save_verrors
+ call assert_equal(type([]), type(verrors))
+endfunc
+
+func Test_match()
+ call assert_equal(0, assert_match('^f.*b.*r$', 'foobar'))
+
+ call assert_equal(1, assert_match('bar.*foo', 'foobar'))
+ call assert_match("Pattern 'bar.*foo' does not match 'foobar'", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, assert_match('bar.*foo', 'foobar', 'wrong'))
+ call assert_match('wrong', v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, 'foobar'->assert_match('bar.*foo', 'wrong'))
+ call assert_match('wrong', v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_notmatch()
+ call assert_equal(0, assert_notmatch('foo', 'bar'))
+ call assert_equal(0, assert_notmatch('^foobar$', 'foobars'))
+
+ call assert_equal(1, assert_notmatch('foo', 'foobar'))
+ call assert_match("Pattern 'foo' does match 'foobar'", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, 'foobar'->assert_notmatch('foo'))
+ call assert_match("Pattern 'foo' does match 'foobar'", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_assert_fail_fails()
+ call assert_equal(1, assert_fails('xxx', 'E12345'))
+ call assert_match("Expected 'E12345' but got 'E492:", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, assert_fails('xxx', 'E9876', 'stupid'))
+ call assert_match("stupid: Expected 'E9876' but got 'E492:", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, assert_fails('echo', '', 'echo command'))
+ call assert_match("command did not fail: echo command", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, 'echo'->assert_fails('', 'echo command'))
+ call assert_match("command did not fail: echo command", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
func Test_assert_fails_in_try_block()
try
call assert_equal(0, assert_fails('throw "error"'))
endtry
endfunc
+func Test_assert_beeps()
+ new
+ call assert_equal(0, assert_beeps('normal h'))
+
+ call assert_equal(1, assert_beeps('normal 0'))
+ call assert_match("command did not beep: normal 0", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(0, 'normal h'->assert_beeps())
+ call assert_equal(1, 'normal 0'->assert_beeps())
+ call assert_match("command did not beep: normal 0", v:errors[0])
+ call remove(v:errors, 0)
+
+ bwipe
+endfunc
+
func Test_assert_inrange()
call assert_equal(0, assert_inrange(7, 7, 7))
call assert_equal(0, assert_inrange(5, 7, 5))
call assert_equal(0, assert_inrange(5, 7, 6))
call assert_equal(0, assert_inrange(5, 7, 7))
+
call assert_equal(1, assert_inrange(5, 7, 4))
call assert_match("Expected range 5 - 7, but got 4", v:errors[0])
call remove(v:errors, 0)
@@ -64,6 +226,12 @@ func Test_assert_inrange()
call assert_match("Expected range 5 - 7, but got 8", v:errors[0])
call remove(v:errors, 0)
+ call assert_equal(0, 5->assert_inrange(5, 7))
+ call assert_equal(0, 7->assert_inrange(5, 7))
+ call assert_equal(1, 8->assert_inrange(5, 7))
+ call assert_match("Expected range 5 - 7, but got 8", v:errors[0])
+ call remove(v:errors, 0)
+
call assert_fails('call assert_inrange(1, 1)', 'E119:')
if has('float')
@@ -83,6 +251,12 @@ func Test_assert_inrange()
endif
endfunc
+func Test_assert_with_msg()
+ call assert_equal('foo', 'bar', 'testing')
+ call assert_match("testing: Expected 'foo' but got 'bar'", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
" Must be last.
func Test_zz_quit_detected()
" Verify that if a test function ends Vim the test script detects this.
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index 45285b69a1..a8d51ef598 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -4,7 +4,7 @@ source shared.vim
source check.vim
source term_util.vim
-func! s:cleanup_buffers() abort
+func s:cleanup_buffers() abort
for bnr in range(1, bufnr('$'))
if bufloaded(bnr) && bufnr('%') != bnr
execute 'bd! ' . bnr
@@ -2380,6 +2380,40 @@ func Test_autocmd_was_using_freed_memory()
pclose
endfunc
+func Test_BufWrite_lockmarks()
+ edit! Xtest
+ call setline(1, ['a', 'b', 'c', 'd'])
+
+ " :lockmarks preserves the marks
+ call SetChangeMarks(2, 3)
+ lockmarks write
+ call assert_equal([2, 3], [line("'["), line("']")])
+
+ " *WritePre autocmds get the correct line range, but lockmarks preserves the
+ " original values for the user
+ augroup lockmarks
+ au!
+ au BufWritePre,FilterWritePre * call assert_equal([1, 4], [line("'["), line("']")])
+ au FileWritePre * call assert_equal([3, 4], [line("'["), line("']")])
+ augroup END
+
+ lockmarks write
+ call assert_equal([2, 3], [line("'["), line("']")])
+
+ if executable('cat')
+ lockmarks %!cat
+ call assert_equal([2, 3], [line("'["), line("']")])
+ endif
+
+ lockmarks 3,4write Xtest2
+ call assert_equal([2, 3], [line("'["), line("']")])
+
+ au! lockmarks
+ augroup! lockmarks
+ call delete('Xtest')
+ call delete('Xtest2')
+endfunc
+
" FileChangedShell tested in test_filechanged.vim
func LogACmd()
@@ -2509,6 +2543,16 @@ func Test_close_autocmd_tab()
%bwipe!
endfunc
+func Test_Visual_doautoall_redraw()
+ call setline(1, ['a', 'b'])
+ new
+ wincmd p
+ call feedkeys("G\<C-V>", 'txn')
+ autocmd User Explode ++once redraw
+ doautoall User Explode
+ %bwipe!
+endfunc
+
func Test_autocmd_closes_window()
au BufNew,BufWinLeave * e %e
file yyy
diff --git a/src/nvim/testdir/test_breakindent.vim b/src/nvim/testdir/test_breakindent.vim
index 8d592f21ea..b619f2adb6 100644
--- a/src/nvim/testdir/test_breakindent.vim
+++ b/src/nvim/testdir/test_breakindent.vim
@@ -20,7 +20,7 @@ func s:screen_lines2(lnums, lnume, width) abort
return ScreenLines([a:lnums, a:lnume], a:width)
endfunc
-func! s:compare_lines(expect, actual)
+func s:compare_lines(expect, actual)
call assert_equal(join(a:expect, "\n"), join(a:actual, "\n"))
endfunc
diff --git a/src/nvim/testdir/test_buffer.vim b/src/nvim/testdir/test_buffer.vim
index 40111fdf06..a31cdbb49a 100644
--- a/src/nvim/testdir/test_buffer.vim
+++ b/src/nvim/testdir/test_buffer.vim
@@ -1,5 +1,7 @@
" Tests for Vim buffer
+source check.vim
+
func Test_buffer_error()
new foo1
new foo2
@@ -30,4 +32,33 @@ func Test_balt()
call assert_equal('OtherBuffer', bufname())
endfunc
+" Test for buffer match URL(scheme) check
+" scheme is alpha and inner hyphen only.
+func Test_buffer_scheme()
+ CheckMSWindows
+
+ set noshellslash
+ %bwipe!
+ let bufnames = [
+ \ #{id: 'b0', name: 'test://xyz/foo/b0' , match: 1},
+ \ #{id: 'b1', name: 'test+abc://xyz/foo/b1', match: 0},
+ \ #{id: 'b2', name: 'test_abc://xyz/foo/b2', match: 0},
+ \ #{id: 'b3', name: 'test-abc://xyz/foo/b3', match: 1},
+ \ #{id: 'b4', name: '-test://xyz/foo/b4' , match: 0},
+ \ #{id: 'b5', name: 'test-://xyz/foo/b5' , match: 0},
+ \]
+ for buf in bufnames
+ new `=buf.name`
+ if buf.match
+ call assert_equal(buf.name, getbufinfo(buf.id)[0].name)
+ else
+ " slashes will have become backslashes
+ call assert_notequal(buf.name, getbufinfo(buf.id)[0].name)
+ endif
+ bwipe
+ endfor
+
+ set shellslash&
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_cd.vim b/src/nvim/testdir/test_cd.vim
index 76a2620be0..c364babd65 100644
--- a/src/nvim/testdir/test_cd.vim
+++ b/src/nvim/testdir/test_cd.vim
@@ -44,6 +44,15 @@ func Test_cd_minus()
cd -
call assert_equal(path, getcwd())
+ " Test for :cd - after a failed :cd
+ " v8.2.1183 is not ported yet
+ " call assert_fails('cd /nonexistent', 'E344:')
+ call assert_fails('cd /nonexistent', 'E472:')
+ call assert_equal(path, getcwd())
+ cd -
+ call assert_equal(path_dotdot, getcwd())
+ cd -
+
" Test for :cd - without a previous directory
let lines =<< trim [SCRIPT]
call assert_fails('cd -', 'E186:')
diff --git a/src/nvim/testdir/test_cdo.vim b/src/nvim/testdir/test_cdo.vim
new file mode 100644
index 0000000000..aa2e4f1a8c
--- /dev/null
+++ b/src/nvim/testdir/test_cdo.vim
@@ -0,0 +1,205 @@
+" Tests for the :cdo, :cfdo, :ldo and :lfdo commands
+
+if !has('quickfix')
+ throw 'Skipped: quickfix feature missing'
+endif
+
+" Create the files used by the tests
+function SetUp()
+ call writefile(["Line1", "Line2", "Line3"], 'Xtestfile1')
+ call writefile(["Line1", "Line2", "Line3"], 'Xtestfile2')
+ call writefile(["Line1", "Line2", "Line3"], 'Xtestfile3')
+endfunction
+
+" Remove the files used by the tests
+function TearDown()
+ call delete('Xtestfile1')
+ call delete('Xtestfile2')
+ call delete('Xtestfile3')
+endfunction
+
+" Returns the current line in '<filename> <linenum>L <column>C' format
+function GetRuler()
+ return expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C'
+endfunction
+
+" Tests for the :cdo and :ldo commands
+function XdoTests(cchar)
+ enew
+
+ " Shortcuts for calling the cdo and ldo commands
+ let Xdo = a:cchar . 'do'
+ let Xgetexpr = a:cchar . 'getexpr'
+ let Xprev = a:cchar. 'prev'
+ let XdoCmd = Xdo . ' call add(l, GetRuler())'
+
+ " Try with an empty list
+ let l = []
+ exe XdoCmd
+ call assert_equal([], l)
+
+ " Populate the list and then try
+ exe Xgetexpr . " ['non-error 1', 'Xtestfile1:1:3:Line1', 'non-error 2', 'Xtestfile2:2:2:Line2', 'non-error 3', 'Xtestfile3:3:1:Line3']"
+
+ let l = []
+ exe XdoCmd
+ call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l)
+
+ " Run command only on selected error lines
+ let l = []
+ enew
+ exe "2,3" . XdoCmd
+ call assert_equal(['Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l)
+
+ " Boundary condition tests
+ let l = []
+ enew
+ exe "1,1" . XdoCmd
+ call assert_equal(['Xtestfile1 1L 3C'], l)
+
+ let l = []
+ enew
+ exe "3" . XdoCmd
+ call assert_equal(['Xtestfile3 3L 1C'], l)
+
+ " Range test commands
+ let l = []
+ enew
+ exe "%" . XdoCmd
+ call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l)
+
+ let l = []
+ enew
+ exe "1,$" . XdoCmd
+ call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l)
+
+ let l = []
+ enew
+ exe Xprev
+ exe "." . XdoCmd
+ call assert_equal(['Xtestfile2 2L 2C'], l)
+
+ let l = []
+ enew
+ exe "+" . XdoCmd
+ call assert_equal(['Xtestfile3 3L 1C'], l)
+
+ " Invalid error lines test
+ let l = []
+ enew
+ exe "silent! 27" . XdoCmd
+ exe "silent! 4,5" . XdoCmd
+ call assert_equal([], l)
+
+ " Run commands from an unsaved buffer
+ let v:errmsg=''
+ let l = []
+ enew
+ setlocal modified
+ exe "silent! 2,2" . XdoCmd
+ if v:errmsg !~# 'No write since last change'
+ call add(v:errors, 'Unsaved file change test failed')
+ endif
+
+ " If the executed command fails, then the operation should be aborted
+ enew!
+ let subst_count = 0
+ exe "silent!" . Xdo . " s/Line/xLine/ | let subst_count += 1"
+ if subst_count != 1 || getline('.') != 'xLine1'
+ call add(v:errors, 'Abort command on error test failed')
+ endif
+
+ let l = []
+ exe "2,2" . Xdo . "! call add(l, GetRuler())"
+ call assert_equal(['Xtestfile2 2L 2C'], l)
+
+ " List with no valid error entries
+ let l = []
+ edit! +2 Xtestfile1
+ exe Xgetexpr . " ['non-error 1', 'non-error 2', 'non-error 3']"
+ exe XdoCmd
+ call assert_equal([], l)
+ exe "silent! 2" . XdoCmd
+ call assert_equal([], l)
+ let v:errmsg=''
+ exe "%" . XdoCmd
+ exe "1,$" . XdoCmd
+ exe "." . XdoCmd
+ call assert_equal('', v:errmsg)
+
+ " List with only one valid entry
+ let l = []
+ exe Xgetexpr . " ['Xtestfile3:3:1:Line3']"
+ exe XdoCmd
+ call assert_equal(['Xtestfile3 3L 1C'], l)
+
+endfunction
+
+" Tests for the :cfdo and :lfdo commands
+function XfdoTests(cchar)
+ enew
+
+ " Shortcuts for calling the cfdo and lfdo commands
+ let Xfdo = a:cchar . 'fdo'
+ let Xgetexpr = a:cchar . 'getexpr'
+ let XfdoCmd = Xfdo . ' call add(l, GetRuler())'
+ let Xpfile = a:cchar. 'pfile'
+
+ " Clear the quickfix/location list
+ exe Xgetexpr . " []"
+
+ " Try with an empty list
+ let l = []
+ exe XfdoCmd
+ call assert_equal([], l)
+
+ " Populate the list and then try
+ exe Xgetexpr . " ['non-error 1', 'Xtestfile1:1:3:Line1', 'Xtestfile1:2:1:Line2', 'non-error 2', 'Xtestfile2:2:2:Line2', 'non-error 3', 'Xtestfile3:2:3:Line2', 'Xtestfile3:3:1:Line3']"
+
+ let l = []
+ exe XfdoCmd
+ call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l)
+
+ " Run command only on selected error lines
+ let l = []
+ exe "2,3" . XfdoCmd
+ call assert_equal(['Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l)
+
+ " Boundary condition tests
+ let l = []
+ exe "3" . XfdoCmd
+ call assert_equal(['Xtestfile3 2L 3C'], l)
+
+ " Range test commands
+ let l = []
+ exe "%" . XfdoCmd
+ call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l)
+
+ let l = []
+ exe "1,$" . XfdoCmd
+ call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l)
+
+ let l = []
+ exe Xpfile
+ exe "." . XfdoCmd
+ call assert_equal(['Xtestfile2 2L 2C'], l)
+
+ " List with only one valid entry
+ let l = []
+ exe Xgetexpr . " ['Xtestfile2:2:5:Line2']"
+ exe XfdoCmd
+ call assert_equal(['Xtestfile2 2L 5C'], l)
+
+endfunction
+
+" Tests for cdo and cfdo
+function Test_cdo()
+ call XdoTests('c')
+ call XfdoTests('c')
+endfunction
+
+" Tests for ldo and lfdo
+function Test_ldo()
+ call XdoTests('l')
+ call XfdoTests('l')
+endfunction
diff --git a/src/nvim/testdir/test_charsearch_utf8.vim b/src/nvim/testdir/test_charsearch_utf8.vim
index 09341a90b0..82a807ac5b 100644
--- a/src/nvim/testdir/test_charsearch_utf8.vim
+++ b/src/nvim/testdir/test_charsearch_utf8.vim
@@ -1,7 +1,7 @@
" Tests for related f{char} and t{char} using utf-8.
" Test for t,f,F,T movement commands
-function! Test_search_cmds()
+func Test_search_cmds()
new!
call setline(1, "・最初から最後まで最強のVimは最高")
1
diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim
index 562867f548..5dc54111e7 100644
--- a/src/nvim/testdir/test_cindent.vim
+++ b/src/nvim/testdir/test_cindent.vim
@@ -1,6 +1,5 @@
" Test for cinoptions and cindent
"
-" TODO: rewrite test3.in into this new style test
func Test_cino_hash()
" Test that curbuf->b_ind_hash_comment is correctly reset
@@ -128,6 +127,5178 @@ func Test_cindent_func()
bwipe!
endfunc
+func Test_cindent_1()
+ new
+ setl cindent ts=4 sw=4
+ setl cino& sts&
+
+ let code =<< trim [CODE]
+ /* start of AUTO matically checked vim: set ts=4 : */
+ {
+ if (test)
+ cmd1;
+ cmd2;
+ }
+
+ {
+ if (test)
+ cmd1;
+ else
+ cmd2;
+ }
+
+ {
+ if (test)
+ {
+ cmd1;
+ cmd2;
+ }
+ }
+
+ {
+ if (test)
+ {
+ cmd1;
+ else
+ }
+ }
+
+ {
+ while (this)
+ if (test)
+ cmd1;
+ cmd2;
+ }
+
+ {
+ while (this)
+ if (test)
+ cmd1;
+ else
+ cmd2;
+ }
+
+ {
+ if (test)
+ {
+ cmd;
+ }
+
+ if (test)
+ cmd;
+ }
+
+ {
+ if (test) {
+ cmd;
+ }
+
+ if (test) cmd;
+ }
+
+ {
+ cmd1;
+ for (blah)
+ while (this)
+ if (test)
+ cmd2;
+ cmd3;
+ }
+
+ {
+ cmd1;
+ for (blah)
+ while (this)
+ if (test)
+ cmd2;
+ cmd3;
+
+ if (test)
+ {
+ cmd1;
+ cmd2;
+ cmd3;
+ }
+ }
+
+
+ /* Test for 'cindent' do/while mixed with if/else: */
+
+ {
+ do
+ if (asdf)
+ asdfasd;
+ while (cond);
+
+ do
+ if (asdf)
+ while (asdf)
+ asdf;
+ while (asdf);
+ }
+
+ /* Test for 'cindent' with two ) on a continuation line */
+ {
+ if (asdfasdf;asldkfj asdlkfj as;ldkfj sal;d
+ aal;sdkjf ( ;asldfkja;sldfk
+ al;sdjfka ;slkdf ) sa;ldkjfsa dlk;)
+ line up here;
+ }
+
+
+ /* C++ tests: */
+
+ // foo() these three lines should remain in column 0
+ // {
+ // }
+
+ /* Test for continuation and unterminated lines: */
+ {
+ i = 99 + 14325 +
+ 21345 +
+ 21345 +
+ 21345 + ( 21345 +
+ 21345) +
+ 2345 +
+ 1234;
+ c = 1;
+ }
+
+ /*
+ testje for indent with empty line
+
+ here */
+
+ {
+ if (testing &&
+ not a joke ||
+ line up here)
+ hay;
+ if (testing &&
+ (not a joke || testing
+ )line up here)
+ hay;
+ if (testing &&
+ (not a joke || testing
+ line up here))
+ hay;
+ }
+
+
+ {
+ switch (c)
+ {
+ case xx:
+ do
+ if (asdf)
+ do
+ asdfasdf;
+ while (asdf);
+ else
+ asdfasdf;
+ while (cond);
+ case yy:
+ case xx:
+ case zz:
+ testing;
+ }
+ }
+
+ {
+ if (cond) {
+ foo;
+ }
+ else
+ {
+ bar;
+ }
+ }
+
+ {
+ if (alskdfj ;alsdkfjal;skdjf (;sadlkfsa ;dlkf j;alksdfj ;alskdjf
+ alsdkfj (asldk;fj
+ awith cino=(0 ;lf this one goes to below the paren with ==
+ ;laksjfd ;lsakdjf ;alskdf asd)
+ asdfasdf;)))
+ asdfasdf;
+ }
+
+ int
+ func(a, b)
+ int a;
+ int c;
+ {
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3)
+ )
+ }
+
+ {
+ while (asd)
+ {
+ if (asdf)
+ if (test)
+ if (that)
+ {
+ if (asdf)
+ do
+ cdasd;
+ while (as
+ df);
+ }
+ else
+ if (asdf)
+ asdf;
+ else
+ asdf;
+ asdf;
+ }
+ }
+
+ {
+ s = "/*"; b = ';'
+ s = "/*"; b = ';';
+ a = b;
+ }
+
+ {
+ switch (a)
+ {
+ case a:
+ switch (t)
+ {
+ case 1:
+ cmd;
+ break;
+ case 2:
+ cmd;
+ break;
+ }
+ cmd;
+ break;
+ case b:
+ {
+ int i;
+ cmd;
+ }
+ break;
+ case c: {
+ int i;
+ cmd;
+ }
+ case d: if (cond &&
+ test) { /* this line doesn't work right */
+ int i;
+ cmd;
+ }
+ break;
+ }
+ }
+
+ {
+ if (!(vim_strchr(p_cpo, CPO_BUFOPTGLOB) != NULL && entering) &&
+ (bp_to->b_p_initialized ||
+ (!entering && vim_strchr(p_cpo, CPO_BUFOPT) != NULL)))
+ return;
+ label :
+ asdf = asdf ?
+ asdf : asdf;
+ asdf = asdf ?
+ asdf: asdf;
+ }
+
+ /* Special Comments : This function has the added complexity (compared */
+ /* : to addtolist) of having to check for a detail */
+ /* : texture and add that to the list first. */
+
+ char *(array[100]) = {
+ "testje",
+ "foo",
+ "bar",
+ }
+
+ enum soppie
+ {
+ yes = 0,
+ no,
+ maybe
+ };
+
+ typedef enum soppie
+ {
+ yes = 0,
+ no,
+ maybe
+ };
+
+ static enum
+ {
+ yes = 0,
+ no,
+ maybe
+ } soppie;
+
+ public static enum
+ {
+ yes = 0,
+ no,
+ maybe
+ } soppie;
+
+ static private enum
+ {
+ yes = 0,
+ no,
+ maybe
+ } soppie;
+
+ {
+ int a,
+ b;
+ }
+
+ {
+ struct Type
+ {
+ int i;
+ char *str;
+ } var[] =
+ {
+ 0, "zero",
+ 1, "one",
+ 2, "two",
+ 3, "three"
+ };
+
+ float matrix[3][3] =
+ {
+ {
+ 0,
+ 1,
+ 2
+ },
+ {
+ 3,
+ 4,
+ 5
+ },
+ {
+ 6,
+ 7,
+ 8
+ }
+ };
+ }
+
+ {
+ /* blah ( blah */
+ /* where does this go? */
+
+ /* blah ( blah */
+ cmd;
+
+ func(arg1,
+ /* comment */
+ arg2);
+ a;
+ {
+ b;
+ {
+ c; /* Hey, NOW it indents?! */
+ }
+ }
+
+ {
+ func(arg1,
+ arg2,
+ arg3);
+ /* Hey, what am I doing here? Is this coz of the ","? */
+ }
+ }
+
+ main ()
+ {
+ if (cond)
+ {
+ a = b;
+ }
+ if (cond) {
+ a = c;
+ }
+ if (cond)
+ a = d;
+ return;
+ }
+
+ {
+ case 2: if (asdf &&
+ asdfasdf)
+ aasdf;
+ a = 9;
+ case 3: if (asdf)
+ aasdf;
+ a = 9;
+ case 4: x = 1;
+ y = 2;
+
+ label: if (asdf)
+ here;
+
+ label: if (asdf &&
+ asdfasdf)
+ {
+ }
+
+ label: if (asdf &&
+ asdfasdf) {
+ there;
+ }
+
+ label: if (asdf &&
+ asdfasdf)
+ there;
+ }
+
+ {
+ /*
+ hello with ":set comments= cino=c5"
+ */
+
+ /*
+ hello with ":set comments= cino="
+ */
+ }
+
+
+ {
+ if (a < b) {
+ a = a + 1;
+ } else
+ a = a + 2;
+
+ if (a)
+ do {
+ testing;
+ } while (asdfasdf);
+ a = b + 1;
+ asdfasdf
+ }
+
+ {
+ for ( int i = 0;
+ i < 10; i++ )
+ {
+ }
+ i = 0;
+ }
+
+ class bob
+ {
+ int foo() {return 1;}
+ int bar;
+ }
+
+ main()
+ {
+ while(1)
+ if (foo)
+ {
+ bar;
+ }
+ else {
+ asdf;
+ }
+ misplacedline;
+ }
+
+ {
+ if (clipboard.state == SELECT_DONE
+ && ((row == clipboard.start.lnum
+ && col >= clipboard.start.col)
+ || row > clipboard.start.lnum))
+ }
+
+ {
+ if (1) {i += 4;}
+ where_am_i;
+ return 0;
+ }
+
+ {
+ {
+ } // sdf(asdf
+ if (asdf)
+ asd;
+ }
+
+ {
+ label1:
+ label2:
+ }
+
+ {
+ int fooRet = foo(pBar1, false /*fKB*/,
+ true /*fPTB*/, 3 /*nT*/, false /*fDF*/);
+ f() {
+ for ( i = 0;
+ i < m;
+ /* c */ i++ ) {
+ a = b;
+ }
+ }
+ }
+
+ {
+ f1(/*comment*/);
+ f2();
+ }
+
+ {
+ do {
+ if (foo) {
+ } else
+ ;
+ } while (foo);
+ foo(); // was wrong
+ }
+
+ int x; // no extra indent because of the ;
+ void func()
+ {
+ }
+
+ char *tab[] = {"aaa",
+ "};", /* }; */ NULL}
+ int indented;
+ {}
+
+ char *a[] = {"aaa", "bbb",
+ "ccc", NULL};
+ // here
+
+ char *tab[] = {"aaa",
+ "xx", /* xx */}; /* asdf */
+ int not_indented;
+
+ {
+ do {
+ switch (bla)
+ {
+ case 1: if (foo)
+ bar;
+ }
+ } while (boo);
+ wrong;
+ }
+
+ int foo,
+ bar;
+ int foo;
+
+ #if defined(foo) \
+ && defined(bar)
+ char * xx = "asdf\
+ foo\
+ bor";
+ int x;
+
+ char *foo = "asdf\
+ asdf\
+ asdf",
+ *bar;
+
+ void f()
+ {
+ #if defined(foo) \
+ && defined(bar)
+ char *foo = "asdf\
+ asdf\
+ asdf",
+ *bar;
+ {
+ int i;
+ char *foo = "asdf\
+ asdf\
+ asdf",
+ *bar;
+ }
+ #endif
+ }
+ #endif
+
+ int y; // comment
+ // comment
+
+ // comment
+
+ {
+ Constructor(int a,
+ int b ) : BaseClass(a)
+ {
+ }
+ }
+
+ void foo()
+ {
+ char one,
+ two;
+ struct bla piet,
+ jan;
+ enum foo kees,
+ jannie;
+ static unsigned sdf,
+ krap;
+ unsigned int piet,
+ jan;
+ int
+ kees,
+ jan;
+ }
+
+ {
+ t(int f,
+ int d); // )
+ d();
+ }
+
+ Constructor::Constructor(int a,
+ int b
+ ) :
+ BaseClass(a,
+ b,
+ c),
+ mMember(b),
+ {
+ }
+
+ Constructor::Constructor(int a,
+ int b ) :
+ BaseClass(a)
+ {
+ }
+
+ Constructor::Constructor(int a,
+ int b ) /*x*/ : /*x*/ BaseClass(a),
+ member(b)
+ {
+ }
+
+ A::A(int a, int b)
+ : aa(a),
+ bb(b),
+ cc(c)
+ {
+ }
+
+ class CAbc :
+ public BaseClass1,
+ protected BaseClass2
+ {
+ int Test() { return FALSE; }
+ int Test1() { return TRUE; }
+
+ CAbc(int a, int b ) :
+ BaseClass(a)
+ {
+ switch(xxx)
+ {
+ case abc:
+ asdf();
+ break;
+
+ case 999:
+ baer();
+ break;
+ }
+ }
+
+ public: // <-- this was incoreectly indented before!!
+ void testfall();
+ protected:
+ void testfall();
+ };
+
+ class CAbc : public BaseClass1,
+ protected BaseClass2
+ {
+ };
+
+ static struct
+ {
+ int a;
+ int b;
+ } variable[COUNT] =
+ {
+ {
+ 123,
+ 456
+ },
+ {
+ 123,
+ 456
+ }
+ };
+
+ static struct
+ {
+ int a;
+ int b;
+ } variable[COUNT] =
+ {
+ { 123, 456 },
+ { 123, 456 }
+ };
+
+ void asdf() /* ind_maxparen may cause trouble here */
+ {
+ if ((0
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1)) break;
+ }
+
+ foo()
+ {
+ a = cond ? foo() : asdf
+ + asdf;
+
+ a = cond ?
+ foo() : asdf
+ + asdf;
+ }
+
+ int main(void)
+ {
+ if (a)
+ if (b)
+ 2;
+ else 3;
+ next_line_of_code();
+ }
+
+ barry()
+ {
+ Foo::Foo (int one,
+ int two)
+ : something(4)
+ {}
+ }
+
+ barry()
+ {
+ Foo::Foo (int one, int two)
+ : something(4)
+ {}
+ }
+
+ Constructor::Constructor(int a,
+ int b
+ ) :
+ BaseClass(a,
+ b,
+ c),
+ mMember(b)
+ {
+ }
+ int main ()
+ {
+ if (lala)
+ do
+ ++(*lolo);
+ while (lili
+ && lele);
+ lulu;
+ }
+
+ int main ()
+ {
+ switch (c)
+ {
+ case 'c': if (cond)
+ {
+ }
+ }
+ }
+
+ main()
+ {
+ (void) MyFancyFuasdfadsfnction(
+ argument);
+ }
+
+ main()
+ {
+ char foo[] = "/*";
+ /* as
+ df */
+ hello
+ }
+
+ /* valid namespaces with normal indent */
+ namespace
+ {
+ {
+ 111111111111;
+ }
+ }
+ namespace /* test */
+ {
+ 11111111111111111;
+ }
+ namespace // test
+ {
+ 111111111111111111;
+ }
+ namespace
+ {
+ 111111111111111111;
+ }
+ namespace test
+ {
+ 111111111111111111;
+ }
+ namespace{
+ 111111111111111111;
+ }
+ namespace test{
+ 111111111111111111;
+ }
+ namespace {
+ 111111111111111111;
+ }
+ namespace test {
+ 111111111111111111;
+ namespace test2 {
+ 22222222222222222;
+ }
+ }
+ inline namespace {
+ 111111111111111111;
+ }
+ inline /* test */ namespace {
+ 111111111111111111;
+ }
+ inline/* test */namespace {
+ 111111111111111111;
+ }
+
+ /* invalid namespaces use block indent */
+ namespace test test2 {
+ 111111111111111111111;
+ }
+ namespace11111111111 {
+ 111111111111;
+ }
+ namespace() {
+ 1111111111111;
+ }
+ namespace()
+ {
+ 111111111111111111;
+ }
+ namespace test test2
+ {
+ 1111111111111111111;
+ }
+ namespace111111111
+ {
+ 111111111111111111;
+ }
+ inlinenamespace {
+ 111111111111111111;
+ }
+
+ void getstring() {
+ /* Raw strings */
+ const char* s = R"(
+ test {
+ # comment
+ field: 123
+ }
+ )";
+ }
+
+ void getstring() {
+ const char* s = R"foo(
+ test {
+ # comment
+ field: 123
+ }
+ )foo";
+ }
+
+ {
+ int a[4] = {
+ [0] = 0,
+ [1] = 1,
+ [2] = 2,
+ [3] = 3,
+ };
+ }
+
+ {
+ a = b[2]
+ + 3;
+ }
+
+ {
+ if (1)
+ /* aaaaa
+ * bbbbb
+ */
+ a = 1;
+ }
+
+ void func()
+ {
+ switch (foo)
+ {
+ case (bar):
+ if (baz())
+ quux();
+ break;
+ case (shmoo):
+ if (!bar)
+ {
+ }
+ case (foo1):
+ switch (bar)
+ {
+ case baz:
+ baz_f();
+ break;
+ }
+ break;
+ default:
+ baz();
+ baz();
+ break;
+ }
+ }
+
+ /* end of AUTO */
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('start of AUTO')
+ exe "normal =/end of AUTO\<CR>"
+
+ let expected =<< trim [CODE]
+ /* start of AUTO matically checked vim: set ts=4 : */
+ {
+ if (test)
+ cmd1;
+ cmd2;
+ }
+
+ {
+ if (test)
+ cmd1;
+ else
+ cmd2;
+ }
+
+ {
+ if (test)
+ {
+ cmd1;
+ cmd2;
+ }
+ }
+
+ {
+ if (test)
+ {
+ cmd1;
+ else
+ }
+ }
+
+ {
+ while (this)
+ if (test)
+ cmd1;
+ cmd2;
+ }
+
+ {
+ while (this)
+ if (test)
+ cmd1;
+ else
+ cmd2;
+ }
+
+ {
+ if (test)
+ {
+ cmd;
+ }
+
+ if (test)
+ cmd;
+ }
+
+ {
+ if (test) {
+ cmd;
+ }
+
+ if (test) cmd;
+ }
+
+ {
+ cmd1;
+ for (blah)
+ while (this)
+ if (test)
+ cmd2;
+ cmd3;
+ }
+
+ {
+ cmd1;
+ for (blah)
+ while (this)
+ if (test)
+ cmd2;
+ cmd3;
+
+ if (test)
+ {
+ cmd1;
+ cmd2;
+ cmd3;
+ }
+ }
+
+
+ /* Test for 'cindent' do/while mixed with if/else: */
+
+ {
+ do
+ if (asdf)
+ asdfasd;
+ while (cond);
+
+ do
+ if (asdf)
+ while (asdf)
+ asdf;
+ while (asdf);
+ }
+
+ /* Test for 'cindent' with two ) on a continuation line */
+ {
+ if (asdfasdf;asldkfj asdlkfj as;ldkfj sal;d
+ aal;sdkjf ( ;asldfkja;sldfk
+ al;sdjfka ;slkdf ) sa;ldkjfsa dlk;)
+ line up here;
+ }
+
+
+ /* C++ tests: */
+
+ // foo() these three lines should remain in column 0
+ // {
+ // }
+
+ /* Test for continuation and unterminated lines: */
+ {
+ i = 99 + 14325 +
+ 21345 +
+ 21345 +
+ 21345 + ( 21345 +
+ 21345) +
+ 2345 +
+ 1234;
+ c = 1;
+ }
+
+ /*
+ testje for indent with empty line
+
+ here */
+
+ {
+ if (testing &&
+ not a joke ||
+ line up here)
+ hay;
+ if (testing &&
+ (not a joke || testing
+ )line up here)
+ hay;
+ if (testing &&
+ (not a joke || testing
+ line up here))
+ hay;
+ }
+
+
+ {
+ switch (c)
+ {
+ case xx:
+ do
+ if (asdf)
+ do
+ asdfasdf;
+ while (asdf);
+ else
+ asdfasdf;
+ while (cond);
+ case yy:
+ case xx:
+ case zz:
+ testing;
+ }
+ }
+
+ {
+ if (cond) {
+ foo;
+ }
+ else
+ {
+ bar;
+ }
+ }
+
+ {
+ if (alskdfj ;alsdkfjal;skdjf (;sadlkfsa ;dlkf j;alksdfj ;alskdjf
+ alsdkfj (asldk;fj
+ awith cino=(0 ;lf this one goes to below the paren with ==
+ ;laksjfd ;lsakdjf ;alskdf asd)
+ asdfasdf;)))
+ asdfasdf;
+ }
+
+ int
+ func(a, b)
+ int a;
+ int c;
+ {
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3)
+ )
+ }
+
+ {
+ while (asd)
+ {
+ if (asdf)
+ if (test)
+ if (that)
+ {
+ if (asdf)
+ do
+ cdasd;
+ while (as
+ df);
+ }
+ else
+ if (asdf)
+ asdf;
+ else
+ asdf;
+ asdf;
+ }
+ }
+
+ {
+ s = "/*"; b = ';'
+ s = "/*"; b = ';';
+ a = b;
+ }
+
+ {
+ switch (a)
+ {
+ case a:
+ switch (t)
+ {
+ case 1:
+ cmd;
+ break;
+ case 2:
+ cmd;
+ break;
+ }
+ cmd;
+ break;
+ case b:
+ {
+ int i;
+ cmd;
+ }
+ break;
+ case c: {
+ int i;
+ cmd;
+ }
+ case d: if (cond &&
+ test) { /* this line doesn't work right */
+ int i;
+ cmd;
+ }
+ break;
+ }
+ }
+
+ {
+ if (!(vim_strchr(p_cpo, CPO_BUFOPTGLOB) != NULL && entering) &&
+ (bp_to->b_p_initialized ||
+ (!entering && vim_strchr(p_cpo, CPO_BUFOPT) != NULL)))
+ return;
+ label :
+ asdf = asdf ?
+ asdf : asdf;
+ asdf = asdf ?
+ asdf: asdf;
+ }
+
+ /* Special Comments : This function has the added complexity (compared */
+ /* : to addtolist) of having to check for a detail */
+ /* : texture and add that to the list first. */
+
+ char *(array[100]) = {
+ "testje",
+ "foo",
+ "bar",
+ }
+
+ enum soppie
+ {
+ yes = 0,
+ no,
+ maybe
+ };
+
+ typedef enum soppie
+ {
+ yes = 0,
+ no,
+ maybe
+ };
+
+ static enum
+ {
+ yes = 0,
+ no,
+ maybe
+ } soppie;
+
+ public static enum
+ {
+ yes = 0,
+ no,
+ maybe
+ } soppie;
+
+ static private enum
+ {
+ yes = 0,
+ no,
+ maybe
+ } soppie;
+
+ {
+ int a,
+ b;
+ }
+
+ {
+ struct Type
+ {
+ int i;
+ char *str;
+ } var[] =
+ {
+ 0, "zero",
+ 1, "one",
+ 2, "two",
+ 3, "three"
+ };
+
+ float matrix[3][3] =
+ {
+ {
+ 0,
+ 1,
+ 2
+ },
+ {
+ 3,
+ 4,
+ 5
+ },
+ {
+ 6,
+ 7,
+ 8
+ }
+ };
+ }
+
+ {
+ /* blah ( blah */
+ /* where does this go? */
+
+ /* blah ( blah */
+ cmd;
+
+ func(arg1,
+ /* comment */
+ arg2);
+ a;
+ {
+ b;
+ {
+ c; /* Hey, NOW it indents?! */
+ }
+ }
+
+ {
+ func(arg1,
+ arg2,
+ arg3);
+ /* Hey, what am I doing here? Is this coz of the ","? */
+ }
+ }
+
+ main ()
+ {
+ if (cond)
+ {
+ a = b;
+ }
+ if (cond) {
+ a = c;
+ }
+ if (cond)
+ a = d;
+ return;
+ }
+
+ {
+ case 2: if (asdf &&
+ asdfasdf)
+ aasdf;
+ a = 9;
+ case 3: if (asdf)
+ aasdf;
+ a = 9;
+ case 4: x = 1;
+ y = 2;
+
+ label: if (asdf)
+ here;
+
+ label: if (asdf &&
+ asdfasdf)
+ {
+ }
+
+ label: if (asdf &&
+ asdfasdf) {
+ there;
+ }
+
+ label: if (asdf &&
+ asdfasdf)
+ there;
+ }
+
+ {
+ /*
+ hello with ":set comments= cino=c5"
+ */
+
+ /*
+ hello with ":set comments= cino="
+ */
+ }
+
+
+ {
+ if (a < b) {
+ a = a + 1;
+ } else
+ a = a + 2;
+
+ if (a)
+ do {
+ testing;
+ } while (asdfasdf);
+ a = b + 1;
+ asdfasdf
+ }
+
+ {
+ for ( int i = 0;
+ i < 10; i++ )
+ {
+ }
+ i = 0;
+ }
+
+ class bob
+ {
+ int foo() {return 1;}
+ int bar;
+ }
+
+ main()
+ {
+ while(1)
+ if (foo)
+ {
+ bar;
+ }
+ else {
+ asdf;
+ }
+ misplacedline;
+ }
+
+ {
+ if (clipboard.state == SELECT_DONE
+ && ((row == clipboard.start.lnum
+ && col >= clipboard.start.col)
+ || row > clipboard.start.lnum))
+ }
+
+ {
+ if (1) {i += 4;}
+ where_am_i;
+ return 0;
+ }
+
+ {
+ {
+ } // sdf(asdf
+ if (asdf)
+ asd;
+ }
+
+ {
+ label1:
+ label2:
+ }
+
+ {
+ int fooRet = foo(pBar1, false /*fKB*/,
+ true /*fPTB*/, 3 /*nT*/, false /*fDF*/);
+ f() {
+ for ( i = 0;
+ i < m;
+ /* c */ i++ ) {
+ a = b;
+ }
+ }
+ }
+
+ {
+ f1(/*comment*/);
+ f2();
+ }
+
+ {
+ do {
+ if (foo) {
+ } else
+ ;
+ } while (foo);
+ foo(); // was wrong
+ }
+
+ int x; // no extra indent because of the ;
+ void func()
+ {
+ }
+
+ char *tab[] = {"aaa",
+ "};", /* }; */ NULL}
+ int indented;
+ {}
+
+ char *a[] = {"aaa", "bbb",
+ "ccc", NULL};
+ // here
+
+ char *tab[] = {"aaa",
+ "xx", /* xx */}; /* asdf */
+ int not_indented;
+
+ {
+ do {
+ switch (bla)
+ {
+ case 1: if (foo)
+ bar;
+ }
+ } while (boo);
+ wrong;
+ }
+
+ int foo,
+ bar;
+ int foo;
+
+ #if defined(foo) \
+ && defined(bar)
+ char * xx = "asdf\
+ foo\
+ bor";
+ int x;
+
+ char *foo = "asdf\
+ asdf\
+ asdf",
+ *bar;
+
+ void f()
+ {
+ #if defined(foo) \
+ && defined(bar)
+ char *foo = "asdf\
+ asdf\
+ asdf",
+ *bar;
+ {
+ int i;
+ char *foo = "asdf\
+ asdf\
+ asdf",
+ *bar;
+ }
+ #endif
+ }
+ #endif
+
+ int y; // comment
+ // comment
+
+ // comment
+
+ {
+ Constructor(int a,
+ int b ) : BaseClass(a)
+ {
+ }
+ }
+
+ void foo()
+ {
+ char one,
+ two;
+ struct bla piet,
+ jan;
+ enum foo kees,
+ jannie;
+ static unsigned sdf,
+ krap;
+ unsigned int piet,
+ jan;
+ int
+ kees,
+ jan;
+ }
+
+ {
+ t(int f,
+ int d); // )
+ d();
+ }
+
+ Constructor::Constructor(int a,
+ int b
+ ) :
+ BaseClass(a,
+ b,
+ c),
+ mMember(b),
+ {
+ }
+
+ Constructor::Constructor(int a,
+ int b ) :
+ BaseClass(a)
+ {
+ }
+
+ Constructor::Constructor(int a,
+ int b ) /*x*/ : /*x*/ BaseClass(a),
+ member(b)
+ {
+ }
+
+ A::A(int a, int b)
+ : aa(a),
+ bb(b),
+ cc(c)
+ {
+ }
+
+ class CAbc :
+ public BaseClass1,
+ protected BaseClass2
+ {
+ int Test() { return FALSE; }
+ int Test1() { return TRUE; }
+
+ CAbc(int a, int b ) :
+ BaseClass(a)
+ {
+ switch(xxx)
+ {
+ case abc:
+ asdf();
+ break;
+
+ case 999:
+ baer();
+ break;
+ }
+ }
+
+ public: // <-- this was incoreectly indented before!!
+ void testfall();
+ protected:
+ void testfall();
+ };
+
+ class CAbc : public BaseClass1,
+ protected BaseClass2
+ {
+ };
+
+ static struct
+ {
+ int a;
+ int b;
+ } variable[COUNT] =
+ {
+ {
+ 123,
+ 456
+ },
+ {
+ 123,
+ 456
+ }
+ };
+
+ static struct
+ {
+ int a;
+ int b;
+ } variable[COUNT] =
+ {
+ { 123, 456 },
+ { 123, 456 }
+ };
+
+ void asdf() /* ind_maxparen may cause trouble here */
+ {
+ if ((0
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1)) break;
+ }
+
+ foo()
+ {
+ a = cond ? foo() : asdf
+ + asdf;
+
+ a = cond ?
+ foo() : asdf
+ + asdf;
+ }
+
+ int main(void)
+ {
+ if (a)
+ if (b)
+ 2;
+ else 3;
+ next_line_of_code();
+ }
+
+ barry()
+ {
+ Foo::Foo (int one,
+ int two)
+ : something(4)
+ {}
+ }
+
+ barry()
+ {
+ Foo::Foo (int one, int two)
+ : something(4)
+ {}
+ }
+
+ Constructor::Constructor(int a,
+ int b
+ ) :
+ BaseClass(a,
+ b,
+ c),
+ mMember(b)
+ {
+ }
+ int main ()
+ {
+ if (lala)
+ do
+ ++(*lolo);
+ while (lili
+ && lele);
+ lulu;
+ }
+
+ int main ()
+ {
+ switch (c)
+ {
+ case 'c': if (cond)
+ {
+ }
+ }
+ }
+
+ main()
+ {
+ (void) MyFancyFuasdfadsfnction(
+ argument);
+ }
+
+ main()
+ {
+ char foo[] = "/*";
+ /* as
+ df */
+ hello
+ }
+
+ /* valid namespaces with normal indent */
+ namespace
+ {
+ {
+ 111111111111;
+ }
+ }
+ namespace /* test */
+ {
+ 11111111111111111;
+ }
+ namespace // test
+ {
+ 111111111111111111;
+ }
+ namespace
+ {
+ 111111111111111111;
+ }
+ namespace test
+ {
+ 111111111111111111;
+ }
+ namespace{
+ 111111111111111111;
+ }
+ namespace test{
+ 111111111111111111;
+ }
+ namespace {
+ 111111111111111111;
+ }
+ namespace test {
+ 111111111111111111;
+ namespace test2 {
+ 22222222222222222;
+ }
+ }
+ inline namespace {
+ 111111111111111111;
+ }
+ inline /* test */ namespace {
+ 111111111111111111;
+ }
+ inline/* test */namespace {
+ 111111111111111111;
+ }
+
+ /* invalid namespaces use block indent */
+ namespace test test2 {
+ 111111111111111111111;
+ }
+ namespace11111111111 {
+ 111111111111;
+ }
+ namespace() {
+ 1111111111111;
+ }
+ namespace()
+ {
+ 111111111111111111;
+ }
+ namespace test test2
+ {
+ 1111111111111111111;
+ }
+ namespace111111111
+ {
+ 111111111111111111;
+ }
+ inlinenamespace {
+ 111111111111111111;
+ }
+
+ void getstring() {
+ /* Raw strings */
+ const char* s = R"(
+ test {
+ # comment
+ field: 123
+ }
+ )";
+ }
+
+ void getstring() {
+ const char* s = R"foo(
+ test {
+ # comment
+ field: 123
+ }
+ )foo";
+ }
+
+ {
+ int a[4] = {
+ [0] = 0,
+ [1] = 1,
+ [2] = 2,
+ [3] = 3,
+ };
+ }
+
+ {
+ a = b[2]
+ + 3;
+ }
+
+ {
+ if (1)
+ /* aaaaa
+ * bbbbb
+ */
+ a = 1;
+ }
+
+ void func()
+ {
+ switch (foo)
+ {
+ case (bar):
+ if (baz())
+ quux();
+ break;
+ case (shmoo):
+ if (!bar)
+ {
+ }
+ case (foo1):
+ switch (bar)
+ {
+ case baz:
+ baz_f();
+ break;
+ }
+ break;
+ default:
+ baz();
+ baz();
+ break;
+ }
+ }
+
+ /* end of AUTO */
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_2()
+ new
+ setl cindent ts=4 sw=4
+ setl tw=0 noai fo=croq
+ let &wm = &columns - 20
+
+ let code =<< trim [CODE]
+ {
+
+ /* this is
+ * a real serious important big
+ * comment
+ */
+ /* insert " about life, the universe, and the rest" after "serious" */
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('serious', 'e')
+ normal a about life, the universe, and the rest
+
+ let expected =<< trim [CODE]
+ {
+
+ /* this is
+ * a real serious
+ * about life, the
+ * universe, and the
+ * rest important big
+ * comment
+ */
+ /* insert " about life, the universe, and the rest" after "serious" */
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ set wm&
+ enew! | close
+endfunc
+
+func Test_cindent_3()
+ new
+ setl nocindent ts=4 sw=4
+
+ let code =<< trim [CODE]
+ {
+ /*
+ * Testing for comments, without 'cin' set
+ */
+
+ /*
+ * what happens here?
+ */
+
+ /*
+ the end of the comment, try inserting a line below */
+
+ /* how about
+ this one */
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('comments')
+ normal joabout life
+ call search('happens')
+ normal jothere
+ call search('below')
+ normal oline
+ call search('this')
+ normal Ohello
+
+ let expected =<< trim [CODE]
+ {
+ /*
+ * Testing for comments, without 'cin' set
+ */
+ about life
+
+ /*
+ * what happens here?
+ */
+ there
+
+ /*
+ the end of the comment, try inserting a line below */
+ line
+
+ /* how about
+ hello
+ this one */
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_4()
+ new
+ setl cindent ts=4 sw=4
+
+ let code =<< trim [CODE]
+ {
+ var = this + that + vec[0] * vec[0]
+ + vec[1] * vec[1]
+ + vec2[2] * vec[2];
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('vec2')
+ normal ==
+
+ let expected =<< trim [CODE]
+ {
+ var = this + that + vec[0] * vec[0]
+ + vec[1] * vec[1]
+ + vec2[2] * vec[2];
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_5()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=}4
+
+ let code =<< trim [CODE]
+ {
+ asdf asdflkajds f;
+ if (tes & ting) {
+ asdf asdf asdf ;
+ asdfa sdf asdf;
+ }
+ testing1;
+ if (tes & ting)
+ {
+ asdf asdf asdf ;
+ asdfa sdf asdf;
+ }
+ testing2;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('testing1')
+ exe "normal k2==/testing2\<CR>"
+ normal k2==
+
+ let expected =<< trim [CODE]
+ {
+ asdf asdflkajds f;
+ if (tes & ting) {
+ asdf asdf asdf ;
+ asdfa sdf asdf;
+ }
+ testing1;
+ if (tes & ting)
+ {
+ asdf asdf asdf ;
+ asdfa sdf asdf;
+ }
+ testing2;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_6()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(0,)20
+
+ let code =<< trim [CODE]
+ main ( int first_par, /*
+ * Comment for
+ * first par
+ */
+ int second_par /*
+ * Comment for
+ * second par
+ */
+ )
+ {
+ func( first_par, /*
+ * Comment for
+ * first par
+ */
+ second_par /*
+ * Comment for
+ * second par
+ */
+ );
+
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('main')
+ normal =][
+
+ let expected =<< trim [CODE]
+ main ( int first_par, /*
+ * Comment for
+ * first par
+ */
+ int second_par /*
+ * Comment for
+ * second par
+ */
+ )
+ {
+ func( first_par, /*
+ * Comment for
+ * first par
+ */
+ second_par /*
+ * Comment for
+ * second par
+ */
+ );
+
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_7()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=es,n0s
+
+ let code =<< trim [CODE]
+ main(void)
+ {
+ /* Make sure that cino=X0s is not parsed like cino=Xs. */
+ if (cond)
+ foo();
+ else
+ {
+ bar();
+ }
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('main')
+ normal =][
+
+ let expected =<< trim [CODE]
+ main(void)
+ {
+ /* Make sure that cino=X0s is not parsed like cino=Xs. */
+ if (cond)
+ foo();
+ else
+ {
+ bar();
+ }
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_8()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=
+
+ let code =<< trim [CODE]
+
+ {
+ do
+ {
+ if ()
+ {
+ if ()
+ asdf;
+ else
+ asdf;
+ }
+ } while ();
+ cmd; /* this should go under the } */
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+
+ {
+ do
+ {
+ if ()
+ {
+ if ()
+ asdf;
+ else
+ asdf;
+ }
+ } while ();
+ cmd; /* this should go under the } */
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_9()
+ new
+ setl cindent ts=4 sw=4
+
+ let code =<< trim [CODE]
+
+ void f()
+ {
+ if ( k() ) {
+ l();
+
+ } else { /* Start (two words) end */
+ m();
+ }
+
+ n();
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+
+ void f()
+ {
+ if ( k() ) {
+ l();
+
+ } else { /* Start (two words) end */
+ m();
+ }
+
+ n();
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_10()
+ new
+ setl cindent ts=4 sw=4
+ setl cino={s,e-s
+
+ let code =<< trim [CODE]
+
+ void f()
+ {
+ if ( k() )
+ {
+ l();
+ } else { /* Start (two words) end */
+ m();
+ }
+ n(); /* should be under the if () */
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+
+ void f()
+ {
+ if ( k() )
+ {
+ l();
+ } else { /* Start (two words) end */
+ m();
+ }
+ n(); /* should be under the if () */
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_11()
+ new
+ setl cindent ts=4 sw=4
+ setl cino={s,fs
+
+ let code =<< trim [CODE]
+ void bar(void)
+ {
+ static array[2][2] =
+ {
+ { 1, 2 },
+ { 3, 4 },
+ }
+
+ while (a)
+ {
+ foo(&a);
+ }
+
+ {
+ int a;
+ {
+ a = a + 1;
+ }
+ }
+ b = a;
+ }
+
+ void func(void)
+ {
+ a = 1;
+ {
+ b = 2;
+ }
+ c = 3;
+ d = 4;
+ }
+ /* foo */
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ exe "normal ]]=/ foo\<CR>"
+
+ let expected =<< trim [CODE]
+ void bar(void)
+ {
+ static array[2][2] =
+ {
+ { 1, 2 },
+ { 3, 4 },
+ }
+
+ while (a)
+ {
+ foo(&a);
+ }
+
+ {
+ int a;
+ {
+ a = a + 1;
+ }
+ }
+ b = a;
+ }
+
+ void func(void)
+ {
+ a = 1;
+ {
+ b = 2;
+ }
+ c = 3;
+ d = 4;
+ }
+ /* foo */
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_12()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=
+
+ let code =<< trim [CODE]
+ a()
+ {
+ do {
+ a = a +
+ a;
+ } while ( a ); /* add text under this line */
+ if ( a )
+ a;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('while')
+ normal ohere
+
+ let expected =<< trim [CODE]
+ a()
+ {
+ do {
+ a = a +
+ a;
+ } while ( a ); /* add text under this line */
+ here
+ if ( a )
+ a;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_13()
+ new
+ setl cindent ts=4 sw=4
+ setl cino= com=
+
+ let code =<< trim [CODE]
+ a()
+ {
+ label1:
+ /* hmm */
+ // comment
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('comment')
+ exe "normal olabel2: b();\rlabel3 /* post */:\r/* pre */ label4:\r" .
+ \ "f(/*com*/);\rif (/*com*/)\rcmd();"
+
+ let expected =<< trim [CODE]
+ a()
+ {
+ label1:
+ /* hmm */
+ // comment
+ label2: b();
+ label3 /* post */:
+ /* pre */ label4:
+ f(/*com*/);
+ if (/*com*/)
+ cmd();
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_14()
+ new
+ setl cindent ts=4 sw=4
+ setl comments& comments^=s:/*,m:**,ex:*/
+
+ let code =<< trim [CODE]
+ /*
+ * A simple comment
+ */
+
+ /*
+ ** A different comment
+ */
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('simple')
+ normal =5j
+
+ let expected =<< trim [CODE]
+ /*
+ * A simple comment
+ */
+
+ /*
+ ** A different comment
+ */
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_15()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=c0
+ setl comments& comments-=s1:/* comments^=s0:/*
+
+ let code =<< trim [CODE]
+ void f()
+ {
+
+ /*********
+ A comment.
+ *********/
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+
+ /*********
+ A comment.
+ *********/
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_16()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=c0,C1
+ setl comments& comments-=s1:/* comments^=s0:/*
+
+ let code =<< trim [CODE]
+ void f()
+ {
+
+ /*********
+ A comment.
+ *********/
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+
+ /*********
+ A comment.
+ *********/
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_17()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=
+
+ let code =<< trim [CODE]
+ void f()
+ {
+ c = c1 &&
+ (
+ c2 ||
+ c3
+ ) && c4;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+ c = c1 &&
+ (
+ c2 ||
+ c3
+ ) && c4;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_18()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(s
+
+ let code =<< trim [CODE]
+ void f()
+ {
+ c = c1 &&
+ (
+ c2 ||
+ c3
+ ) && c4;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+ c = c1 &&
+ (
+ c2 ||
+ c3
+ ) && c4;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_19()
+ new
+ setl cindent ts=4 sw=4
+ set cino=(s,U1
+
+ let code =<< trim [CODE]
+ void f()
+ {
+ c = c1 &&
+ (
+ c2 ||
+ c3
+ ) && c4;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+ c = c1 &&
+ (
+ c2 ||
+ c3
+ ) && c4;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_20()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(0
+
+ let code =<< trim [CODE]
+ void f()
+ {
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_21()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(0,w1
+
+ let code =<< trim [CODE]
+ void f()
+ {
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_22()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(s
+
+ let code =<< trim [CODE]
+ void f()
+ {
+ c = c1 && (
+ c2 ||
+ c3
+ ) && c4;
+ if (
+ c1 && c2
+ )
+ foo;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+ c = c1 && (
+ c2 ||
+ c3
+ ) && c4;
+ if (
+ c1 && c2
+ )
+ foo;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_23()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(s,m1
+
+ let code =<< trim [CODE]
+ void f()
+ {
+ c = c1 && (
+ c2 ||
+ c3
+ ) && c4;
+ if (
+ c1 && c2
+ )
+ foo;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+ c = c1 && (
+ c2 ||
+ c3
+ ) && c4;
+ if (
+ c1 && c2
+ )
+ foo;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_24()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=b1
+
+ let code =<< trim [CODE]
+ void f()
+ {
+ switch (x)
+ {
+ case 1:
+ a = b;
+ break;
+ default:
+ a = 0;
+ break;
+ }
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+ switch (x)
+ {
+ case 1:
+ a = b;
+ break;
+ default:
+ a = 0;
+ break;
+ }
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_25()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(0,W5
+
+ let code =<< trim [CODE]
+ void f()
+ {
+ invokeme(
+ argu,
+ ment);
+ invokeme(
+ argu,
+ ment
+ );
+ invokeme(argu,
+ ment
+ );
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+ invokeme(
+ argu,
+ ment);
+ invokeme(
+ argu,
+ ment
+ );
+ invokeme(argu,
+ ment
+ );
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_26()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=/6
+
+ let code =<< trim [CODE]
+ void f()
+ {
+ statement;
+ // comment 1
+ // comment 2
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+ statement;
+ // comment 1
+ // comment 2
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_27()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=
+
+ let code =<< trim [CODE]
+ void f()
+ {
+ statement;
+ // comment 1
+ // comment 2
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ exe "normal ]]/comment 1/+1\<CR>=="
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+ statement;
+ // comment 1
+ // comment 2
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_28()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=g0
+
+ let code =<< trim [CODE]
+ class CAbc
+ {
+ int Test() { return FALSE; }
+
+ public: // comment
+ void testfall();
+ protected:
+ void testfall();
+ };
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ class CAbc
+ {
+ int Test() { return FALSE; }
+
+ public: // comment
+ void testfall();
+ protected:
+ void testfall();
+ };
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_29()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(0,gs,hs
+
+ let code =<< trim [CODE]
+ class Foo : public Bar
+ {
+ public:
+ virtual void method1(void) = 0;
+ virtual void method2(int arg1,
+ int arg2,
+ int arg3) = 0;
+ };
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ class Foo : public Bar
+ {
+ public:
+ virtual void method1(void) = 0;
+ virtual void method2(int arg1,
+ int arg2,
+ int arg3) = 0;
+ };
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_30()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=+20
+
+ let code =<< [CODE]
+ void
+foo()
+{
+ if (a)
+ {
+ } else
+ asdf;
+}
+[CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< [CODE]
+ void
+foo()
+{
+ if (a)
+ {
+ } else
+ asdf;
+}
+
+[CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_31()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(0,W2s
+
+ let code =<< trim [CODE]
+
+ {
+ averylongfunctionnamelongfunctionnameaverylongfunctionname()->asd(
+ asdasdf,
+ func(asdf,
+ asdfadsf),
+ asdfasdf
+ );
+
+ /* those are ugly, but consequent */
+
+ func()->asd(asdasdf,
+ averylongfunctionname(
+ abc,
+ dec)->averylongfunctionname(
+ asdfadsf,
+ asdfasdf,
+ asdfasdf,
+ ),
+ func(asdfadf,
+ asdfasdf
+ ),
+ asdasdf
+ );
+
+ averylongfunctionnameaverylongfunctionnameavery()->asd(fasdf(
+ abc,
+ dec)->asdfasdfasdf(
+ asdfadsf,
+ asdfasdf,
+ asdfasdf,
+ ),
+ func(asdfadf,
+ asdfasdf),
+ asdasdf
+ );
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+
+ {
+ averylongfunctionnamelongfunctionnameaverylongfunctionname()->asd(
+ asdasdf,
+ func(asdf,
+ asdfadsf),
+ asdfasdf
+ );
+
+ /* those are ugly, but consequent */
+
+ func()->asd(asdasdf,
+ averylongfunctionname(
+ abc,
+ dec)->averylongfunctionname(
+ asdfadsf,
+ asdfasdf,
+ asdfasdf,
+ ),
+ func(asdfadf,
+ asdfasdf
+ ),
+ asdasdf
+ );
+
+ averylongfunctionnameaverylongfunctionnameavery()->asd(fasdf(
+ abc,
+ dec)->asdfasdfasdf(
+ asdfadsf,
+ asdfasdf,
+ asdfasdf,
+ ),
+ func(asdfadf,
+ asdfasdf),
+ asdasdf
+ );
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_32()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=M1
+
+ let code =<< trim [CODE]
+ int main ()
+ {
+ if (cond1 &&
+ cond2
+ )
+ foo;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ int main ()
+ {
+ if (cond1 &&
+ cond2
+ )
+ foo;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_33()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(0,ts
+
+ let code =<< trim [CODE]
+ void func(int a
+ #if defined(FOO)
+ , int b
+ , int c
+ #endif
+ )
+ {
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal 2j=][
+
+ let expected =<< trim [CODE]
+ void func(int a
+ #if defined(FOO)
+ , int b
+ , int c
+ #endif
+ )
+ {
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_34()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(0
+
+ let code =<< trim [CODE]
+
+ void
+ func(int a
+ #if defined(FOO)
+ , int b
+ , int c
+ #endif
+ )
+ {
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal =][
+
+ let expected =<< trim [CODE]
+
+ void
+ func(int a
+ #if defined(FOO)
+ , int b
+ , int c
+ #endif
+ )
+ {
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_35()
+ new
+ setl cindent ts=4 sw=4
+ setl cino&
+
+ let code =<< trim [CODE]
+ void func(void)
+ {
+ if(x==y)
+ if(y==z)
+ foo=1;
+ else { bar=1;
+ baz=2;
+ }
+ printf("Foo!\n");
+ }
+
+ void func1(void)
+ {
+ char* tab[] = {"foo", "bar",
+ "baz", "quux",
+ "this line used", "to be indented incorrectly"};
+ foo();
+ }
+
+ void func2(void)
+ {
+ int tab[] =
+ {1, 2,
+ 3, 4,
+ 5, 6};
+
+ printf("This line used to be indented incorrectly.\n");
+ }
+
+ int foo[]
+ #ifdef BAR
+
+ = { 1, 2, 3,
+ 4, 5, 6 }
+
+ #endif
+ ;
+ int baz;
+
+ void func3(void)
+ {
+ int tab[] = {
+ 1, 2,
+ 3, 4,
+ 5, 6};
+
+ printf("Don't you dare indent this line incorrectly!\n");
+ }
+
+ void
+ func4(a, b,
+ c)
+ int a;
+ int b;
+ int c;
+ {
+ }
+
+ void
+ func5(
+ int a,
+ int b)
+ {
+ }
+
+ void
+ func6(
+ int a)
+ {
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=7][
+
+ let expected =<< trim [CODE]
+ void func(void)
+ {
+ if(x==y)
+ if(y==z)
+ foo=1;
+ else { bar=1;
+ baz=2;
+ }
+ printf("Foo!\n");
+ }
+
+ void func1(void)
+ {
+ char* tab[] = {"foo", "bar",
+ "baz", "quux",
+ "this line used", "to be indented incorrectly"};
+ foo();
+ }
+
+ void func2(void)
+ {
+ int tab[] =
+ {1, 2,
+ 3, 4,
+ 5, 6};
+
+ printf("This line used to be indented incorrectly.\n");
+ }
+
+ int foo[]
+ #ifdef BAR
+
+ = { 1, 2, 3,
+ 4, 5, 6 }
+
+ #endif
+ ;
+ int baz;
+
+ void func3(void)
+ {
+ int tab[] = {
+ 1, 2,
+ 3, 4,
+ 5, 6};
+
+ printf("Don't you dare indent this line incorrectly!\n");
+ }
+
+ void
+ func4(a, b,
+ c)
+ int a;
+ int b;
+ int c;
+ {
+ }
+
+ void
+ func5(
+ int a,
+ int b)
+ {
+ }
+
+ void
+ func6(
+ int a)
+ {
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_36()
+ new
+ setl cindent ts=4 sw=4
+ setl cino&
+ setl cino+=l1
+
+ let code =<< trim [CODE]
+ void func(void)
+ {
+ int tab[] =
+ {
+ 1, 2, 3,
+ 4, 5, 6};
+
+ printf("Indent this line correctly!\n");
+
+ switch (foo)
+ {
+ case bar:
+ printf("bar");
+ break;
+ case baz: {
+ printf("baz");
+ break;
+ }
+ case quux:
+ printf("But don't break the indentation of this instruction\n");
+ break;
+ }
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void func(void)
+ {
+ int tab[] =
+ {
+ 1, 2, 3,
+ 4, 5, 6};
+
+ printf("Indent this line correctly!\n");
+
+ switch (foo)
+ {
+ case bar:
+ printf("bar");
+ break;
+ case baz: {
+ printf("baz");
+ break;
+ }
+ case quux:
+ printf("But don't break the indentation of this instruction\n");
+ break;
+ }
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_37()
+ new
+ setl cindent ts=4 sw=4
+ setl cino&
+
+ let code =<< trim [CODE]
+ void func(void)
+ {
+ cout << "a"
+ << "b"
+ << ") :"
+ << "c";
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void func(void)
+ {
+ cout << "a"
+ << "b"
+ << ") :"
+ << "c";
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_38()
+ new
+ setl cindent ts=4 sw=4
+ setl com=s1:/*,m:*,ex:*/
+
+ let code =<< trim [CODE]
+ void func(void)
+ {
+ /*
+ * This is a comment.
+ */
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]3jofoo();
+
+ let expected =<< trim [CODE]
+ void func(void)
+ {
+ /*
+ * This is a comment.
+ */
+ foo();
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_39()
+ new
+ setl cindent ts=4 sw=4
+ setl cino&
+
+ let code =<< trim [CODE]
+ void func(void)
+ {
+ for (int i = 0; i < 10; ++i)
+ if (i & 1) {
+ foo(1);
+ } else
+ foo(0);
+ baz();
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void func(void)
+ {
+ for (int i = 0; i < 10; ++i)
+ if (i & 1) {
+ foo(1);
+ } else
+ foo(0);
+ baz();
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_40()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=k2s,(0
+
+ let code =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ func( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ func( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_41()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=k2s,(s
+
+ let code =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ func( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ func( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_42()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=k2s,(s,U1
+
+ let code =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ if (c123456789
+ && (c22345
+ || c3))
+ printf("foo\n");
+
+ c = c1 &&
+ (
+ c2 ||
+ c3
+ ) && c4;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ if (c123456789
+ && (c22345
+ || c3))
+ printf("foo\n");
+
+ c = c1 &&
+ (
+ c2 ||
+ c3
+ ) && c4;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_43()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=k2s,(0,W4
+
+ let code =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ if (c123456789
+ && (c22345
+ || c3))
+ printf("foo\n");
+
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+
+ a_long_line(
+ argument,
+ argument);
+ a_short_line(argument,
+ argument);
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ if (c123456789
+ && (c22345
+ || c3))
+ printf("foo\n");
+
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+
+ a_long_line(
+ argument,
+ argument);
+ a_short_line(argument,
+ argument);
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_44()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=k2s,u2
+
+ let code =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ if (c123456789
+ && (c22345
+ || c3))
+ printf("foo\n");
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ if (c123456789
+ && (c22345
+ || c3))
+ printf("foo\n");
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_45()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=k2s,(0,w1
+
+ let code =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ if (c123456789
+ && (c22345
+ || c3))
+ printf("foo\n");
+
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ func( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ if (c123456789
+ && (c22345
+ || c3))
+ printf("foo\n");
+
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ func( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_46()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=k2,(s
+
+ let code =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_47()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=N-s
+
+ let code =<< trim [CODE]
+ NAMESPACESTART
+ /* valid namespaces with normal indent */
+ namespace
+ {
+ {
+ 111111111111;
+ }
+ }
+ namespace /* test */
+ {
+ 11111111111111111;
+ }
+ namespace // test
+ {
+ 111111111111111111;
+ }
+ namespace
+ {
+ 111111111111111111;
+ }
+ namespace test
+ {
+ 111111111111111111;
+ }
+ namespace test::cpp17
+ {
+ 111111111111111111;
+ }
+ namespace ::incorrectcpp17
+ {
+ 111111111111111111;
+ }
+ namespace test::incorrectcpp17::
+ {
+ 111111111111111111;
+ }
+ namespace test:incorrectcpp17
+ {
+ 111111111111111111;
+ }
+ namespace test:::incorrectcpp17
+ {
+ 111111111111111111;
+ }
+ namespace{
+ 111111111111111111;
+ }
+ namespace test{
+ 111111111111111111;
+ }
+ namespace {
+ 111111111111111111;
+ }
+ namespace test {
+ 111111111111111111;
+ namespace test2 {
+ 22222222222222222;
+ }
+ }
+ inline namespace {
+ 111111111111111111;
+ }
+ inline /* test */ namespace {
+ 111111111111111111;
+ }
+ inline/* test */namespace {
+ 111111111111111111;
+ }
+
+ /* invalid namespaces use block indent */
+ namespace test test2 {
+ 111111111111111111111;
+ }
+ namespace11111111111 {
+ 111111111111;
+ }
+ namespace() {
+ 1111111111111;
+ }
+ namespace()
+ {
+ 111111111111111111;
+ }
+ namespace test test2
+ {
+ 1111111111111111111;
+ }
+ namespace111111111
+ {
+ 111111111111111111;
+ }
+ inlinenamespace {
+ 111111111111111111;
+ }
+ NAMESPACEEND
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('^NAMESPACESTART')
+ exe "normal =/^NAMESPACEEND\n"
+
+ let expected =<< trim [CODE]
+ NAMESPACESTART
+ /* valid namespaces with normal indent */
+ namespace
+ {
+ {
+ 111111111111;
+ }
+ }
+ namespace /* test */
+ {
+ 11111111111111111;
+ }
+ namespace // test
+ {
+ 111111111111111111;
+ }
+ namespace
+ {
+ 111111111111111111;
+ }
+ namespace test
+ {
+ 111111111111111111;
+ }
+ namespace test::cpp17
+ {
+ 111111111111111111;
+ }
+ namespace ::incorrectcpp17
+ {
+ 111111111111111111;
+ }
+ namespace test::incorrectcpp17::
+ {
+ 111111111111111111;
+ }
+ namespace test:incorrectcpp17
+ {
+ 111111111111111111;
+ }
+ namespace test:::incorrectcpp17
+ {
+ 111111111111111111;
+ }
+ namespace{
+ 111111111111111111;
+ }
+ namespace test{
+ 111111111111111111;
+ }
+ namespace {
+ 111111111111111111;
+ }
+ namespace test {
+ 111111111111111111;
+ namespace test2 {
+ 22222222222222222;
+ }
+ }
+ inline namespace {
+ 111111111111111111;
+ }
+ inline /* test */ namespace {
+ 111111111111111111;
+ }
+ inline/* test */namespace {
+ 111111111111111111;
+ }
+
+ /* invalid namespaces use block indent */
+ namespace test test2 {
+ 111111111111111111111;
+ }
+ namespace11111111111 {
+ 111111111111;
+ }
+ namespace() {
+ 1111111111111;
+ }
+ namespace()
+ {
+ 111111111111111111;
+ }
+ namespace test test2
+ {
+ 1111111111111111111;
+ }
+ namespace111111111
+ {
+ 111111111111111111;
+ }
+ inlinenamespace {
+ 111111111111111111;
+ }
+ NAMESPACEEND
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_48()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=j1,J1
+
+ let code =<< trim [CODE]
+ JSSTART
+ var bar = {
+ foo: {
+ that: this,
+ some: ok,
+ },
+ "bar":{
+ a : 2,
+ b: "123abc",
+ x: 4,
+ "y": 5
+ }
+ }
+ JSEND
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('^JSSTART')
+ exe "normal =/^JSEND\n"
+
+ let expected =<< trim [CODE]
+ JSSTART
+ var bar = {
+ foo: {
+ that: this,
+ some: ok,
+ },
+ "bar":{
+ a : 2,
+ b: "123abc",
+ x: 4,
+ "y": 5
+ }
+ }
+ JSEND
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_49()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=j1,J1
+
+ let code =<< trim [CODE]
+ JSSTART
+ var foo = [
+ 1,
+ 2,
+ 3
+ ];
+ JSEND
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('^JSSTART')
+ exe "normal =/^JSEND\n"
+
+ let expected =<< trim [CODE]
+ JSSTART
+ var foo = [
+ 1,
+ 2,
+ 3
+ ];
+ JSEND
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_50()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=j1,J1
+
+ let code =<< trim [CODE]
+ JSSTART
+ function bar() {
+ var foo = [
+ 1,
+ 2,
+ 3
+ ];
+ }
+ JSEND
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('^JSSTART')
+ exe "normal =/^JSEND\n"
+
+ let expected =<< trim [CODE]
+ JSSTART
+ function bar() {
+ var foo = [
+ 1,
+ 2,
+ 3
+ ];
+ }
+ JSEND
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_51()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=j1,J1
+
+ let code =<< trim [CODE]
+ JSSTART
+ (function($){
+
+ if (cond &&
+ cond) {
+ stmt;
+ }
+ window.something.left =
+ (width - 50 + offset) + "px";
+ var class_name='myclass';
+
+ function private_method() {
+ }
+
+ var public_method={
+ method: function(options,args){
+ private_method();
+ }
+ }
+
+ function init(options) {
+
+ $(this).data(class_name+'_public',$.extend({},{
+ foo: 'bar',
+ bar: 2,
+ foobar: [
+ 1,
+ 2,
+ 3
+ ],
+ callback: function(){
+ return true;
+ }
+ }, options||{}));
+ }
+
+ $.fn[class_name]=function() {
+
+ var _arguments=arguments;
+ return this.each(function(){
+
+ var options=$(this).data(class_name+'_public');
+ if (!options) {
+ init.apply(this,_arguments);
+
+ } else {
+ var method=public_method[_arguments[0]];
+
+ if (typeof(method)!='function') {
+ console.log(class_name+' has no method "'+_arguments[0]+'"');
+ return false;
+ }
+ _arguments[0]=options;
+ method.apply(this,_arguments);
+ }
+ });
+ }
+
+ })(jQuery);
+ JSEND
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('^JSSTART')
+ exe "normal =/^JSEND\n"
+
+ let expected =<< trim [CODE]
+ JSSTART
+ (function($){
+
+ if (cond &&
+ cond) {
+ stmt;
+ }
+ window.something.left =
+ (width - 50 + offset) + "px";
+ var class_name='myclass';
+
+ function private_method() {
+ }
+
+ var public_method={
+ method: function(options,args){
+ private_method();
+ }
+ }
+
+ function init(options) {
+
+ $(this).data(class_name+'_public',$.extend({},{
+ foo: 'bar',
+ bar: 2,
+ foobar: [
+ 1,
+ 2,
+ 3
+ ],
+ callback: function(){
+ return true;
+ }
+ }, options||{}));
+ }
+
+ $.fn[class_name]=function() {
+
+ var _arguments=arguments;
+ return this.each(function(){
+
+ var options=$(this).data(class_name+'_public');
+ if (!options) {
+ init.apply(this,_arguments);
+
+ } else {
+ var method=public_method[_arguments[0]];
+
+ if (typeof(method)!='function') {
+ console.log(class_name+' has no method "'+_arguments[0]+'"');
+ return false;
+ }
+ _arguments[0]=options;
+ method.apply(this,_arguments);
+ }
+ });
+ }
+
+ })(jQuery);
+ JSEND
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_52()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=j1,J1
+
+ let code =<< trim [CODE]
+ JSSTART
+ function init(options) {
+ $(this).data(class_name+'_public',$.extend({},{
+ foo: 'bar',
+ bar: 2,
+ foobar: [
+ 1,
+ 2,
+ 3
+ ],
+ callback: function(){
+ return true;
+ }
+ }, options||{}));
+ }
+ JSEND
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('^JSSTART')
+ exe "normal =/^JSEND\n"
+
+ let expected =<< trim [CODE]
+ JSSTART
+ function init(options) {
+ $(this).data(class_name+'_public',$.extend({},{
+ foo: 'bar',
+ bar: 2,
+ foobar: [
+ 1,
+ 2,
+ 3
+ ],
+ callback: function(){
+ return true;
+ }
+ }, options||{}));
+ }
+ JSEND
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_53()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=j1,J1
+
+ let code =<< trim [CODE]
+ JSSTART
+ (function($){
+ function init(options) {
+ $(this).data(class_name+'_public',$.extend({},{
+ foo: 'bar',
+ bar: 2,
+ foobar: [
+ 1,
+ 2,
+ 3
+ ],
+ callback: function(){
+ return true;
+ }
+ }, options||{}));
+ }
+ })(jQuery);
+ JSEND
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('^JSSTART')
+ exe "normal =/^JSEND\n"
+
+ let expected =<< trim [CODE]
+ JSSTART
+ (function($){
+ function init(options) {
+ $(this).data(class_name+'_public',$.extend({},{
+ foo: 'bar',
+ bar: 2,
+ foobar: [
+ 1,
+ 2,
+ 3
+ ],
+ callback: function(){
+ return true;
+ }
+ }, options||{}));
+ }
+ })(jQuery);
+ JSEND
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_54()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=j1,J1,+2
+
+ let code =<< trim [CODE]
+ JSSTART
+ // Results of JavaScript indent
+ // 1
+ (function(){
+ var a = [
+ 'a',
+ 'b',
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i'
+ ];
+ }())
+
+ // 2
+ (function(){
+ var a = [
+ 0 +
+ 5 *
+ 9 *
+ 'a',
+ 'b',
+ 0 +
+ 5 *
+ 9 *
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i'
+ ];
+ }())
+
+ // 3
+ (function(){
+ var a = [
+ 0 +
+ // comment 1
+ 5 *
+ /* comment 2 */
+ 9 *
+ 'a',
+ 'b',
+ 0 +
+ 5 *
+ 9 *
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i'
+ ];
+ }())
+
+ // 4
+ {
+ var a = [
+ 0,
+ 1
+ ];
+ var b;
+ var c;
+ }
+
+ // 5
+ {
+ var a = [
+ [
+ 0
+ ],
+ 2,
+ 3
+ ];
+ }
+
+ // 6
+ {
+ var a = [
+ [
+ 0,
+ 1
+ ],
+ 2,
+ 3
+ ];
+ }
+
+ // 7
+ {
+ var a = [
+ // [
+ 0,
+ // 1
+ // ],
+ 2,
+ 3
+ ];
+ }
+
+ // 8
+ var x = [
+ (function(){
+ var a,
+ b,
+ c,
+ d,
+ e,
+ f,
+ g,
+ h,
+ i;
+ })
+ ];
+
+ // 9
+ var a = [
+ 0 +
+ 5 *
+ 9 *
+ 'a',
+ 'b',
+ 0 +
+ 5 *
+ 9 *
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i'
+ ];
+
+ // 10
+ var a,
+ b,
+ c,
+ d,
+ e,
+ f,
+ g,
+ h,
+ i;
+ JSEND
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('^JSSTART')
+ exe "normal =/^JSEND\n"
+
+ let expected =<< trim [CODE]
+ JSSTART
+ // Results of JavaScript indent
+ // 1
+ (function(){
+ var a = [
+ 'a',
+ 'b',
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i'
+ ];
+ }())
+
+ // 2
+ (function(){
+ var a = [
+ 0 +
+ 5 *
+ 9 *
+ 'a',
+ 'b',
+ 0 +
+ 5 *
+ 9 *
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i'
+ ];
+ }())
+
+ // 3
+ (function(){
+ var a = [
+ 0 +
+ // comment 1
+ 5 *
+ /* comment 2 */
+ 9 *
+ 'a',
+ 'b',
+ 0 +
+ 5 *
+ 9 *
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i'
+ ];
+ }())
+
+ // 4
+ {
+ var a = [
+ 0,
+ 1
+ ];
+ var b;
+ var c;
+ }
+
+ // 5
+ {
+ var a = [
+ [
+ 0
+ ],
+ 2,
+ 3
+ ];
+ }
+
+ // 6
+ {
+ var a = [
+ [
+ 0,
+ 1
+ ],
+ 2,
+ 3
+ ];
+ }
+
+ // 7
+ {
+ var a = [
+ // [
+ 0,
+ // 1
+ // ],
+ 2,
+ 3
+ ];
+ }
+
+ // 8
+ var x = [
+ (function(){
+ var a,
+ b,
+ c,
+ d,
+ e,
+ f,
+ g,
+ h,
+ i;
+ })
+ ];
+
+ // 9
+ var a = [
+ 0 +
+ 5 *
+ 9 *
+ 'a',
+ 'b',
+ 0 +
+ 5 *
+ 9 *
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i'
+ ];
+
+ // 10
+ var a,
+ b,
+ c,
+ d,
+ e,
+ f,
+ g,
+ h,
+ i;
+ JSEND
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_55()
+ new
+ setl cindent ts=4 sw=4
+ setl cino&
+
+ let code =<< trim [CODE]
+ /* start of define */
+ {
+ }
+ #define AAA \
+ BBB\
+ CCC
+
+ #define CNT \
+ 1 + \
+ 2 + \
+ 4
+ /* end of define */
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('start of define')
+ exe "normal =/end of define\n"
+
+ let expected =<< trim [CODE]
+ /* start of define */
+ {
+ }
+ #define AAA \
+ BBB\
+ CCC
+
+ #define CNT \
+ 1 + \
+ 2 + \
+ 4
+ /* end of define */
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_56()
+ new
+ setl cindent ts=4 sw=4
+ setl cino&
+
+ let code =<< trim [CODE]
+ {
+ a = second/*bug*/*line;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('a = second')
+ normal ox
+
+ let expected =<< trim [CODE]
+ {
+ a = second/*bug*/*line;
+ x
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
" this was going beyond the end of the line.
func Test_cindent_case()
new
@@ -173,4 +5344,23 @@ func Test_cindent_pragma()
enew! | close
endfunc
+func Test_backslash_at_end_of_line()
+ new
+ exe "norm v>O'\\\<C-m>-"
+ exe "norm \<C-q>="
+ bwipe!
+endfunc
+
+func Test_find_brace_backwards()
+ " this was looking beyond the end of the line
+ new
+ norm R/*
+ norm o0{
+ norm o//
+ norm V{=
+ call assert_equal(['/*', ' 0{', '//'], getline(1, 3))
+ bwipe!
+endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim
index 1672b0e840..ff4cbe544c 100644
--- a/src/nvim/testdir/test_cmdline.vim
+++ b/src/nvim/testdir/test_cmdline.vim
@@ -500,8 +500,16 @@ func Test_fullcommand()
for [in, want] in items(tests)
call assert_equal(want, fullcommand(in))
endfor
+ call assert_equal('', fullcommand(v:_null_string))
call assert_equal('syntax', 'syn'->fullcommand())
+
+ command -buffer BufferLocalCommand :
+ command GlobalCommand :
+ call assert_equal('GlobalCommand', fullcommand('GlobalCom'))
+ call assert_equal('BufferLocalCommand', fullcommand('BufferL'))
+ delcommand BufferLocalCommand
+ delcommand GlobalCommand
endfunc
func Test_shellcmd_completion()
diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim
index aaa2301bca..c0c572ce65 100644
--- a/src/nvim/testdir/test_compiler.vim
+++ b/src/nvim/testdir/test_compiler.vim
@@ -19,6 +19,9 @@ func Test_compiler()
call assert_equal('perl', b:current_compiler)
call assert_fails('let g:current_compiler', 'E121:')
+ let verbose_efm = execute('verbose set efm')
+ call assert_match('Last set from .*[/\\]compiler[/\\]perl.vim ', verbose_efm)
+
call setline(1, ['#!/usr/bin/perl -w', 'use strict;', 'my $foo=1'])
w!
call feedkeys(":make\<CR>\<CR>", 'tx')
diff --git a/src/nvim/testdir/test_conceal.vim b/src/nvim/testdir/test_conceal.vim
index 1306dbe5cf..bffc2f49d3 100644
--- a/src/nvim/testdir/test_conceal.vim
+++ b/src/nvim/testdir/test_conceal.vim
@@ -4,10 +4,10 @@ source check.vim
CheckFeature conceal
source screendump.vim
-" CheckScreendump
func Test_conceal_two_windows()
CheckScreendump
+
let code =<< trim [CODE]
let lines = ["one one one one one", "two |hidden| here", "three |hidden| three"]
call setline(1, lines)
@@ -111,6 +111,7 @@ endfunc
func Test_conceal_with_cursorline()
CheckScreendump
+
" Opens a help window, where 'conceal' is set, switches to the other window
" where 'cursorline' needs to be updated when the cursor moves.
let code =<< trim [CODE]
@@ -139,6 +140,7 @@ endfunc
func Test_conceal_resize_term()
CheckScreendump
+
let code =<< trim [CODE]
call setline(1, '`one` `two` `three` `four` `five`, the backticks should be concealed')
setl cocu=n cole=3
diff --git a/src/nvim/testdir/test_cscope.vim b/src/nvim/testdir/test_cscope.vim
index cc6154af69..faf37485cd 100644
--- a/src/nvim/testdir/test_cscope.vim
+++ b/src/nvim/testdir/test_cscope.vim
@@ -102,7 +102,7 @@ func Test_cscopeWithCscopeConnections()
for cmd in ['cs find f Xmemfile_test.c', 'cs find 7 Xmemfile_test.c']
enew
let a = execute(cmd)
- call assert_true(a =~ '"Xmemfile_test.c" \d\+L, \d\+C')
+ call assert_true(a =~ '"Xmemfile_test.c" \d\+L, \d\+B')
call assert_equal('Xmemfile_test.c', @%)
endfor
@@ -112,7 +112,7 @@ func Test_cscopeWithCscopeConnections()
let a = execute(cmd)
let alines = split(a, '\n', 1)
call assert_equal('', alines[0])
- call assert_true(alines[1] =~ '"Xmemfile_test.c" \d\+L, \d\+C')
+ call assert_true(alines[1] =~ '"Xmemfile_test.c" \d\+L, \d\+B')
call assert_equal('(1 of 1): <<global>> #include <assert.h>', alines[2])
call assert_equal('#include <assert.h>', getline('.'))
endfor
diff --git a/src/nvim/testdir/test_cursor_func.vim b/src/nvim/testdir/test_cursor_func.vim
index e8c4a952ee..57825b4551 100644
--- a/src/nvim/testdir/test_cursor_func.vim
+++ b/src/nvim/testdir/test_cursor_func.vim
@@ -1,4 +1,4 @@
-" Tests for cursor().
+" Tests for cursor() and other functions that get/set the cursor position
func Test_wrong_arguments()
call assert_fails('call cursor(1. 3)', 'E474:')
@@ -24,6 +24,9 @@ func Test_move_cursor()
" below last line goes to last line
call cursor(9, 1)
call assert_equal([4, 1, 0, 1], getcurpos()[1:])
+ " pass string arguments
+ call cursor('3', '3')
+ call assert_equal([3, 3, 0, 3], getcurpos()[1:])
call setline(1, ["\<TAB>"])
call cursor(1, 1, 1)
@@ -72,7 +75,6 @@ func Test_curswant_with_cursorline()
endfunc
func Test_screenpos()
- throw 'skipped: TODO: '
rightbelow new
rightbelow 20vsplit
call setline(1, ["\tsome text", "long wrapping line here", "next line"])
@@ -100,9 +102,10 @@ func Test_screenpos()
bwipe!
call assert_equal({'col': 1, 'row': 1, 'endcol': 1, 'curscol': 1}, screenpos(win_getid(), 1, 1))
- nmenu WinBar.TEST :
- call assert_equal({'col': 1, 'row': 2, 'endcol': 1, 'curscol': 1}, screenpos(win_getid(), 1, 1))
- nunmenu WinBar.TEST
+ " Needs WinBar
+ " nmenu WinBar.TEST :
+ " call assert_equal({'col': 1, 'row': 2, 'endcol': 1, 'curscol': 1}, screenpos(win_getid(), 1, 1))
+ " nunmenu WinBar.TEST
endfunc
func Test_screenpos_number()
@@ -119,3 +122,247 @@ func Test_screenpos_number()
close
bwipe!
endfunc
+
+" Save the visual start character position
+func SaveVisualStartCharPos()
+ call add(g:VisualStartPos, getcharpos('v'))
+ return ''
+endfunc
+
+" Save the current cursor character position in insert mode
+func SaveInsertCurrentCharPos()
+ call add(g:InsertCurrentPos, getcharpos('.'))
+ return ''
+endfunc
+
+" Test for the getcharpos() function
+func Test_getcharpos()
+ call assert_fails('call getcharpos({})', 'E731:')
+ call assert_equal([0, 0, 0, 0], getcharpos(0))
+ new
+ call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678'])
+
+ " Test for '.' and '$'
+ normal 1G
+ call assert_equal([0, 1, 1, 0], getcharpos('.'))
+ call assert_equal([0, 4, 1, 0], getcharpos('$'))
+ normal 2G6l
+ call assert_equal([0, 2, 7, 0], getcharpos('.'))
+ normal 3G$
+ call assert_equal([0, 3, 1, 0], getcharpos('.'))
+ normal 4G$
+ call assert_equal([0, 4, 9, 0], getcharpos('.'))
+
+ " Test for a mark
+ normal 2G7lmmgg
+ call assert_equal([0, 2, 8, 0], getcharpos("'m"))
+ delmarks m
+ call assert_equal([0, 0, 0, 0], getcharpos("'m"))
+
+ " Test for the visual start column
+ vnoremap <expr> <F3> SaveVisualStartCharPos()
+ let g:VisualStartPos = []
+ exe "normal 2G6lv$\<F3>ohh\<F3>o\<F3>"
+ call assert_equal([[0, 2, 7, 0], [0, 2, 10, 0], [0, 2, 5, 0]], g:VisualStartPos)
+ call assert_equal([0, 2, 9, 0], getcharpos('v'))
+ let g:VisualStartPos = []
+ exe "normal 3Gv$\<F3>o\<F3>"
+ call assert_equal([[0, 3, 1, 0], [0, 3, 2, 0]], g:VisualStartPos)
+ let g:VisualStartPos = []
+ exe "normal 1Gv$\<F3>o\<F3>"
+ call assert_equal([[0, 1, 1, 0], [0, 1, 1, 0]], g:VisualStartPos)
+ vunmap <F3>
+
+ " Test for getting the position in insert mode with the cursor after the
+ " last character in a line
+ inoremap <expr> <F3> SaveInsertCurrentCharPos()
+ let g:InsertCurrentPos = []
+ exe "normal 1GA\<F3>"
+ exe "normal 2GA\<F3>"
+ exe "normal 3GA\<F3>"
+ exe "normal 4GA\<F3>"
+ exe "normal 2G6li\<F3>"
+ call assert_equal([[0, 1, 1, 0], [0, 2, 10, 0], [0, 3, 2, 0], [0, 4, 10, 0],
+ \ [0, 2, 7, 0]], g:InsertCurrentPos)
+ iunmap <F3>
+
+ %bw!
+endfunc
+
+" Test for the setcharpos() function
+func Test_setcharpos()
+ call assert_equal(-1, setcharpos('.', v:_null_list))
+ new
+ call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678'])
+ call setcharpos('.', [0, 1, 1, 0])
+ call assert_equal([1, 1], [line('.'), col('.')])
+ call setcharpos('.', [0, 2, 7, 0])
+ call assert_equal([2, 9], [line('.'), col('.')])
+ call setcharpos('.', [0, 3, 4, 0])
+ call assert_equal([3, 1], [line('.'), col('.')])
+ call setcharpos('.', [0, 3, 1, 0])
+ call assert_equal([3, 1], [line('.'), col('.')])
+ call setcharpos('.', [0, 4, 0, 0])
+ call assert_equal([4, 1], [line('.'), col('.')])
+ call setcharpos('.', [0, 4, 20, 0])
+ call assert_equal([4, 9], [line('.'), col('.')])
+
+ " Test for mark
+ delmarks m
+ call setcharpos("'m", [0, 2, 9, 0])
+ normal `m
+ call assert_equal([2, 11], [line('.'), col('.')])
+ " unload the buffer and try to set the mark
+ let bnr = bufnr()
+ enew!
+ call assert_equal(-1, setcharpos("'m", [bnr, 2, 2, 0]))
+
+ %bw!
+ call assert_equal(-1, setcharpos('.', [10, 3, 1, 0]))
+endfunc
+
+func SaveVisualStartCharCol()
+ call add(g:VisualStartCol, charcol('v'))
+ return ''
+endfunc
+
+func SaveInsertCurrentCharCol()
+ call add(g:InsertCurrentCol, charcol('.'))
+ return ''
+endfunc
+
+" Test for the charcol() function
+func Test_charcol()
+ call assert_fails('call charcol({})', 'E731:')
+ call assert_equal(0, charcol(0))
+ new
+ call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678'])
+
+ " Test for '.' and '$'
+ normal 1G
+ call assert_equal(1, charcol('.'))
+ call assert_equal(1, charcol('$'))
+ normal 2G6l
+ call assert_equal(7, charcol('.'))
+ call assert_equal(10, charcol('$'))
+ normal 3G$
+ call assert_equal(1, charcol('.'))
+ call assert_equal(2, charcol('$'))
+ normal 4G$
+ call assert_equal(9, charcol('.'))
+ call assert_equal(10, charcol('$'))
+
+ " Test for [lnum, '$']
+ call assert_equal(1, charcol([1, '$']))
+ call assert_equal(10, charcol([2, '$']))
+ call assert_equal(2, charcol([3, '$']))
+ call assert_equal(0, charcol([5, '$']))
+
+ " Test for a mark
+ normal 2G7lmmgg
+ call assert_equal(8, charcol("'m"))
+ delmarks m
+ call assert_equal(0, charcol("'m"))
+
+ " Test for the visual start column
+ vnoremap <expr> <F3> SaveVisualStartCharCol()
+ let g:VisualStartCol = []
+ exe "normal 2G6lv$\<F3>ohh\<F3>o\<F3>"
+ call assert_equal([7, 10, 5], g:VisualStartCol)
+ call assert_equal(9, charcol('v'))
+ let g:VisualStartCol = []
+ exe "normal 3Gv$\<F3>o\<F3>"
+ call assert_equal([1, 2], g:VisualStartCol)
+ let g:VisualStartCol = []
+ exe "normal 1Gv$\<F3>o\<F3>"
+ call assert_equal([1, 1], g:VisualStartCol)
+ vunmap <F3>
+
+ " Test for getting the column number in insert mode with the cursor after
+ " the last character in a line
+ inoremap <expr> <F3> SaveInsertCurrentCharCol()
+ let g:InsertCurrentCol = []
+ exe "normal 1GA\<F3>"
+ exe "normal 2GA\<F3>"
+ exe "normal 3GA\<F3>"
+ exe "normal 4GA\<F3>"
+ exe "normal 2G6li\<F3>"
+ call assert_equal([1, 10, 2, 10, 7], g:InsertCurrentCol)
+ iunmap <F3>
+
+ %bw!
+endfunc
+
+func SaveInsertCursorCharPos()
+ call add(g:InsertCursorPos, getcursorcharpos('.'))
+ return ''
+endfunc
+
+" Test for getcursorcharpos()
+func Test_getcursorcharpos()
+ call assert_equal(getcursorcharpos(), getcursorcharpos(0))
+ call assert_equal([0, 0, 0, 0, 0], getcursorcharpos(-1))
+ call assert_equal([0, 0, 0, 0, 0], getcursorcharpos(1999))
+
+ new
+ call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678'])
+ normal 1G9l
+ call assert_equal([0, 1, 1, 0, 1], getcursorcharpos())
+ normal 2G9l
+ call assert_equal([0, 2, 9, 0, 14], getcursorcharpos())
+ normal 3G9l
+ call assert_equal([0, 3, 1, 0, 1], getcursorcharpos())
+ normal 4G9l
+ call assert_equal([0, 4, 9, 0, 9], getcursorcharpos())
+
+ " Test for getting the cursor position in insert mode with the cursor after
+ " the last character in a line
+ inoremap <expr> <F3> SaveInsertCursorCharPos()
+ let g:InsertCursorPos = []
+ exe "normal 1GA\<F3>"
+ exe "normal 2GA\<F3>"
+ exe "normal 3GA\<F3>"
+ exe "normal 4GA\<F3>"
+ exe "normal 2G6li\<F3>"
+ call assert_equal([[0, 1, 1, 0, 1], [0, 2, 10, 0, 15], [0, 3, 2, 0, 2],
+ \ [0, 4, 10, 0, 10], [0, 2, 7, 0, 12]], g:InsertCursorPos)
+ iunmap <F3>
+
+ let winid = win_getid()
+ normal 2G5l
+ wincmd w
+ call assert_equal([0, 2, 6, 0, 11], getcursorcharpos(winid))
+ %bw!
+endfunc
+
+" Test for setcursorcharpos()
+func Test_setcursorcharpos()
+ call assert_fails('call setcursorcharpos(v:_null_list)', 'E474:')
+ call assert_fails('call setcursorcharpos([1])', 'E474:')
+ call assert_fails('call setcursorcharpos([1, 1, 1, 1, 1])', 'E474:')
+ new
+ call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678'])
+ normal G
+ call setcursorcharpos([1, 1])
+ call assert_equal([1, 1], [line('.'), col('.')])
+ call setcursorcharpos([2, 7, 0])
+ call assert_equal([2, 9], [line('.'), col('.')])
+ call setcursorcharpos(3, 4)
+ call assert_equal([3, 1], [line('.'), col('.')])
+ call setcursorcharpos([3, 1])
+ call assert_equal([3, 1], [line('.'), col('.')])
+ call setcursorcharpos([4, 0, 0, 0])
+ call assert_equal([4, 1], [line('.'), col('.')])
+ call setcursorcharpos([4, 20])
+ call assert_equal([4, 9], [line('.'), col('.')])
+ normal 1G
+ call setcursorcharpos([100, 100, 100, 100])
+ call assert_equal([4, 9], [line('.'), col('.')])
+ normal 1G
+ call setcursorcharpos('$', 1)
+ call assert_equal([4, 1], [line('.'), col('.')])
+
+ %bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim
index 3a0c615cf6..482d39056f 100644
--- a/src/nvim/testdir/test_diffmode.vim
+++ b/src/nvim/testdir/test_diffmode.vim
@@ -1146,6 +1146,35 @@ func Test_diff_followwrap()
bwipe!
endfunc
+func Test_diff_maintains_change_mark()
+ func DiffMaintainsChangeMark()
+ enew!
+ call setline(1, ['a', 'b', 'c', 'd'])
+ diffthis
+ new
+ call setline(1, ['a', 'b', 'c', 'e'])
+ " Set '[ and '] marks
+ 2,3yank
+ call assert_equal([2, 3], [line("'["), line("']")])
+ " Verify they aren't affected by the implicit diff
+ diffthis
+ call assert_equal([2, 3], [line("'["), line("']")])
+ " Verify they aren't affected by an explicit diff
+ diffupdate
+ call assert_equal([2, 3], [line("'["), line("']")])
+ bwipe!
+ bwipe!
+ endfunc
+
+ set diffopt-=internal
+ call DiffMaintainsChangeMark()
+ set diffopt+=internal
+ call DiffMaintainsChangeMark()
+
+ set diffopt&
+ delfunc DiffMaintainsChangeMark
+endfunc
+
func Test_diff_rnu()
CheckScreendump
diff --git a/src/nvim/testdir/test_display.vim b/src/nvim/testdir/test_display.vim
index c2a9683f7c..9f74d0a38a 100644
--- a/src/nvim/testdir/test_display.vim
+++ b/src/nvim/testdir/test_display.vim
@@ -41,7 +41,7 @@ func Test_display_foldcolumn()
quit!
endfunc
-func! Test_display_foldtext_mbyte()
+func Test_display_foldtext_mbyte()
CheckFeature folding
call NewWindow(10, 40)
diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim
index fc4e80f0d6..a1f6a84a99 100644
--- a/src/nvim/testdir/test_edit.vim
+++ b/src/nvim/testdir/test_edit.vim
@@ -213,7 +213,7 @@ func Test_edit_07()
bw!
endfunc
-func! Test_edit_08()
+func Test_edit_08()
throw 'skipped: moved to test/functional/legacy/edit_spec.lua'
" reset insertmode from i_ctrl-r_=
let g:bufnr = bufnr('%')
@@ -417,7 +417,7 @@ func Test_edit_13()
bwipe!
endfunc
-func! Test_edit_CR()
+func Test_edit_CR()
" Test for <CR> in insert mode
" basically only in quickfix mode ist tested, the rest
" has been taken care of by other tests
@@ -450,7 +450,7 @@ func! Test_edit_CR()
call delete('Xqflist.txt')
endfunc
-func! Test_edit_CTRL_()
+func Test_edit_CTRL_()
" disabled for Windows builds, why?
if !has("rightleft") || has("win32")
return
@@ -734,7 +734,7 @@ func Test_edit_CTRL_O()
bw!
endfunc
-func! Test_edit_CTRL_R()
+func Test_edit_CTRL_R()
" Insert Register
new
" call test_override("ALL", 1)
@@ -1006,16 +1006,14 @@ func Test_edit_DROP()
endfunc
func Test_edit_CTRL_V()
- if has("ebcdic")
- return
- endif
new
call setline(1, ['abc'])
call cursor(2, 1)
+
" force some redraws
set showmode showcmd
- "call test_override_char_avail(1)
- " call test_override('ALL', 1)
+ " call test_override('char_avail', 1)
+
call feedkeys("A\<c-v>\<c-n>\<c-v>\<c-l>\<c-v>\<c-b>\<esc>", 'tnix')
call assert_equal(["abc\x0e\x0c\x02"], getline(1, '$'))
@@ -1028,8 +1026,19 @@ func Test_edit_CTRL_V()
set norl
endif
- " call test_override('ALL', 0)
set noshowmode showcmd
+ " call test_override('char_avail', 0)
+
+ " No modifiers should be applied to the char typed using i_CTRL-V_digit.
+ call feedkeys(":append\<CR>\<C-V>76c\<C-V>76\<C-F2>\<C-V>u3c0j\<C-V>u3c0\<M-F3>\<CR>.\<CR>", 'tnix')
+ call assert_equal('LcL<C-F2>πjπ<M-F3>', getline(2))
+
+ if has('osx')
+ " A char with a modifier should not be a valid char for i_CTRL-V_digit.
+ call feedkeys("o\<C-V>\<D-j>\<C-V>\<D-1>\<C-V>\<D-o>\<C-V>\<D-x>\<C-V>\<D-u>", 'tnix')
+ call assert_equal('<D-j><D-1><D-o><D-x><D-u>', getline(3))
+ endif
+
bw!
endfunc
@@ -1550,11 +1559,7 @@ endfunc
func Test_edit_special_chars()
new
- if has("ebcdic")
- let t = "o\<C-V>193\<C-V>xc2\<C-V>o303 \<C-V>90a\<C-V>xfg\<C-V>o578\<Esc>"
- else
- let t = "o\<C-V>65\<C-V>x42\<C-V>o103 \<C-V>33a\<C-V>xfg\<C-V>o78\<Esc>"
- endif
+ let t = "o\<C-V>65\<C-V>x42\<C-V>o103 \<C-V>33a\<C-V>xfg\<C-V>o78\<Esc>"
exe "normal " . t
call assert_equal("ABC !a\<C-O>g\<C-G>8", getline(2))
diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim
index 883ba5de3d..95eccde35c 100644
--- a/src/nvim/testdir/test_eval_stuff.vim
+++ b/src/nvim/testdir/test_eval_stuff.vim
@@ -65,11 +65,9 @@ func Test_E963()
endfunc
func Test_for_invalid()
- " Vim gives incorrect emsg here until v8.2.3284, but the exact emsg from that
- " patch cannot be used until v8.2.2658 is ported (for loop over Strings)
- call assert_fails("for x in 99", 'E897:')
- call assert_fails("for x in function('winnr')", 'E897:')
- call assert_fails("for x in {'a': 9}", 'E897:')
+ call assert_fails("for x in 99", 'E1098:')
+ call assert_fails("for x in function('winnr')", 'E1098:')
+ call assert_fails("for x in {'a': 9}", 'E1098:')
if 0
/1/5/2/s/\n
diff --git a/src/nvim/testdir/test_ex_mode.vim b/src/nvim/testdir/test_ex_mode.vim
index 92e0559618..dcec5f7cc6 100644
--- a/src/nvim/testdir/test_ex_mode.vim
+++ b/src/nvim/testdir/test_ex_mode.vim
@@ -29,12 +29,11 @@ endfunc
" Test editing line in Ex mode (both Q and gQ)
func Test_ex_mode()
- throw 'skipped: TODO: '
+ throw 'Skipped: Nvim only supports Vim Ex mode'
let encoding_save = &encoding
set sw=2
- " for e in ['utf8', 'latin1']
- for e in ['utf8']
+ for e in ['utf8', 'latin1']
exe 'set encoding=' . e
call assert_equal(['bar', 'bar'], Ex("foo bar\<C-u>bar"), e)
@@ -98,4 +97,14 @@ func Test_ex_mode_count_overflow()
call delete('Xexmodescript')
endfunc
+func Test_ex_mode_large_indent()
+ new
+ set ts=500 ai
+ call setline(1, "\t")
+ exe "normal gQi\<CR>."
+ set ts=8 noai
+ bwipe!
+endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim
index 1c053c824f..bbf8b4dfc8 100644
--- a/src/nvim/testdir/test_excmd.vim
+++ b/src/nvim/testdir/test_excmd.vim
@@ -400,3 +400,13 @@ func Test_winsize_cmd()
call assert_fails('win_getid(1)', 'E475: Invalid argument: _getid(1)')
" Actually changing the window size would be flaky.
endfunc
+
+func Test_not_break_expression_register()
+ call setreg('=', '1+1')
+ if 0
+ put =1
+ endif
+ call assert_equal('1+1', getreg('=', 1))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_exec_while_if.vim b/src/nvim/testdir/test_exec_while_if.vim
index 3da2784d77..3f13b09945 100644
--- a/src/nvim/testdir/test_exec_while_if.vim
+++ b/src/nvim/testdir/test_exec_while_if.vim
@@ -6,11 +6,7 @@ func Test_exec_while_if()
let i = 0
while i < 12
let i = i + 1
- if has("ebcdic")
- execute "normal o" . i . "\047"
- else
- execute "normal o" . i . "\033"
- endif
+ execute "normal o" . i . "\033"
if i % 2
normal Ax
if i == 9
@@ -21,21 +17,13 @@ func Test_exec_while_if()
else
let j = 9
while j > 0
- if has("ebcdic")
- execute "normal" j . "a" . j . "\x27"
- else
- execute "normal" j . "a" . j . "\x1b"
- endif
+ execute "normal" j . "a" . j . "\x1b"
let j = j - 1
endwhile
endif
endif
if i == 9
- if has("ebcdic")
- execute "normal Az\047"
- else
- execute "normal Az\033"
- endif
+ execute "normal Az\033"
endif
endwhile
unlet i j
diff --git a/src/nvim/testdir/test_execute_func.vim b/src/nvim/testdir/test_execute_func.vim
index 2cb6d73407..16cc20e9a7 100644
--- a/src/nvim/testdir/test_execute_func.vim
+++ b/src/nvim/testdir/test_execute_func.vim
@@ -147,3 +147,30 @@ func Test_win_execute_other_tab()
tabclose
unlet xyz
endfunc
+
+func Test_win_execute_visual_redraw()
+ call setline(1, ['a', 'b', 'c'])
+ new
+ wincmd p
+ " start Visual in current window, redraw in other window with fewer lines
+ call feedkeys("G\<C-V>", 'txn')
+ call win_execute(winnr('#')->win_getid(), 'redraw')
+ call feedkeys("\<Esc>", 'txn')
+ bwipe!
+ bwipe!
+
+ enew
+ new
+ call setline(1, ['a', 'b', 'c'])
+ let winid = win_getid()
+ wincmd p
+ " start Visual in current window, extend it in other window with more lines
+ call feedkeys("\<C-V>", 'txn')
+ call win_execute(winid, 'call feedkeys("G\<C-V>", ''txn'')')
+ redraw
+
+ bwipe!
+ bwipe!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_expand.vim b/src/nvim/testdir/test_expand.vim
new file mode 100644
index 0000000000..48dce25bb3
--- /dev/null
+++ b/src/nvim/testdir/test_expand.vim
@@ -0,0 +1,83 @@
+" Test for expanding file names
+
+func Test_with_directories()
+ call mkdir('Xdir1')
+ call mkdir('Xdir2')
+ call mkdir('Xdir3')
+ cd Xdir3
+ call mkdir('Xdir4')
+ cd ..
+
+ split Xdir1/file
+ call setline(1, ['a', 'b'])
+ w
+ w Xdir3/Xdir4/file
+ close
+
+ next Xdir?/*/file
+ call assert_equal('Xdir3/Xdir4/file', expand('%'))
+ if has('unix')
+ next! Xdir?/*/nofile
+ call assert_equal('Xdir?/*/nofile', expand('%'))
+ endif
+ " Edit another file, on MS-Windows the swap file would be in use and can't
+ " be deleted.
+ edit foo
+
+ call assert_equal(0, delete('Xdir1', 'rf'))
+ call assert_equal(0, delete('Xdir2', 'rf'))
+ call assert_equal(0, delete('Xdir3', 'rf'))
+endfunc
+
+func Test_with_tilde()
+ let dir = getcwd()
+ call mkdir('Xdir ~ dir')
+ call assert_true(isdirectory('Xdir ~ dir'))
+ cd Xdir\ ~\ dir
+ call assert_true(getcwd() =~ 'Xdir \~ dir')
+ call chdir(dir)
+ call delete('Xdir ~ dir', 'd')
+ call assert_false(isdirectory('Xdir ~ dir'))
+endfunc
+
+func Test_expand_tilde_filename()
+ split ~
+ call assert_equal('~', expand('%'))
+ call assert_notequal(expand('%:p'), expand('~/'))
+ call assert_match('\~', expand('%:p'))
+ bwipe!
+endfunc
+
+func Test_expandcmd()
+ let $FOO = 'Test'
+ call assert_equal('e x/Test/y', expandcmd('e x/$FOO/y'))
+ unlet $FOO
+
+ new
+ edit Xfile1
+ call assert_equal('e Xfile1', expandcmd('e %'))
+ edit Xfile2
+ edit Xfile1
+ call assert_equal('e Xfile2', 'e #'->expandcmd())
+ edit Xfile2
+ edit Xfile3
+ edit Xfile4
+ let bnum = bufnr('Xfile2')
+ call assert_equal('e Xfile2', expandcmd('e #' . bnum))
+ call setline('.', 'Vim!@#')
+ call assert_equal('e Vim', expandcmd('e <cword>'))
+ call assert_equal('e Vim!@#', expandcmd('e <cWORD>'))
+ enew!
+ edit Xfile.java
+ call assert_equal('e Xfile.py', expandcmd('e %:r.py'))
+ call assert_equal('make abc.java', expandcmd('make abc.%:e'))
+ call assert_equal('make Xabc.java', expandcmd('make %:s?file?abc?'))
+ edit a1a2a3.rb
+ call assert_equal('make b1b2b3.rb a1a2a3 Xfile.o', expandcmd('make %:gs?a?b? %< #<.o'))
+
+ call assert_fails('call expandcmd("make <afile>")', 'E495:')
+ call assert_fails('call expandcmd("make <afile>")', 'E495:')
+ enew
+ call assert_fails('call expandcmd("make %")', 'E499:')
+ close
+endfunc
diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim
index 1d7fd3e385..5b10e691e5 100644
--- a/src/nvim/testdir/test_expr.vim
+++ b/src/nvim/testdir/test_expr.vim
@@ -283,6 +283,71 @@ function Test_printf_misc()
call assert_equal('🐍', printf('%.2S', '🐍🐍'))
call assert_equal('', printf('%.1S', '🐍🐍'))
+ call assert_equal('[ あいう]', printf('[%10.6S]', 'あいうえお'))
+ call assert_equal('[ あいうえ]', printf('[%10.8S]', 'あいうえお'))
+ call assert_equal('[あいうえお]', printf('[%10.10S]', 'あいうえお'))
+ call assert_equal('[あいうえお]', printf('[%10.12S]', 'あいうえお'))
+
+ call assert_equal('あいう', printf('%S', 'あいう'))
+ call assert_equal('あいう', printf('%#S', 'あいう'))
+
+ call assert_equal('あb', printf('%2S', 'あb'))
+ call assert_equal('あb', printf('%.4S', 'あb'))
+ call assert_equal('あ', printf('%.2S', 'あb'))
+ call assert_equal(' あb', printf('%4S', 'あb'))
+ call assert_equal('0あb', printf('%04S', 'あb'))
+ call assert_equal('あb ', printf('%-4S', 'あb'))
+ call assert_equal('あ ', printf('%-4.2S', 'あb'))
+
+ call assert_equal('aい', printf('%2S', 'aい'))
+ call assert_equal('aい', printf('%.4S', 'aい'))
+ call assert_equal('a', printf('%.2S', 'aい'))
+ call assert_equal(' aい', printf('%4S', 'aい'))
+ call assert_equal('0aい', printf('%04S', 'aい'))
+ call assert_equal('aい ', printf('%-4S', 'aい'))
+ call assert_equal('a ', printf('%-4.2S', 'aい'))
+
+ call assert_equal('[あいう]', printf('[%05S]', 'あいう'))
+ call assert_equal('[あいう]', printf('[%06S]', 'あいう'))
+ call assert_equal('[0あいう]', printf('[%07S]', 'あいう'))
+
+ call assert_equal('[あiう]', printf('[%05S]', 'あiう'))
+ call assert_equal('[0あiう]', printf('[%06S]', 'あiう'))
+ call assert_equal('[00あiう]', printf('[%07S]', 'あiう'))
+
+ call assert_equal('[0あい]', printf('[%05.4S]', 'あいう'))
+ call assert_equal('[00あい]', printf('[%06.4S]', 'あいう'))
+ call assert_equal('[000あい]', printf('[%07.4S]', 'あいう'))
+
+ call assert_equal('[00あi]', printf('[%05.4S]', 'あiう'))
+ call assert_equal('[000あi]', printf('[%06.4S]', 'あiう'))
+ call assert_equal('[0000あi]', printf('[%07.4S]', 'あiう'))
+
+ call assert_equal('[0あい]', printf('[%05.5S]', 'あいう'))
+ call assert_equal('[00あい]', printf('[%06.5S]', 'あいう'))
+ call assert_equal('[000あい]', printf('[%07.5S]', 'あいう'))
+
+ call assert_equal('[あiう]', printf('[%05.5S]', 'あiう'))
+ call assert_equal('[0あiう]', printf('[%06.5S]', 'あiう'))
+ call assert_equal('[00あiう]', printf('[%07.5S]', 'あiう'))
+
+ call assert_equal('[0000000000]', printf('[%010.0S]', 'あいう'))
+ call assert_equal('[0000000000]', printf('[%010.1S]', 'あいう'))
+ call assert_equal('[00000000あ]', printf('[%010.2S]', 'あいう'))
+ call assert_equal('[00000000あ]', printf('[%010.3S]', 'あいう'))
+ call assert_equal('[000000あい]', printf('[%010.4S]', 'あいう'))
+ call assert_equal('[000000あい]', printf('[%010.5S]', 'あいう'))
+ call assert_equal('[0000あいう]', printf('[%010.6S]', 'あいう'))
+ call assert_equal('[0000あいう]', printf('[%010.7S]', 'あいう'))
+
+ call assert_equal('[0000000000]', printf('[%010.1S]', 'あiう'))
+ call assert_equal('[00000000あ]', printf('[%010.2S]', 'あiう'))
+ call assert_equal('[0000000あi]', printf('[%010.3S]', 'あiう'))
+ call assert_equal('[0000000あi]', printf('[%010.4S]', 'あiう'))
+ call assert_equal('[00000あiう]', printf('[%010.5S]', 'あiう'))
+ call assert_equal('[00000あiう]', printf('[%010.6S]', 'あiう'))
+ call assert_equal('[00000あiう]', printf('[%010.7S]', 'あiう'))
+
call assert_equal('1%', printf('%d%%', 1))
endfunc
diff --git a/src/nvim/testdir/test_feedkeys.vim b/src/nvim/testdir/test_feedkeys.vim
index 70500f2bb5..f343b0174c 100644
--- a/src/nvim/testdir/test_feedkeys.vim
+++ b/src/nvim/testdir/test_feedkeys.vim
@@ -12,3 +12,15 @@ func Test_feedkeys_x_with_empty_string()
call assert_equal('foo', getline('.'))
quit!
endfunc
+
+func Test_feedkeys_with_abbreviation()
+ new
+ inoreabbrev trigger value
+ call feedkeys("atrigger ", 'x')
+ call feedkeys("atrigger ", 'x')
+ call assert_equal('value value ', getline(1))
+ bwipe!
+ iunabbrev trigger
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_filechanged.vim b/src/nvim/testdir/test_filechanged.vim
index b95cd5faf8..b4c32fcc16 100644
--- a/src/nvim/testdir/test_filechanged.vim
+++ b/src/nvim/testdir/test_filechanged.vim
@@ -91,7 +91,7 @@ func Test_FileChangedShell_reload()
endfunc
func Test_file_changed_dialog()
- throw 'skipped: TODO: '
+ throw 'Skipped: requires a UI to a active'
if !has('unix') || has('gui_running')
return
endif
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index 4da68539fb..b663032c24 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -76,6 +76,7 @@ let s:filename_checks = {
\ 'ave': ['file.ave'],
\ 'awk': ['file.awk', 'file.gawk'],
\ 'b': ['file.mch', 'file.ref', 'file.imp'],
+ \ 'basic': ['file.bas', 'file.bi', 'file.bm'],
\ 'bzl': ['file.bazel', 'file.bzl', 'WORKSPACE'],
\ 'bc': ['file.bc'],
\ 'bdf': ['file.bdf'],
@@ -182,47 +183,59 @@ let s:filename_checks = {
\ 'fgl': ['file.4gl', 'file.4gh', 'file.m4gl'],
\ 'fish': ['file.fish'],
\ 'focexec': ['file.fex', 'file.focexec'],
+ \ 'form': ['file.frm'],
\ 'forth': ['file.ft', 'file.fth'],
\ 'fortran': ['file.f', 'file.for', 'file.fortran', 'file.fpp', 'file.ftn', 'file.f77', 'file.f90', 'file.f95', 'file.f03', 'file.f08'],
\ 'fpcmake': ['file.fpc'],
\ 'framescript': ['file.fsl'],
- \ 'freebasic': ['file.fb', 'file.bi'],
+ \ 'freebasic': ['file.fb'],
\ 'fsharp': ['file.fs', 'file.fsi', 'file.fsx'],
\ 'fstab': ['fstab', 'mtab'],
+ \ 'fusion': ['file.fusion'],
\ 'fvwm': ['/.fvwm/file', 'any/.fvwm/file'],
\ 'gdb': ['.gdbinit', 'gdbinit'],
+ \ 'gdresource': ['file.tscn', 'file.tres'],
+ \ 'gdscript': ['file.gd'],
\ 'gdmo': ['file.mo', 'file.gdmo'],
\ 'gedcom': ['file.ged', 'lltxxxxx.txt', '/tmp/lltmp', '/tmp/lltmp-file', 'any/tmp/lltmp', 'any/tmp/lltmp-file'],
\ 'gemtext': ['file.gmi', 'file.gemini'],
\ 'gift': ['file.gift'],
- \ 'gitcommit': ['COMMIT_EDITMSG', 'MERGE_MSG', 'TAG_EDITMSG'],
- \ 'gitconfig': ['file.git/config', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config', '/etc/gitconfig', '/etc/gitconfig.d/file', '/.gitconfig.d/file', 'any/.config/git/config', 'any/.gitconfig.d/file', 'some.git/config', 'some.git/modules/any/config'],
+ \ 'gitcommit': ['COMMIT_EDITMSG', 'MERGE_MSG', 'TAG_EDITMSG', 'NOTES_EDITMSG', 'EDIT_DESCRIPTION'],
+ \ 'gitconfig': ['file.git/config', 'file.git/config.worktree', 'file.git/worktrees/x/config.worktree', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config', '/etc/gitconfig', '/usr/local/etc/gitconfig', '/etc/gitconfig.d/file', '/.gitconfig.d/file', 'any/.config/git/config', 'any/.gitconfig.d/file', 'some.git/config', 'some.git/modules/any/config'],
\ 'gitolite': ['gitolite.conf', '/gitolite-admin/conf/file', 'any/gitolite-admin/conf/file'],
\ 'gitrebase': ['git-rebase-todo'],
\ 'gitsendemail': ['.gitsendemail.msg.xxxxxx'],
\ 'gkrellmrc': ['gkrellmrc', 'gkrellmrc_x'],
+ \ 'glsl': ['file.glsl'],
\ 'gnash': ['gnashrc', '.gnashrc', 'gnashpluginrc', '.gnashpluginrc'],
- \ 'gnuplot': ['file.gpi'],
+ \ 'gnuplot': ['file.gpi', '.gnuplot'],
\ 'go': ['file.go'],
\ 'gomod': ['go.mod'],
+ \ 'gowork': ['go.work'],
\ 'gp': ['file.gp', '.gprc'],
\ 'gpg': ['/.gnupg/options', '/.gnupg/gpg.conf', '/usr/any/gnupg/options.skel', 'any/.gnupg/gpg.conf', 'any/.gnupg/options', 'any/usr/any/gnupg/options.skel'],
\ 'grads': ['file.gs'],
+ \ 'graphql': ['file.graphql', 'file.graphqls', 'file.gql'],
\ 'gretl': ['file.gretl'],
\ 'groovy': ['file.gradle', 'file.groovy'],
\ 'group': ['any/etc/group', 'any/etc/group-', 'any/etc/group.edit', 'any/etc/gshadow', 'any/etc/gshadow-', 'any/etc/gshadow.edit', 'any/var/backups/group.bak', 'any/var/backups/gshadow.bak', '/etc/group', '/etc/group-', '/etc/group.edit', '/etc/gshadow', '/etc/gshadow-', '/etc/gshadow.edit', '/var/backups/group.bak', '/var/backups/gshadow.bak'],
\ 'grub': ['/boot/grub/menu.lst', '/boot/grub/grub.conf', '/etc/grub.conf', 'any/boot/grub/grub.conf', 'any/boot/grub/menu.lst', 'any/etc/grub.conf'],
\ 'gsp': ['file.gsp'],
\ 'gtkrc': ['.gtkrc', 'gtkrc', '.gtkrc-file', 'gtkrc-file'],
+ \ 'hack': ['file.hack', 'file.hackpartial'],
\ 'haml': ['file.haml'],
\ 'hamster': ['file.hsm'],
+ \ 'handlebars': ['file.hbs'],
\ 'haskell': ['file.hs', 'file.hsc', 'file.hs-boot', 'file.hsig'],
\ 'haste': ['file.ht'],
\ 'hastepreproc': ['file.htpp'],
\ 'hb': ['file.hb'],
+ \ 'hcl': ['file.hcl'],
\ 'hercules': ['file.vc', 'file.ev', 'file.sum', 'file.errsum'],
+ \ 'heex': ['file.heex'],
\ 'hex': ['file.hex', 'file.h32'],
\ 'hgcommit': ['hg-editor-file.txt'],
+ \ 'hjson': ['file.hjson'],
\ 'hog': ['file.hog', 'snort.conf', 'vision.conf'],
\ 'hollywood': ['file.hws'],
\ 'hostconf': ['/etc/host.conf', 'any/etc/host.conf'],
@@ -264,6 +277,7 @@ let s:filename_checks = {
\ 'jovial': ['file.jov', 'file.j73', 'file.jovial'],
\ 'jproperties': ['file.properties', 'file.properties_xx', 'file.properties_xx_xx', 'some.properties_xx_xx_file'],
\ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', '.babelrc', '.eslintrc', '.prettierrc', '.firebaserc', 'file.slnf'],
+ \ 'json5': ['file.json5'],
\ 'jsonc': ['file.jsonc'],
\ 'jsp': ['file.jsp'],
\ 'julia': ['file.jl'],
@@ -277,6 +291,7 @@ let s:filename_checks = {
\ 'latte': ['file.latte', 'file.lte'],
\ 'ld': ['file.ld'],
\ 'ldif': ['file.ldif'],
+ \ 'ledger': ['file.ldg', 'file.ledger', 'file.journal'],
\ 'less': ['file.less'],
\ 'lex': ['file.lex', 'file.l', 'file.lxx', 'file.l++'],
\ 'lftp': ['lftp.conf', '.lftprc', 'anylftp/rc', 'lftp/rc', 'some-lftp/rc'],
@@ -350,6 +365,7 @@ let s:filename_checks = {
\ 'netrc': ['.netrc'],
\ 'nginx': ['file.nginx', 'nginxfile.conf', 'filenginx.conf', 'any/etc/nginx/file', 'any/usr/local/nginx/conf/file', 'any/nginx/file.conf'],
\ 'ninja': ['file.ninja'],
+ \ 'nix': ['file.nix'],
\ 'nqc': ['file.nqc'],
\ 'nroff': ['file.tr', 'file.nr', 'file.roff', 'file.tmac', 'file.mom', 'tmac.file'],
\ 'nsis': ['file.nsi', 'file.nsh'],
@@ -392,6 +408,7 @@ let s:filename_checks = {
\ 'ppd': ['file.ppd'],
\ 'ppwiz': ['file.it', 'file.ih'],
\ 'privoxy': ['file.action'],
+ \ 'prisma': ['file.prisma'],
\ 'proc': ['file.pc'],
\ 'procmail': ['.procmail', '.procmailrc'],
\ 'prolog': ['file.pdb'],
@@ -402,10 +419,12 @@ let s:filename_checks = {
\ 'ps1xml': ['file.ps1xml'],
\ 'psf': ['file.psf'],
\ 'psl': ['file.psl'],
+ \ 'pug': ['file.pug'],
\ 'puppet': ['file.pp'],
\ 'pyret': ['file.arr'],
\ 'pyrex': ['file.pyx', 'file.pxd'],
\ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', 'file.ptl', 'file.pyi', 'SConstruct'],
+ \ 'ql': ['file.ql', 'file.qll'],
\ 'quake': ['anybaseq2/file.cfg', 'anyid1/file.cfg', 'quake3/file.cfg', 'baseq2/file.cfg', 'id1/file.cfg', 'quake1/file.cfg', 'some-baseq2/file.cfg', 'some-id1/file.cfg', 'some-quake1/file.cfg'],
\ 'radiance': ['file.rad', 'file.mat'],
\ 'raku': ['file.pm6', 'file.p6', 'file.t6', 'file.pod6', 'file.raku', 'file.rakumod', 'file.rakudoc', 'file.rakutest'],
@@ -416,6 +435,7 @@ let s:filename_checks = {
\ 'readline': ['.inputrc', 'inputrc'],
\ 'remind': ['.reminders', 'file.remind', 'file.rem', '.reminders-file'],
\ 'rego': ['file.rego'],
+ \ 'rescript': ['file.res', 'file.resi'],
\ 'resolv': ['resolv.conf'],
\ 'reva': ['file.frt'],
\ 'rexx': ['file.rex', 'file.orx', 'file.rxo', 'file.rxj', 'file.jrexx', 'file.rexxj', 'file.rexx', 'file.testGroup', 'file.testUnit'],
@@ -446,7 +466,7 @@ let s:filename_checks = {
\ 'sdc': ['file.sdc'],
\ 'sdl': ['file.sdl', 'file.pr'],
\ 'sed': ['file.sed'],
- \ 'sensors': ['/etc/sensors.conf', '/etc/sensors3.conf', 'any/etc/sensors.conf', 'any/etc/sensors3.conf'],
+ \ 'sensors': ['/etc/sensors.conf', '/etc/sensors3.conf', '/etc/sensors.d/file', 'any/etc/sensors.conf', 'any/etc/sensors3.conf', 'any/etc/sensors.d/file'],
\ 'services': ['/etc/services', 'any/etc/services'],
\ 'setserial': ['/etc/serial.conf', 'any/etc/serial.conf'],
\ 'sh': ['.bashrc', 'file.bash', '/usr/share/doc/bash-completion/filter.sh','/etc/udev/cdsymlinks.conf', 'any/etc/udev/cdsymlinks.conf'],
@@ -487,6 +507,7 @@ let s:filename_checks = {
\ 'stata': ['file.ado', 'file.do', 'file.imata', 'file.mata'],
\ 'stp': ['file.stp'],
\ 'sudoers': ['any/etc/sudoers', 'sudoers.tmp', '/etc/sudoers', 'any/etc/sudoers.d/file'],
+ \ 'surface': ['file.sface'],
\ 'svg': ['file.svg'],
\ 'svn': ['svn-commitfile.tmp', 'svn-commit-file.tmp', 'svn-commit.tmp'],
\ 'swift': ['file.swift'],
@@ -500,8 +521,10 @@ let s:filename_checks = {
\ 'taskdata': ['pending.data', 'completed.data', 'undo.data'],
\ 'taskedit': ['file.task'],
\ 'tcl': ['file.tcl', 'file.tm', 'file.tk', 'file.itcl', 'file.itk', 'file.jacl', '.tclshrc', 'tclsh.rc', '.wishrc'],
+ \ 'teal': ['file.tl'],
\ 'teraterm': ['file.ttl'],
\ 'terminfo': ['file.ti'],
+ \ 'terraform': ['file.tfvars'],
\ 'tex': ['file.latex', 'file.sty', 'file.dtx', 'file.ltx', 'file.bbl'],
\ 'texinfo': ['file.texinfo', 'file.texi', 'file.txi'],
\ 'texmf': ['texmf.cnf'],
@@ -509,6 +532,7 @@ let s:filename_checks = {
\ 'tf': ['file.tf', '.tfrc', 'tfrc'],
\ 'tidy': ['.tidyrc', 'tidyrc', 'tidy.conf'],
\ 'tilde': ['file.t.html'],
+ \ 'tla': ['file.tla'],
\ 'tli': ['file.tli'],
\ 'tmux': ['tmuxfile.conf', '.tmuxfile.conf', '.tmux-file.conf', '.tmux.conf', 'tmux-file.conf', 'tmux.conf', 'tmux.conf.local'],
\ 'toml': ['file.toml', 'Gopkg.lock', 'Pipfile', '/home/user/.cargo/config'],
@@ -534,6 +558,7 @@ let s:filename_checks = {
\ 'usserverlog': ['usserver.log', 'USSERVER.LOG', 'usserver.file.log', 'USSERVER.FILE.LOG', 'file.usserver.log', 'FILE.USSERVER.LOG'],
\ 'usw2kagtlog': ['usw2kagt.log', 'USW2KAGT.LOG', 'usw2kagt.file.log', 'USW2KAGT.FILE.LOG', 'file.usw2kagt.log', 'FILE.USW2KAGT.LOG'],
\ 'vb': ['file.sba', 'file.vb', 'file.vbs', 'file.dsm', 'file.ctl'],
+ \ 'vala': ['file.vala'],
\ 'vera': ['file.vr', 'file.vri', 'file.vrh'],
\ 'verilog': ['file.v'],
\ 'verilogams': ['file.va', 'file.vams'],
@@ -569,6 +594,7 @@ let s:filename_checks = {
\ 'xslt': ['file.xsl', 'file.xslt'],
\ 'yacc': ['file.yy', 'file.yxx', 'file.y++'],
\ 'yaml': ['file.yaml', 'file.yml'],
+ \ 'yang': ['file.yang'],
\ 'raml': ['file.raml'],
\ 'z8a': ['file.z8a'],
\ 'zig': ['file.zig'],
@@ -653,7 +679,7 @@ let s:script_checks = {
\ ['#!/path/nodejs'],
\ ['#!/path/rhino']],
\ 'bc': [['#!/path/bc']],
- \ 'sed': [['#!/path/sed']],
+ \ 'sed': [['#!/path/sed'], ['#n'], ['#n comment']],
\ 'ocaml': [['#!/path/ocaml']],
\ 'awk': [['#!/path/awk'],
\ ['#!/path/gawk']],
@@ -706,83 +732,126 @@ func Test_setfiletype_completion()
call assert_equal('"setfiletype java javacc javascript javascriptreact', @:)
endfunc
-func Test_hook_file()
+"""""""""""""""""""""""""""""""""""""""""""""""""
+" Tests for specific extentions and filetypes.
+" Keep sorted.
+"""""""""""""""""""""""""""""""""""""""""""""""""
+
+func Test_bas_file()
filetype on
- call writefile(['[Trigger]', 'this is pacman config'], 'Xfile.hook')
- split Xfile.hook
- call assert_equal('dosini', &filetype)
+ call writefile(['looks like BASIC'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('basic', &filetype)
bwipe!
- call writefile(['not pacman'], 'Xfile.hook')
- split Xfile.hook
- call assert_notequal('dosini', &filetype)
+ " Test dist#ft#FTbas()
+
+ let g:filetype_bas = 'freebasic'
+ split Xfile.bas
+ call assert_equal('freebasic', &filetype)
bwipe!
+ unlet g:filetype_bas
- call delete('Xfile.hook')
- filetype off
-endfunc
+ " FreeBASIC
-func Test_ts_file()
- filetype on
+ call writefile(["/' FreeBASIC multiline comment '/"], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('freebasic', &filetype)
+ bwipe!
- call writefile(['<?xml version="1.0" encoding="utf-8"?>'], 'Xfile.ts')
- split Xfile.ts
- call assert_equal('xml', &filetype)
+ call writefile(['#define TESTING'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('freebasic', &filetype)
bwipe!
- call writefile(['// looks like Typescript'], 'Xfile.ts')
- split Xfile.ts
- call assert_equal('typescript', &filetype)
+ call writefile(['option byval'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('freebasic', &filetype)
bwipe!
- call delete('Xfile.hook')
- filetype off
-endfunc
+ call writefile(['extern "C"'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('freebasic', &filetype)
+ bwipe!
-func Test_ttl_file()
- filetype on
+ " QB64
- call writefile(['@base <http://example.org/> .'], 'Xfile.ttl')
- split Xfile.ttl
- call assert_equal('turtle', &filetype)
+ call writefile(['$LET TESTING = 1'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('qb64', &filetype)
bwipe!
- call writefile(['looks like Tera Term Language'], 'Xfile.ttl')
- split Xfile.ttl
- call assert_equal('teraterm', &filetype)
+ call writefile(['OPTION _EXPLICIT'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('qb64', &filetype)
bwipe!
- call delete('Xfile.ttl')
+ " Visual Basic
+
+ call writefile(['Attribute VB_NAME = "Testing"'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('vb', &filetype)
+ bwipe!
+
+ call delete('Xfile.bas')
filetype off
endfunc
-func Test_pp_file()
+func Test_dep3patch_file()
filetype on
- call writefile(['looks like puppet'], 'Xfile.pp')
- split Xfile.pp
- call assert_equal('puppet', &filetype)
+ call assert_true(mkdir('debian/patches', 'p'))
+
+ " series files are not patches
+ call writefile(['Description: some awesome patch'], 'debian/patches/series')
+ split debian/patches/series
+ call assert_notequal('dep3patch', &filetype)
bwipe!
- let g:filetype_pp = 'pascal'
- split Xfile.pp
- call assert_equal('pascal', &filetype)
+ " diff/patch files without the right headers should still show up as ft=diff
+ call writefile([], 'debian/patches/foo.diff')
+ split debian/patches/foo.diff
+ call assert_equal('diff', &filetype)
bwipe!
- unlet g:filetype_pp
- " Test dist#ft#FTpp()
- call writefile(['{ pascal comment'], 'Xfile.pp')
- split Xfile.pp
- call assert_equal('pascal', &filetype)
+ " Files with the right headers are detected as dep3patch, even if they don't
+ " have a diff/patch extension
+ call writefile(['Subject: dep3patches'], 'debian/patches/bar')
+ split debian/patches/bar
+ call assert_equal('dep3patch', &filetype)
bwipe!
- call writefile(['procedure pascal'], 'Xfile.pp')
- split Xfile.pp
- call assert_equal('pascal', &filetype)
+ " Files in sub-directories are detected
+ call assert_true(mkdir('debian/patches/s390x', 'p'))
+ call writefile(['Subject: dep3patches'], 'debian/patches/s390x/bar')
+ split debian/patches/s390x/bar
+ call assert_equal('dep3patch', &filetype)
bwipe!
- call delete('Xfile.pp')
+ " The detection stops when seeing the "header end" marker
+ call writefile(['---', 'Origin: the cloud'], 'debian/patches/baz')
+ split debian/patches/baz
+ call assert_notequal('dep3patch', &filetype)
+ bwipe!
+
+ call delete('debian', 'rf')
+endfunc
+
+func Test_dsl_file()
+ filetype on
+
+ call writefile([' <!doctype dsssl-spec ['], 'dslfile.dsl')
+ split dslfile.dsl
+ call assert_equal('dsl', &filetype)
+ bwipe!
+
+ call writefile(['workspace {'], 'dslfile.dsl')
+ split dslfile.dsl
+ call assert_equal('structurizr', &filetype)
+ bwipe!
+
+ call delete('dslfile.dsl')
filetype off
endfunc
@@ -823,20 +892,183 @@ func Test_ex_file()
filetype off
endfunc
-func Test_dsl_file()
+func Test_foam_file()
filetype on
+ call assert_true(mkdir('0', 'p'))
+ call assert_true(mkdir('0.orig', 'p'))
- call writefile([' <!doctype dsssl-spec ['], 'dslfile.dsl')
- split dslfile.dsl
- call assert_equal('dsl', &filetype)
+ call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict')
+ split Xfile1Dict
+ call assert_equal('foam', &filetype)
bwipe!
- call writefile(['workspace {'], 'dslfile.dsl')
- split dslfile.dsl
- call assert_equal('structurizr', &filetype)
+ call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict.something')
+ split Xfile1Dict.something
+ call assert_equal('foam', &filetype)
bwipe!
- call delete('dslfile.dsl')
+ call writefile(['FoamFile {', ' object something;'], 'XfileProperties')
+ split XfileProperties
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something')
+ split XfileProperties.something
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ call writefile(['FoamFile {', ' object something;'], 'XfileProperties')
+ split XfileProperties
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something')
+ split XfileProperties.something
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ call writefile(['FoamFile {', ' object something;'], '0/Xfile')
+ split 0/Xfile
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ call writefile(['FoamFile {', ' object something;'], '0.orig/Xfile')
+ split 0.orig/Xfile
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ call delete('0', 'rf')
+ call delete('0.orig', 'rf')
+ call delete('Xfile1Dict')
+ call delete('Xfile1Dict.something')
+ call delete('XfileProperties')
+ call delete('XfileProperties.something')
+ filetype off
+endfunc
+
+func Test_frm_file()
+ filetype on
+
+ call writefile(['looks like FORM'], 'Xfile.frm')
+ split Xfile.frm
+ call assert_equal('form', &filetype)
+ bwipe!
+
+ " Test dist#ft#FTfrm()
+
+ let g:filetype_frm = 'form'
+ split Xfile.frm
+ call assert_equal('form', &filetype)
+ bwipe!
+ unlet g:filetype_frm
+
+ " Visual Basic
+
+ call writefile(['Begin VB.Form Form1'], 'Xfile.frm')
+ split Xfile.frm
+ call assert_equal('vb', &filetype)
+ bwipe!
+
+ call delete('Xfile.frm')
+ filetype off
+endfunc
+
+func Test_fs_file()
+ filetype on
+
+ call writefile(['looks like F#'], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('fsharp', &filetype)
+ bwipe!
+
+ let g:filetype_fs = 'forth'
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+ unlet g:filetype_fs
+
+ " Test dist#ft#FTfs()
+
+ " Forth (Gforth)
+
+ call writefile(['( Forth inline comment )'], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ call writefile(['.( Forth displayed inline comment )'], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ call writefile(['\ Forth line comment'], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ " empty line comment - no space required
+ call writefile(['\'], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ call writefile(['\G Forth documentation comment '], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ call writefile([': squared ( n -- n^2 )', 'dup * ;'], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ call delete('Xfile.fs')
+ filetype off
+endfunc
+
+func Test_git_file()
+ filetype on
+
+ call assert_true(mkdir('Xrepo.git', 'p'))
+
+ call writefile([], 'Xrepo.git/HEAD')
+ split Xrepo.git/HEAD
+ call assert_equal('', &filetype)
+ bwipe!
+
+ call writefile(['0000000000000000000000000000000000000000'], 'Xrepo.git/HEAD')
+ split Xrepo.git/HEAD
+ call assert_equal('git', &filetype)
+ bwipe!
+
+ call writefile(['0000000000000000000000000000000000000000000000000000000000000000'], 'Xrepo.git/HEAD')
+ split Xrepo.git/HEAD
+ call assert_equal('git', &filetype)
+ bwipe!
+
+ call writefile(['ref: refs/heads/master'], 'Xrepo.git/HEAD')
+ split Xrepo.git/HEAD
+ call assert_equal('git', &filetype)
+ bwipe!
+
+ call delete('Xrepo.git', 'rf')
+ filetype off
+endfunc
+
+func Test_hook_file()
+ filetype on
+
+ call writefile(['[Trigger]', 'this is pacman config'], 'Xfile.hook')
+ split Xfile.hook
+ call assert_equal('dosini', &filetype)
+ bwipe!
+
+ call writefile(['not pacman'], 'Xfile.hook')
+ split Xfile.hook
+ call assert_notequal('dosini', &filetype)
+ bwipe!
+
+ call delete('Xfile.hook')
filetype off
endfunc
@@ -940,109 +1172,154 @@ func Test_m_file()
filetype off
endfunc
-func Test_xpm_file()
+func Test_patch_file()
filetype on
- call writefile(['this is XPM2'], 'file.xpm')
- split file.xpm
- call assert_equal('xpm2', &filetype)
+ call writefile([], 'Xfile.patch')
+ split Xfile.patch
+ call assert_equal('diff', &filetype)
bwipe!
- call delete('file.xpm')
+ call writefile(['From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001'], 'Xfile.patch')
+ split Xfile.patch
+ call assert_equal('gitsendemail', &filetype)
+ bwipe!
+
+ call writefile(['From 0000000000000000000000000000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001'], 'Xfile.patch')
+ split Xfile.patch
+ call assert_equal('gitsendemail', &filetype)
+ bwipe!
+
+ call delete('Xfile.patch')
filetype off
endfunc
-func Test_fs_file()
+func Test_perl_file()
filetype on
- call writefile(['looks like F#'], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('fsharp', &filetype)
- bwipe!
+ " only tests one case, should do more
+ let lines =<< trim END
- let g:filetype_fs = 'forth'
- split Xfile.fs
- call assert_equal('forth', &filetype)
- bwipe!
- unlet g:filetype_fs
+ use a
+ END
+ call writefile(lines, "Xfile.t")
+ split Xfile.t
+ call assert_equal('perl', &filetype)
+ bwipe
- " Test dist#ft#FTfs()
+ call delete('Xfile.t')
+ filetype off
+endfunc
- " Forth (Gforth)
+func Test_pp_file()
+ filetype on
- call writefile(['( Forth inline comment )'], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('forth', &filetype)
+ call writefile(['looks like puppet'], 'Xfile.pp')
+ split Xfile.pp
+ call assert_equal('puppet', &filetype)
bwipe!
- call writefile(['.( Forth displayed inline comment )'], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('forth', &filetype)
+ let g:filetype_pp = 'pascal'
+ split Xfile.pp
+ call assert_equal('pascal', &filetype)
bwipe!
+ unlet g:filetype_pp
- call writefile(['\ Forth line comment'], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('forth', &filetype)
+ " Test dist#ft#FTpp()
+ call writefile(['{ pascal comment'], 'Xfile.pp')
+ split Xfile.pp
+ call assert_equal('pascal', &filetype)
bwipe!
- " empty line comment - no space required
- call writefile(['\'], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('forth', &filetype)
+ call writefile(['procedure pascal'], 'Xfile.pp')
+ split Xfile.pp
+ call assert_equal('pascal', &filetype)
bwipe!
- call writefile(['\G Forth documentation comment '], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('forth', &filetype)
+ call delete('Xfile.pp')
+ filetype off
+endfunc
+
+func Test_tex_file()
+ filetype on
+
+ " only tests one case, should do more
+ let lines =<< trim END
+ % This is a sentence.
+
+ This is a sentence.
+ END
+ call writefile(lines, "Xfile.tex")
+ split Xfile.tex
+ call assert_equal('plaintex', &filetype)
+ bwipe
+
+ call delete('Xfile.tex')
+ filetype off
+endfunc
+
+func Test_tf_file()
+ filetype on
+
+ call writefile([';;; TF MUD client is super duper cool'], 'Xfile.tf')
+ split Xfile.tf
+ call assert_equal('tf', &filetype)
bwipe!
- call writefile([': squared ( n -- n^2 )', 'dup * ;'], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('forth', &filetype)
+ call writefile(['provider "azurerm" {'], 'Xfile.tf')
+ split Xfile.tf
+ call assert_equal('terraform', &filetype)
bwipe!
- call delete('Xfile.fs')
+ call delete('Xfile.tf')
filetype off
endfunc
-func Test_dep3patch_file()
+func Test_ts_file()
filetype on
- call assert_true(mkdir('debian/patches', 'p'))
-
- " series files are not patches
- call writefile(['Description: some awesome patch'], 'debian/patches/series')
- split debian/patches/series
- call assert_notequal('dep3patch', &filetype)
+ call writefile(['<?xml version="1.0" encoding="utf-8"?>'], 'Xfile.ts')
+ split Xfile.ts
+ call assert_equal('xml', &filetype)
bwipe!
- " diff/patch files without the right headers should still show up as ft=diff
- call writefile([], 'debian/patches/foo.diff')
- split debian/patches/foo.diff
- call assert_equal('diff', &filetype)
+ call writefile(['// looks like Typescript'], 'Xfile.ts')
+ split Xfile.ts
+ call assert_equal('typescript', &filetype)
bwipe!
- " Files with the right headers are detected as dep3patch, even if they don't
- " have a diff/patch extension
- call writefile(['Subject: dep3patches'], 'debian/patches/bar')
- split debian/patches/bar
- call assert_equal('dep3patch', &filetype)
+ call delete('Xfile.ts')
+ filetype off
+endfunc
+
+func Test_ttl_file()
+ filetype on
+
+ call writefile(['@base <http://example.org/> .'], 'Xfile.ttl')
+ split Xfile.ttl
+ call assert_equal('turtle', &filetype)
bwipe!
- " Files in sub-directories are detected
- call assert_true(mkdir('debian/patches/s390x', 'p'))
- call writefile(['Subject: dep3patches'], 'debian/patches/s390x/bar')
- split debian/patches/s390x/bar
- call assert_equal('dep3patch', &filetype)
+ call writefile(['looks like Tera Term Language'], 'Xfile.ttl')
+ split Xfile.ttl
+ call assert_equal('teraterm', &filetype)
bwipe!
- " The detection stops when seeing the "header end" marker
- call writefile(['---', 'Origin: the cloud'], 'debian/patches/baz')
- split debian/patches/baz
- call assert_notequal('dep3patch', &filetype)
+ call delete('Xfile.ttl')
+ filetype off
+endfunc
+
+func Test_xpm_file()
+ filetype on
+
+ call writefile(['this is XPM2'], 'file.xpm')
+ split file.xpm
+ call assert_equal('xpm2', &filetype)
bwipe!
- call delete('debian/patches', 'rf')
+ call delete('file.xpm')
+ filetype off
endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_filetype_lua.vim b/src/nvim/testdir/test_filetype_lua.vim
new file mode 100644
index 0000000000..f73e4ca33f
--- /dev/null
+++ b/src/nvim/testdir/test_filetype_lua.vim
@@ -0,0 +1,2 @@
+let g:do_filetype_lua = 1
+source test_filetype.vim
diff --git a/src/nvim/testdir/test_filter_cmd.vim b/src/nvim/testdir/test_filter_cmd.vim
index 0c45db049b..d465e48c7b 100644
--- a/src/nvim/testdir/test_filter_cmd.vim
+++ b/src/nvim/testdir/test_filter_cmd.vim
@@ -145,3 +145,38 @@ func Test_filter_commands()
bwipe! file.h
bwipe! file.hs
endfunc
+
+func Test_filter_display()
+ edit Xdoesnotmatch
+ let @a = '!!willmatch'
+ let @b = '!!doesnotmatch'
+ let @c = "oneline\ntwoline\nwillmatch\n"
+ let @/ = '!!doesnotmatch'
+ call feedkeys(":echo '!!doesnotmatch:'\<CR>", 'ntx')
+ let lines = map(split(execute('filter /willmatch/ display'), "\n"), 'v:val[5:6]')
+
+ call assert_true(index(lines, '"a') >= 0)
+ call assert_false(index(lines, '"b') >= 0)
+ call assert_true(index(lines, '"c') >= 0)
+ call assert_false(index(lines, '"/') >= 0)
+ call assert_false(index(lines, '":') >= 0)
+ call assert_false(index(lines, '"%') >= 0)
+
+ let lines = map(split(execute('filter /doesnotmatch/ display'), "\n"), 'v:val[5:6]')
+ call assert_true(index(lines, '"a') < 0)
+ call assert_false(index(lines, '"b') < 0)
+ call assert_true(index(lines, '"c') < 0)
+ call assert_false(index(lines, '"/') < 0)
+ call assert_false(index(lines, '":') < 0)
+ call assert_false(index(lines, '"%') < 0)
+
+ bwipe!
+endfunc
+
+func Test_filter_scriptnames()
+ let lines = split(execute('filter /test_filter_cmd/ scriptnames'), "\n")
+ call assert_equal(1, len(lines))
+ call assert_match('filter_cmd', lines[0])
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_filter_map.vim b/src/nvim/testdir/test_filter_map.vim
index a52a66ac2f..1cd3a2287b 100644
--- a/src/nvim/testdir/test_filter_map.vim
+++ b/src/nvim/testdir/test_filter_map.vim
@@ -88,4 +88,14 @@ func Test_map_filter_fails()
call assert_fails("let l = filter('abc', '\"> \" . v:val')", 'E896:')
endfunc
+func Test_map_and_modify()
+ let l = ["abc"]
+ " cannot change the list halfway a map()
+ call assert_fails('call map(l, "remove(l, 0)[0]")', 'E741:')
+
+ let d = #{a: 1, b: 2, c: 3}
+ call assert_fails('call map(d, "remove(d, v:key)[0]")', 'E741:')
+ call assert_fails('echo map(d, {k,v -> remove(d, k)})', 'E741:')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_findfile.vim b/src/nvim/testdir/test_findfile.vim
index 5a20475d3d..1684c5d30a 100644
--- a/src/nvim/testdir/test_findfile.vim
+++ b/src/nvim/testdir/test_findfile.vim
@@ -226,4 +226,26 @@ func Test_find_cmd()
call assert_fails('tabfind', 'E471:')
endfunc
+func Test_find_non_existing_path()
+ new
+ let save_path = &path
+ let save_dir = getcwd()
+ call mkdir('dir1/dir2', 'p')
+ call writefile([], 'dir1/file.txt')
+ call writefile([], 'dir1/dir2/base.txt')
+ call chdir('dir1/dir2')
+ e base.txt
+ set path=../include
+
+ call assert_fails(':find file.txt', 'E345:')
+
+ call chdir(save_dir)
+ bw!
+ call delete('dir1/dir2/base.txt', 'rf')
+ call delete('dir1/dir2', 'rf')
+ call delete('dir1/file.txt', 'rf')
+ call delete('dir1', 'rf')
+ let &path = save_path
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_fnamemodify.vim b/src/nvim/testdir/test_fnamemodify.vim
index 411f7ebbb3..5ae2a5ee17 100644
--- a/src/nvim/testdir/test_fnamemodify.vim
+++ b/src/nvim/testdir/test_fnamemodify.vim
@@ -3,8 +3,10 @@
func Test_fnamemodify()
let save_home = $HOME
let save_shell = &shell
+ let save_shellslash = &shellslash
let $HOME = fnamemodify('.', ':p:h:h')
set shell=sh
+ set shellslash
call assert_equal('/', fnamemodify('.', ':p')[-1:])
call assert_equal('r', fnamemodify('.', ':p:h')[-1:])
@@ -27,6 +29,21 @@ func Test_fnamemodify()
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(getcwd(), fnamemodify('', ':p:h'))
+
+ let cwd = getcwd()
+ call chdir($HOME)
+ call assert_equal('foobar', fnamemodify('~/foobar', ':~:.'))
+ call chdir(cwd)
+ call mkdir($HOME . '/XXXXXXXX/a', 'p')
+ call mkdir($HOME . '/XXXXXXXX/b', 'p')
+ call chdir($HOME . '/XXXXXXXX/a/')
+ call assert_equal('foo', fnamemodify($HOME . '/XXXXXXXX/a/foo', ':p:~:.'))
+ call assert_equal('~/XXXXXXXX/b/foo', fnamemodify($HOME . '/XXXXXXXX/b/foo', ':p:~:.'))
+ call mkdir($HOME . '/XXXXXXXX/a.ext', 'p')
+ call assert_equal('~/XXXXXXXX/a.ext/foo', fnamemodify($HOME . '/XXXXXXXX/a.ext/foo', ':p:~:.'))
+ call chdir(cwd)
+ call delete($HOME . '/XXXXXXXX', 'rf')
call assert_equal('''abc def''', fnamemodify('abc def', ':S'))
call assert_equal('''abc" "def''', fnamemodify('abc" "def', ':S'))
@@ -44,6 +61,7 @@ func Test_fnamemodify()
let $HOME = save_home
let &shell = save_shell
+ let &shellslash = save_shellslash
endfunc
func Test_fnamemodify_er()
@@ -73,6 +91,7 @@ func Test_fnamemodify_er()
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'))
+ call assert_equal('', fnamemodify('', ':p:t'))
call assert_equal('', fnamemodify(v:_null_string, v:_null_string))
endfunc
diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
index 0edbeb420a..438bed51c6 100644
--- a/src/nvim/testdir/test_functions.vim
+++ b/src/nvim/testdir/test_functions.vim
@@ -376,6 +376,25 @@ func Test_pathshorten()
call assert_equal('~.f/bar', pathshorten('~.foo/bar'))
call assert_equal('.~f/bar', pathshorten('.~foo/bar'))
call assert_equal('~/f/bar', pathshorten('~/foo/bar'))
+ call assert_fails('call pathshorten([])', 'E730:')
+
+ " test pathshorten with optional variable to set preferred size of shortening
+ call assert_equal('', pathshorten('', 2))
+ call assert_equal('foo', pathshorten('foo', 2))
+ call assert_equal('/foo', pathshorten('/foo', 2))
+ call assert_equal('fo/', pathshorten('foo/', 2))
+ call assert_equal('fo/bar', pathshorten('foo/bar', 2))
+ call assert_equal('fo/ba/foobar', pathshorten('foo/bar/foobar', 2))
+ call assert_equal('/fo/ba/foobar', pathshorten('/foo/bar/foobar', 2))
+ call assert_equal('.fo/bar', pathshorten('.foo/bar', 2))
+ call assert_equal('~fo/bar', pathshorten('~foo/bar', 2))
+ call assert_equal('~.fo/bar', pathshorten('~.foo/bar', 2))
+ call assert_equal('.~fo/bar', pathshorten('.~foo/bar', 2))
+ call assert_equal('~/fo/bar', pathshorten('~/foo/bar', 2))
+ call assert_fails('call pathshorten([],2)', 'E730:')
+ call assert_notequal('~/fo/bar', pathshorten('~/foo/bar', 3))
+ call assert_equal('~/foo/bar', pathshorten('~/foo/bar', 3))
+ call assert_equal('~/f/bar', pathshorten('~/foo/bar', 0))
endfunc
func Test_strpart()
@@ -1377,6 +1396,36 @@ func Test_func_exists_on_reload()
delfunc ExistingFunction
endfunc
+func Test_platform_name()
+ " The system matches at most only one name.
+ let names = ['amiga', 'beos', 'bsd', 'hpux', 'linux', 'mac', 'qnx', 'sun', 'vms', 'win32', 'win32unix']
+ call assert_inrange(0, 1, len(filter(copy(names), 'has(v:val)')))
+
+ " Is Unix?
+ call assert_equal(has('beos'), has('beos') && has('unix'))
+ call assert_equal(has('bsd'), has('bsd') && has('unix'))
+ call assert_equal(has('hpux'), has('hpux') && has('unix'))
+ call assert_equal(has('linux'), has('linux') && has('unix'))
+ call assert_equal(has('mac'), has('mac') && has('unix'))
+ call assert_equal(has('qnx'), has('qnx') && has('unix'))
+ call assert_equal(has('sun'), has('sun') && has('unix'))
+ call assert_equal(has('win32'), has('win32') && !has('unix'))
+ call assert_equal(has('win32unix'), has('win32unix') && has('unix'))
+
+ if has('unix') && executable('uname')
+ let uname = system('uname')
+ call assert_equal(uname =~? 'BeOS', has('beos'))
+ " GNU userland on BSD kernels (e.g., GNU/kFreeBSD) don't have BSD defined
+ call assert_equal(uname =~? '\%(GNU/k\w\+\)\@<!BSD\|DragonFly', has('bsd'))
+ call assert_equal(uname =~? 'HP-UX', has('hpux'))
+ call assert_equal(uname =~? 'Linux', has('linux'))
+ call assert_equal(uname =~? 'Darwin', has('mac'))
+ call assert_equal(uname =~? 'QNX', has('qnx'))
+ call assert_equal(uname =~? 'SunOS', has('sun'))
+ call assert_equal(uname =~? 'CYGWIN\|MSYS', has('win32unix'))
+ endif
+endfunc
+
sandbox function Fsandbox()
normal ix
endfunc
@@ -1519,24 +1568,31 @@ func Test_libcall_libcallnr()
let libc = 'msvcrt.dll'
elseif has('mac')
let libc = 'libSystem.B.dylib'
- elseif system('uname -s') =~ 'SunOS'
- " Set the path to libc.so according to the architecture.
- let test_bits = system('file ' . GetVimProg())
- let test_arch = system('uname -p')
- if test_bits =~ '64-bit' && test_arch =~ 'sparc'
- let libc = '/usr/lib/sparcv9/libc.so'
- elseif test_bits =~ '64-bit' && test_arch =~ 'i386'
- let libc = '/usr/lib/amd64/libc.so'
+ elseif executable('ldd')
+ let libc = matchstr(split(system('ldd ' . GetVimProg())), '/libc\.so\>')
+ endif
+ if get(l:, 'libc', '') ==# ''
+ " On Unix, libc.so can be in various places.
+ if has('linux')
+ " There is not documented but regarding the 1st argument of glibc's
+ " dlopen an empty string and nullptr are equivalent, so using an empty
+ " string for the 1st argument of libcall allows to call functions.
+ let libc = ''
+ elseif has('sun')
+ " Set the path to libc.so according to the architecture.
+ let test_bits = system('file ' . GetVimProg())
+ let test_arch = system('uname -p')
+ if test_bits =~ '64-bit' && test_arch =~ 'sparc'
+ let libc = '/usr/lib/sparcv9/libc.so'
+ elseif test_bits =~ '64-bit' && test_arch =~ 'i386'
+ let libc = '/usr/lib/amd64/libc.so'
+ else
+ let libc = '/usr/lib/libc.so'
+ endif
else
- let libc = '/usr/lib/libc.so'
+ " Unfortunately skip this test until a good way is found.
+ return
endif
- elseif system('uname -s') =~ 'OpenBSD'
- let libc = 'libc.so'
- else
- " On Unix, libc.so can be in various places.
- " Interestingly, using an empty string for the 1st argument of libcall
- " allows to call functions from libc which is not documented.
- let libc = ''
endif
if has('win32')
@@ -1675,6 +1731,33 @@ func Test_nr2char()
call assert_equal("\x80\xfc\b\xfd\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX", eval('"\<M-' .. nr2char(0x40000000) .. '>"'))
endfunc
+" Test for getcurpos() and setpos()
+func Test_getcurpos_setpos()
+ new
+ call setline(1, ['012345678', '012345678'])
+ normal gg6l
+ let sp = getcurpos()
+ normal 0
+ call setpos('.', sp)
+ normal jyl
+ call assert_equal('6', @")
+ call assert_equal(-1, setpos('.', v:_null_list))
+ call assert_equal(-1, setpos('.', {}))
+
+ let winid = win_getid()
+ normal G$
+ let pos = getcurpos()
+ wincmd w
+ call assert_equal(pos, getcurpos(winid))
+
+ wincmd w
+ close!
+
+ call assert_equal(getcurpos(), getcurpos(0))
+ call assert_equal([0, 0, 0, 0, 0], getcurpos(-1))
+ call assert_equal([0, 0, 0, 0, 0], getcurpos(1999))
+endfunc
+
func HasDefault(msg = 'msg')
return a:msg
endfunc
diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim
index 43efd6248e..589899f532 100644
--- a/src/nvim/testdir/test_gf.vim
+++ b/src/nvim/testdir/test_gf.vim
@@ -19,11 +19,7 @@ func Test_gf_url()
call search("^second")
call search("URL")
call assert_equal("URL://machine.name/tmp/vimtest2b", expand("<cfile>"))
- if has("ebcdic")
- set isf=@,240-249,/,.,-,_,+,,,$,:,~,\
- else
- set isf=@,48-57,/,.,-,_,+,,,$,~,\
- endif
+ set isf=@,48-57,/,.,-,_,+,,,$,~,\
call search("^third")
call search("name")
call assert_equal("URL:\\\\machine.name\\vimtest2c", expand("<cfile>"))
@@ -76,11 +72,7 @@ endfunc
" Test for invoking 'gf' on a ${VAR} variable
func Test_gf()
- if has("ebcdic")
- set isfname=@,240-249,/,.,-,_,+,,,$,:,~,{,}
- else
- set isfname=@,48-57,/,.,-,_,+,,,$,:,~,{,}
- endif
+ set isfname=@,48-57,/,.,-,_,+,,,$,:,~,{,}
call writefile(["Test for gf command"], "Xtest1")
if has("unix")
diff --git a/src/nvim/testdir/test_global.vim b/src/nvim/testdir/test_global.vim
index 8edc9c2608..ad561baf4a 100644
--- a/src/nvim/testdir/test_global.vim
+++ b/src/nvim/testdir/test_global.vim
@@ -36,6 +36,36 @@ func Test_global_error()
call assert_fails('g/\(/y', 'E476:')
endfunc
+" Test for printing lines using :g with different search patterns
+func Test_global_print()
+ new
+ call setline(1, ['foo', 'bar', 'foo', 'foo'])
+ let @/ = 'foo'
+ let t = execute("g/")->trim()->split("\n")
+ call assert_equal(['foo', 'foo', 'foo'], t)
+
+ " Test for Vi compatible patterns
+ let @/ = 'bar'
+ let t = execute('g\/')->trim()->split("\n")
+ call assert_equal(['bar'], t)
+
+ normal gg
+ s/foo/foo/
+ let t = execute('g\&')->trim()->split("\n")
+ call assert_equal(['foo', 'foo', 'foo'], t)
+
+ let @/ = 'bar'
+ let t = execute('g?')->trim()->split("\n")
+ call assert_equal(['bar'], t)
+
+ " Test for the 'Pattern found in every line' message
+ let v:statusmsg = ''
+ v/foo\|bar/p
+ call assert_notequal('', v:statusmsg)
+
+ close!
+endfunc
+
func Test_wrong_delimiter()
call assert_fails('g x^bxd', 'E146:')
endfunc
diff --git a/src/nvim/testdir/test_help.vim b/src/nvim/testdir/test_help.vim
index 8e59efd22d..b2d943be00 100644
--- a/src/nvim/testdir/test_help.vim
+++ b/src/nvim/testdir/test_help.vim
@@ -1,4 +1,3 @@
-
" Tests for :help
func Test_help_restore_snapshot()
@@ -13,6 +12,18 @@ endfunc
func Test_help_errors()
call assert_fails('help doesnotexist', 'E149:')
call assert_fails('help!', 'E478:')
+ if has('multi_lang')
+ call assert_fails('help help@xy', 'E661:')
+ endif
+
+ let save_hf = &helpfile
+ set helpfile=help_missing
+ help
+ call assert_equal(1, winnr('$'))
+ call assert_notequal('help', &buftype)
+ let &helpfile = save_hf
+
+ call assert_fails('help ' . repeat('a', 1048), 'E149:')
new
set keywordprg=:help
@@ -101,4 +112,13 @@ func Test_helptag_cmd()
call delete('Xdir', 'rf')
endfunc
+func Test_help_long_argument()
+ try
+ exe 'help \%' .. repeat('0', 1021)
+ catch
+ call assert_match("E149:", v:exception)
+ endtry
+endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_help_tagjump.vim b/src/nvim/testdir/test_help_tagjump.vim
index a6494c531c..a43889b57e 100644
--- a/src/nvim/testdir/test_help_tagjump.vim
+++ b/src/nvim/testdir/test_help_tagjump.vim
@@ -23,6 +23,11 @@ func Test_help_tagjump()
call assert_true(getline('.') =~ '\*bar\*')
helpclose
+ help "
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*quote\*')
+ helpclose
+
help "*
call assert_equal("help", &filetype)
call assert_true(getline('.') =~ '\*quotestar\*')
@@ -86,11 +91,40 @@ func Test_help_tagjump()
call assert_true(getline('.') =~ '\*i_^_CTRL-D\*')
helpclose
+ help i^x^y
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*i_CTRL-X_CTRL-Y\*')
+ helpclose
+
+ exe "help i\<C-\>\<C-G>"
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*i_CTRL-\\_CTRL-G\*')
+ helpclose
+
exec "help \<C-V>"
call assert_equal("help", &filetype)
call assert_true(getline('.') =~ '\*CTRL-V\*')
helpclose
+ help /\|
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*/\\bar\*')
+ helpclose
+
+ help CTRL-\_CTRL-N
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*CTRL-\\_CTRL-N\*')
+ helpclose
+
+ help `:pwd`,
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*:pwd\*')
+ helpclose
+
+ help `:ls`.
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*:ls\*')
+ helpclose
exec "help! ('textwidth'"
call assert_equal("help", &filetype)
@@ -122,6 +156,15 @@ func Test_help_tagjump()
call assert_true(getline('.') =~ '\*{address}\*')
helpclose
+ " Use special patterns in the help tag
+ for h in ['/\w', '/\%^', '/\%(', '/\zs', '/\@<=', '/\_$', '[++opt]', '/\{']
+ exec "help! " . h
+ call assert_equal("help", &filetype)
+ let pat = '\*' . escape(h, '\$[') . '\*'
+ call assert_true(getline('.') =~ pat, pat)
+ helpclose
+ endfor
+
exusage
call assert_equal("help", &filetype)
call assert_true(getline('.') =~ '\*:index\*')
diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim
index 899eb530ec..6971ecd357 100644
--- a/src/nvim/testdir/test_highlight.vim
+++ b/src/nvim/testdir/test_highlight.vim
@@ -597,6 +597,31 @@ func Test_cursorline_with_visualmode()
call delete('Xtest_cursorline_with_visualmode')
endfunc
+func Test_colorcolumn()
+ CheckScreendump
+
+ " check that setting 'colorcolumn' when entering a buffer works
+ let lines =<< trim END
+ split
+ edit X
+ call setline(1, ["1111111111","22222222222","3333333333"])
+ set nomodified
+ set colorcolumn=3,9
+ set number cursorline cursorlineopt=number
+ wincmd w
+ buf X
+ END
+ call writefile(lines, 'Xtest_colorcolumn')
+ let buf = RunVimInTerminal('-S Xtest_colorcolumn', {'rows': 10})
+ call term_sendkeys(buf, ":\<CR>")
+ call term_wait(buf)
+ call VerifyScreenDump(buf, 'Test_colorcolumn_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xtest_colorcolumn')
+endfunc
+
func Test_colorcolumn_bri()
CheckScreendump
diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim
index ce75799551..186fa8871f 100644
--- a/src/nvim/testdir/test_ins_complete.vim
+++ b/src/nvim/testdir/test_ins_complete.vim
@@ -109,7 +109,7 @@ func s:CompleteDone_CompleteFuncNone( findstart, base )
return v:none
endfunc
-function! s:CompleteDone_CompleteFuncDict( findstart, base )
+func s:CompleteDone_CompleteFuncDict( findstart, base )
if a:findstart
return 0
endif
@@ -126,7 +126,7 @@ function! s:CompleteDone_CompleteFuncDict( findstart, base )
\ }
\ ]
\ }
-endfunction
+endfunc
func s:CompleteDone_CheckCompletedItemNone()
let s:called_completedone = 1
@@ -341,6 +341,14 @@ func Test_compl_feedkeys()
set completeopt&
endfunc
+func s:ComplInCmdwin_GlobalCompletion(a, l, p)
+ return 'global'
+endfunc
+
+func s:ComplInCmdwin_LocalCompletion(a, l, p)
+ return 'local'
+endfunc
+
func Test_compl_in_cmdwin()
set wildmenu wildchar=<Tab>
com! -nargs=1 -complete=command GetInput let input = <q-args>
@@ -376,6 +384,47 @@ func Test_compl_in_cmdwin()
call feedkeys("q::GetInput b:test_\<Tab>\<CR>:q\<CR>", 'tx!')
call assert_equal('b:test_', input)
+
+ " Argument completion of buffer-local command
+ func s:ComplInCmdwin_GlobalCompletionList(a, l, p)
+ return ['global']
+ endfunc
+
+ func s:ComplInCmdwin_LocalCompletionList(a, l, p)
+ return ['local']
+ endfunc
+
+ func s:ComplInCmdwin_CheckCompletion(arg)
+ call assert_equal('local', a:arg)
+ endfunc
+
+ com! -nargs=1 -complete=custom,<SID>ComplInCmdwin_GlobalCompletion
+ \ TestCommand call s:ComplInCmdwin_CheckCompletion(<q-args>)
+ com! -buffer -nargs=1 -complete=custom,<SID>ComplInCmdwin_LocalCompletion
+ \ TestCommand call s:ComplInCmdwin_CheckCompletion(<q-args>)
+ call feedkeys("q:iTestCommand \<Tab>\<CR>", 'tx!')
+
+ com! -nargs=1 -complete=customlist,<SID>ComplInCmdwin_GlobalCompletionList
+ \ TestCommand call s:ComplInCmdwin_CheckCompletion(<q-args>)
+ com! -buffer -nargs=1 -complete=customlist,<SID>ComplInCmdwin_LocalCompletionList
+ \ TestCommand call s:ComplInCmdwin_CheckCompletion(<q-args>)
+
+ call feedkeys("q:iTestCommand \<Tab>\<CR>", 'tx!')
+
+ func! s:ComplInCmdwin_CheckCompletion(arg)
+ call assert_equal('global', a:arg)
+ endfunc
+ new
+ call feedkeys("q:iTestCommand \<Tab>\<CR>", 'tx!')
+ quit
+
+ delfunc s:ComplInCmdwin_GlobalCompletion
+ delfunc s:ComplInCmdwin_LocalCompletion
+ delfunc s:ComplInCmdwin_GlobalCompletionList
+ delfunc s:ComplInCmdwin_LocalCompletionList
+ delfunc s:ComplInCmdwin_CheckCompletion
+
+ delcom -buffer TestCommand
delcom TestCommand
delcom GetInput
unlet w:test_winvar
@@ -445,6 +494,28 @@ func Test_issue_7021()
set completeslash=
endfunc
+func Test_pum_stopped_by_timer()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, ['hello', 'hullo', 'heeee', ''])
+ func StartCompl()
+ call timer_start(100, { -> execute('stopinsert') })
+ call feedkeys("Gah\<C-N>")
+ endfunc
+ END
+
+ call writefile(lines, 'Xpumscript')
+ let buf = RunVimInTerminal('-S Xpumscript', #{rows: 12})
+ call term_sendkeys(buf, ":call StartCompl()\<CR>")
+ call TermWait(buf, 200)
+ call term_sendkeys(buf, "k")
+ call VerifyScreenDump(buf, 'Test_pum_stopped_by_timer', {})
+
+ call StopVimInTerminal(buf)
+ call delete('Xpumscript')
+endfunc
+
func Test_pum_with_folds_two_tabs()
CheckScreendump
diff --git a/src/nvim/testdir/test_lambda.vim b/src/nvim/testdir/test_lambda.vim
index 63bb4ae1ef..c1fe47d1c9 100644
--- a/src/nvim/testdir/test_lambda.vim
+++ b/src/nvim/testdir/test_lambda.vim
@@ -1,24 +1,24 @@
" Test for lambda and closure
-function! Test_lambda_feature()
+func Test_lambda_feature()
call assert_equal(1, has('lambda'))
-endfunction
+endfunc
-function! Test_lambda_with_filter()
+func Test_lambda_with_filter()
let s:x = 2
call assert_equal([2, 3], filter([1, 2, 3], {i, v -> v >= s:x}))
-endfunction
+endfunc
-function! Test_lambda_with_map()
+func Test_lambda_with_map()
let s:x = 1
call assert_equal([2, 3, 4], map([1, 2, 3], {i, v -> v + s:x}))
-endfunction
+endfunc
-function! Test_lambda_with_sort()
+func Test_lambda_with_sort()
call assert_equal([1, 2, 3, 4, 7], sort([3,7,2,1,4], {a, b -> a - b}))
-endfunction
+endfunc
-function! Test_lambda_with_timer()
+func Test_lambda_with_timer()
if !has('timers')
return
endif
@@ -54,10 +54,10 @@ function! Test_lambda_with_timer()
call assert_true(s:n > m)
endfunc
-function! Test_lambda_with_partial()
+func Test_lambda_with_partial()
let l:Cb = function({... -> ['zero', a:1, a:2, a:3]}, ['one', 'two'])
call assert_equal(['zero', 'one', 'two', 'three'], l:Cb('three'))
-endfunction
+endfunc
function Test_lambda_fails()
call assert_equal(3, {a, b -> a + b}(1, 2))
@@ -70,59 +70,59 @@ func Test_not_lambda()
call assert_equal('foo', x['>'])
endfunc
-function! Test_lambda_capture_by_reference()
+func Test_lambda_capture_by_reference()
let v = 1
let l:F = {x -> x + v}
let v = 2
call assert_equal(12, l:F(10))
-endfunction
+endfunc
-function! Test_lambda_side_effect()
- function! s:update_and_return(arr)
+func Test_lambda_side_effect()
+ func! s:update_and_return(arr)
let a:arr[1] = 5
return a:arr
- endfunction
+ endfunc
- function! s:foo(arr)
+ func! s:foo(arr)
return {-> s:update_and_return(a:arr)}
- endfunction
+ endfunc
let arr = [3,2,1]
call assert_equal([3, 5, 1], s:foo(arr)())
-endfunction
+endfunc
-function! Test_lambda_refer_local_variable_from_other_scope()
- function! s:foo(X)
+func Test_lambda_refer_local_variable_from_other_scope()
+ func! s:foo(X)
return a:X() " refer l:x in s:bar()
- endfunction
+ endfunc
- function! s:bar()
+ func! s:bar()
let x = 123
return s:foo({-> x})
- endfunction
+ endfunc
call assert_equal(123, s:bar())
-endfunction
+endfunc
-function! Test_lambda_do_not_share_local_variable()
- function! s:define_funcs()
+func Test_lambda_do_not_share_local_variable()
+ func! s:define_funcs()
let l:One = {-> split(execute("let a = 'abc' | echo a"))[0]}
let l:Two = {-> exists("a") ? a : "no"}
return [l:One, l:Two]
- endfunction
+ endfunc
let l:F = s:define_funcs()
call assert_equal('no', l:F[1]())
call assert_equal('abc', l:F[0]())
call assert_equal('no', l:F[1]())
-endfunction
+endfunc
-function! Test_lambda_closure_counter()
- function! s:foo()
+func Test_lambda_closure_counter()
+ func! s:foo()
let x = 0
return {-> [execute("let x += 1"), x][-1]}
- endfunction
+ endfunc
let l:F = s:foo()
call garbagecollect()
@@ -130,52 +130,52 @@ function! Test_lambda_closure_counter()
call assert_equal(2, l:F())
call assert_equal(3, l:F())
call assert_equal(4, l:F())
-endfunction
+endfunc
-function! Test_lambda_with_a_var()
- function! s:foo()
+func Test_lambda_with_a_var()
+ func! s:foo()
let x = 2
return {... -> a:000 + [x]}
- endfunction
- function! s:bar()
+ endfunc
+ func! s:bar()
return s:foo()(1)
- endfunction
+ endfunc
call assert_equal([1, 2], s:bar())
-endfunction
+endfunc
-function! Test_lambda_call_lambda_from_lambda()
- function! s:foo(x)
+func Test_lambda_call_lambda_from_lambda()
+ func! s:foo(x)
let l:F1 = {-> {-> a:x}}
return {-> l:F1()}
- endfunction
+ endfunc
let l:F = s:foo(1)
call assert_equal(1, l:F()())
-endfunction
+endfunc
-function! Test_lambda_delfunc()
- function! s:gen()
+func Test_lambda_delfunc()
+ func! s:gen()
let pl = l:
let l:Foo = {-> get(pl, "Foo", get(pl, "Bar", {-> 0}))}
let l:Bar = l:Foo
delfunction l:Foo
return l:Bar
- endfunction
+ endfunc
let l:F = s:gen()
call assert_fails(':call l:F()', 'E933:')
-endfunction
+endfunc
-function! Test_lambda_scope()
- function! s:NewCounter()
+func Test_lambda_scope()
+ func! s:NewCounter()
let c = 0
return {-> [execute('let c += 1'), c][-1]}
- endfunction
+ endfunc
- function! s:NewCounter2()
+ func! s:NewCounter2()
return {-> [execute('let c += 100'), c][-1]}
- endfunction
+ endfunc
let l:C = s:NewCounter()
let l:D = s:NewCounter2()
@@ -183,37 +183,37 @@ function! Test_lambda_scope()
call assert_equal(1, l:C())
call assert_fails(':call l:D()', 'E121:')
call assert_equal(2, l:C())
-endfunction
+endfunc
-function! Test_lambda_share_scope()
- function! s:New()
+func Test_lambda_share_scope()
+ func! s:New()
let c = 0
let l:Inc0 = {-> [execute('let c += 1'), c][-1]}
let l:Dec0 = {-> [execute('let c -= 1'), c][-1]}
return [l:Inc0, l:Dec0]
- endfunction
+ endfunc
let [l:Inc, l:Dec] = s:New()
call assert_equal(1, l:Inc())
call assert_equal(2, l:Inc())
call assert_equal(1, l:Dec())
-endfunction
+endfunc
-function! Test_lambda_circular_reference()
- function! s:Foo()
+func Test_lambda_circular_reference()
+ func! s:Foo()
let d = {}
let d.f = {-> d}
return d.f
- endfunction
+ endfunc
call s:Foo()
call garbagecollect()
let i = 0 | while i < 10000 | call s:Foo() | let i+= 1 | endwhile
call garbagecollect()
-endfunction
+endfunc
-function! Test_lambda_combination()
+func Test_lambda_combination()
call assert_equal(2, {x -> {x -> x}}(1)(2))
call assert_equal(10, {y -> {x -> x(y)(10)}({y -> y})}({z -> z}))
call assert_equal(5.0, {x -> {y -> x / y}}(10)(2.0))
@@ -226,17 +226,17 @@ function! Test_lambda_combination()
let Z = {f -> {x -> f({y -> x(x)(y)})}({x -> f({y -> x(x)(y)})})}
let Fact = {f -> {x -> x == 0 ? 1 : x * f(x - 1)}}
call assert_equal(120, Z(Fact)(5))
-endfunction
+endfunc
-function! Test_closure_counter()
- function! s:foo()
+func Test_closure_counter()
+ func! s:foo()
let x = 0
- function! s:bar() closure
+ func! s:bar() closure
let x += 1
return x
- endfunction
+ endfunc
return function('s:bar')
- endfunction
+ endfunc
let l:F = s:foo()
call garbagecollect()
@@ -244,30 +244,30 @@ function! Test_closure_counter()
call assert_equal(2, l:F())
call assert_equal(3, l:F())
call assert_equal(4, l:F())
-endfunction
+endfunc
-function! Test_closure_unlet()
- function! s:foo()
+func Test_closure_unlet()
+ func! s:foo()
let x = 1
- function! s:bar() closure
+ func! s:bar() closure
unlet x
- endfunction
+ endfunc
call s:bar()
return l:
- endfunction
+ endfunc
call assert_false(has_key(s:foo(), 'x'))
call garbagecollect()
-endfunction
+endfunc
-function! LambdaFoo()
+func LambdaFoo()
let x = 0
- function! LambdaBar() closure
+ func! LambdaBar() closure
let x += 1
return x
- endfunction
+ endfunc
return function('LambdaBar')
-endfunction
+endfunc
func Test_closure_refcount()
let g:Count = LambdaFoo()
@@ -303,3 +303,8 @@ func Test_lambda_with_index()
let Extract = {-> function(List, ['foobar'])()[0]}
call assert_equal('foobar', Extract())
endfunc
+
+func Test_lambda_error()
+ " This was causing a crash
+ call assert_fails('ec{@{->{d->()()', 'E15')
+endfunc
diff --git a/src/nvim/testdir/test_listchars.vim b/src/nvim/testdir/test_listchars.vim
index 0bcbd9c4a5..c6e2ebd406 100644
--- a/src/nvim/testdir/test_listchars.vim
+++ b/src/nvim/testdir/test_listchars.vim
@@ -1,6 +1,8 @@
" Tests for 'listchars' display with 'list' and :list
+source check.vim
source view_util.vim
+source screendump.vim
func Test_listchars()
enew!
@@ -517,4 +519,34 @@ func Test_listchars_window_local()
set list& listchars&
endfunc
+func Test_listchars_foldcolumn()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, ['aaa', '', 'a', 'aaaaaa'])
+ vsplit
+ vsplit
+ windo set signcolumn=yes foldcolumn=1 winminwidth=0 nowrap list listchars=extends:>,precedes:<
+ END
+ call writefile(lines, 'XTest_listchars')
+
+ let buf = RunVimInTerminal('-S XTest_listchars', {'rows': 10, 'cols': 60})
+
+ call term_sendkeys(buf, "13\<C-W>>")
+ call VerifyScreenDump(buf, 'Test_listchars_01', {})
+ call term_sendkeys(buf, "\<C-W>>")
+ call VerifyScreenDump(buf, 'Test_listchars_02', {})
+ call term_sendkeys(buf, "\<C-W>>")
+ call VerifyScreenDump(buf, 'Test_listchars_03', {})
+ call term_sendkeys(buf, "\<C-W>>")
+ call VerifyScreenDump(buf, 'Test_listchars_04', {})
+ call term_sendkeys(buf, "\<C-W>>")
+ call VerifyScreenDump(buf, 'Test_listchars_05', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('XTest_listchars')
+endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim
index f6c404d390..aa66d86af1 100644
--- a/src/nvim/testdir/test_listdict.vim
+++ b/src/nvim/testdir/test_listdict.vim
@@ -516,22 +516,22 @@ func Test_dict_lock_operator()
endfunc
" No remove() of write-protected scope-level variable
-func! Tfunc(this_is_a_long_parameter_name)
+func Tfunc1(this_is_a_long_parameter_name)
call assert_fails("call remove(a:, 'this_is_a_long_parameter_name')", 'E742')
-endfun
+endfunc
func Test_dict_scope_var_remove()
- call Tfunc('testval')
+ call Tfunc1('testval')
endfunc
" No extend() of write-protected scope-level variable
func Test_dict_scope_var_extend()
call assert_fails("call extend(a:, {'this_is_a_long_parameter_name': 1234})", 'E742')
endfunc
-func! Tfunc(this_is_a_long_parameter_name)
+func Tfunc2(this_is_a_long_parameter_name)
call assert_fails("call extend(a:, {'this_is_a_long_parameter_name': 1234})", 'E742')
endfunc
func Test_dict_scope_var_extend_overwrite()
- call Tfunc('testval')
+ call Tfunc2('testval')
endfunc
" No :unlet of variable in locked scope
@@ -620,6 +620,49 @@ func Test_reverse_sort_uniq()
call assert_fails('call reverse("")', 'E899:')
endfunc
+" reduce a list or a blob
+func Test_reduce()
+ call assert_equal(1, reduce([], { acc, val -> acc + val }, 1))
+ call assert_equal(10, reduce([1, 3, 5], { acc, val -> acc + val }, 1))
+ call assert_equal(2 * (2 * ((2 * 1) + 2) + 3) + 4, reduce([2, 3, 4], { acc, val -> 2 * acc + val }, 1))
+ call assert_equal('a x y z', ['x', 'y', 'z']->reduce({ acc, val -> acc .. ' ' .. val}, 'a'))
+ call assert_equal(#{ x: 1, y: 1, z: 1 }, ['x', 'y', 'z']->reduce({ acc, val -> extend(acc, { val: 1 }) }, {}))
+ call assert_equal([0, 1, 2, 3], reduce([1, 2, 3], function('add'), [0]))
+
+ let l = ['x', 'y', 'z']
+ call assert_equal(42, reduce(l, function('get'), #{ x: #{ y: #{ z: 42 } } }))
+ call assert_equal(['x', 'y', 'z'], l)
+
+ call assert_equal(1, reduce([1], { acc, val -> acc + val }))
+ call assert_equal('x y z', reduce(['x', 'y', 'z'], { acc, val -> acc .. ' ' .. val }))
+ call assert_equal(120, range(1, 5)->reduce({ acc, val -> acc * val }))
+ call assert_fails("call reduce([], { acc, val -> acc + val })", 'E998: Reduce of an empty List with no initial value')
+
+ call assert_equal(1, reduce(0z, { acc, val -> acc + val }, 1))
+ call assert_equal(1 + 0xaf + 0xbf + 0xcf, reduce(0zAFBFCF, { acc, val -> acc + val }, 1))
+ call assert_equal(2 * (2 * 1 + 0xaf) + 0xbf, 0zAFBF->reduce({ acc, val -> 2 * acc + val }, 1))
+
+ call assert_equal(0xff, reduce(0zff, { acc, val -> acc + val }))
+ call assert_equal(2 * (2 * 0xaf + 0xbf) + 0xcf, reduce(0zAFBFCF, { acc, val -> 2 * acc + val }))
+ call assert_fails("call reduce(0z, { acc, val -> acc + val })", 'E998: Reduce of an empty Blob with no initial value')
+
+ call assert_fails("call reduce({}, { acc, val -> acc + val }, 1)", 'E897:')
+ call assert_fails("call reduce(0, { acc, val -> acc + val }, 1)", 'E897:')
+ call assert_fails("call reduce('', { acc, val -> acc + val }, 1)", 'E897:')
+
+ let g:lut = [1, 2, 3, 4]
+ func EvilRemove()
+ call remove(g:lut, 1)
+ return 1
+ endfunc
+ call assert_fails("call reduce(g:lut, { acc, val -> EvilRemove() }, 1)", 'E742:')
+ unlet g:lut
+ delfunc EvilRemove
+
+ call assert_equal(42, reduce(v:_null_list, function('add'), 42))
+ call assert_equal(42, reduce(v:_null_blob, function('add'), 42))
+endfunc
+
" splitting a string to a List
func Test_str_split()
call assert_equal(['aa', 'bb'], split(' aa bb '))
diff --git a/src/nvim/testdir/test_listlbr_utf8.vim b/src/nvim/testdir/test_listlbr_utf8.vim
index c38e0c5f3c..1f100d6244 100644
--- a/src/nvim/testdir/test_listlbr_utf8.vim
+++ b/src/nvim/testdir/test_listlbr_utf8.vim
@@ -69,6 +69,16 @@ func Test_nolinebreak_with_list()
call s:close_windows()
endfunc
+" this was causing a crash
+func Test_linebreak_with_list_and_tabs()
+ set linebreak list listchars=tab:⇤\ ⇥ tabstop=100
+ new
+ call setline(1, "\t\t\ttext")
+ redraw
+ bwipe!
+ set nolinebreak nolist listchars&vim tabstop=8
+endfunc
+
func Test_linebreak_with_nolist()
call s:test_windows('setl nolist')
call setline(1, "\t*mask = nil;")
diff --git a/src/nvim/testdir/test_marks.vim b/src/nvim/testdir/test_marks.vim
index b3035d73ce..00ee8f6d6a 100644
--- a/src/nvim/testdir/test_marks.vim
+++ b/src/nvim/testdir/test_marks.vim
@@ -1,6 +1,6 @@
" Test that a deleted mark is restored after delete-undo-redo-undo.
-function! Test_Restore_DelMark()
+func Test_Restore_DelMark()
enew!
call append(0, [" textline A", " textline B", " textline C"])
normal! 2gg
@@ -11,10 +11,10 @@ function! Test_Restore_DelMark()
call assert_equal(2, pos[1])
call assert_equal(1, pos[2])
enew!
-endfunction
+endfunc
" Test that CTRL-A and CTRL-X updates last changed mark '[, '].
-function! Test_Incr_Marks()
+func Test_Incr_Marks()
enew!
call append(0, ["123 123 123", "123 123 123", "123 123 123"])
normal! gg
@@ -23,7 +23,17 @@ function! Test_Incr_Marks()
call assert_equal("123 XXXXXXX", getline(2))
call assert_equal("XXX 123 123", getline(3))
enew!
-endfunction
+endfunc
+
+func Test_previous_jump_mark()
+ new
+ call setline(1, ['']->repeat(6))
+ normal Ggg
+ call assert_equal(6, getpos("''")[1])
+ normal jjjjj
+ call assert_equal(6, getpos("''")[1])
+ bwipe!
+endfunc
func Test_setpos()
new Xone
@@ -207,6 +217,21 @@ func Test_mark_error()
call assert_fails('mark _', 'E191:')
endfunc
+" Test for :lockmarks when pasting content
+func Test_lockmarks_with_put()
+ new
+ call append(0, repeat(['sky is blue'], 4))
+ normal gg
+ 1,2yank r
+ put r
+ normal G
+ lockmarks put r
+ call assert_equal(2, line("'["))
+ call assert_equal(3, line("']"))
+
+ bwipe!
+endfunc
+
" Test for the getmarklist() function
func Test_getmarklist()
new
@@ -231,3 +256,5 @@ func Test_getmarklist()
call assert_equal([], {}->getmarklist())
close!
endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_matchadd_conceal.vim b/src/nvim/testdir/test_matchadd_conceal.vim
index 2cbaf5cb76..29a2c30b0d 100644
--- a/src/nvim/testdir/test_matchadd_conceal.vim
+++ b/src/nvim/testdir/test_matchadd_conceal.vim
@@ -7,7 +7,7 @@ source shared.vim
source term_util.vim
source view_util.vim
-function! Test_simple_matchadd()
+func Test_simple_matchadd()
new
1put='# This is a Test'
@@ -333,7 +333,7 @@ func Test_matchadd_and_syn_conceal()
call assert_notequal(screenattr(1, 10) , screenattr(1, 11))
call assert_notequal(screenattr(1, 11) , screenattr(1, 12))
call assert_equal(screenattr(1, 11) , screenattr(1, 32))
-endfunction
+endfunc
func Test_cursor_column_in_concealed_line_after_window_scroll()
CheckRunVimInTerminal
diff --git a/src/nvim/testdir/test_matchadd_conceal_utf8.vim b/src/nvim/testdir/test_matchadd_conceal_utf8.vim
index 7bfac13ad8..1d0c740734 100644
--- a/src/nvim/testdir/test_matchadd_conceal_utf8.vim
+++ b/src/nvim/testdir/test_matchadd_conceal_utf8.vim
@@ -3,19 +3,19 @@ if !has('conceal')
finish
endif
-function! s:screenline(lnum) abort
+func s:screenline(lnum) abort
let line = []
for c in range(1, winwidth(0))
call add(line, nr2char(a:lnum->screenchar(c)))
endfor
return s:trim(join(line, ''))
-endfunction
+endfunc
-function! s:trim(str) abort
+func s:trim(str) abort
return matchstr(a:str,'^\s*\zs.\{-}\ze\s*$')
-endfunction
+endfunc
-function! Test_match_using_multibyte_conceal_char()
+func Test_match_using_multibyte_conceal_char()
new
setlocal concealcursor=n conceallevel=1
diff --git a/src/nvim/testdir/test_matchfuzzy.vim b/src/nvim/testdir/test_matchfuzzy.vim
new file mode 100644
index 0000000000..abcc9b40c1
--- /dev/null
+++ b/src/nvim/testdir/test_matchfuzzy.vim
@@ -0,0 +1,248 @@
+" Tests for fuzzy matching
+
+source shared.vim
+source check.vim
+
+" Test for matchfuzzy()
+func Test_matchfuzzy()
+ call assert_fails('call matchfuzzy(10, "abc")', 'E686:')
+ " Needs v8.2.1183; match the final error that's thrown for now
+ " call assert_fails('call matchfuzzy(["abc"], [])', 'E730:')
+ call assert_fails('call matchfuzzy(["abc"], [])', 'E475:')
+ call assert_fails("let x = matchfuzzy(v:_null_list, 'foo')", 'E686:')
+ call assert_fails('call matchfuzzy(["abc"], v:_null_string)', 'E475:')
+ call assert_equal([], matchfuzzy([], 'abc'))
+ call assert_equal([], matchfuzzy(['abc'], ''))
+ call assert_equal(['abc'], matchfuzzy(['abc', 10], 'ac'))
+ call assert_equal([], matchfuzzy([10, 20], 'ac'))
+ call assert_equal(['abc'], matchfuzzy(['abc'], 'abc'))
+ call assert_equal(['crayon', 'camera'], matchfuzzy(['camera', 'crayon'], 'cra'))
+ call assert_equal(['aabbaa', 'aaabbbaaa', 'aaaabbbbaaaa', 'aba'], matchfuzzy(['aba', 'aabbaa', 'aaabbbaaa', 'aaaabbbbaaaa'], 'aa'))
+ call assert_equal(['one'], matchfuzzy(['one', 'two'], 'one'))
+ call assert_equal(['oneTwo', 'onetwo'], matchfuzzy(['onetwo', 'oneTwo'], 'oneTwo'))
+ call assert_equal(['onetwo', 'one_two'], matchfuzzy(['onetwo', 'one_two'], 'oneTwo'))
+ call assert_equal(['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'], matchfuzzy(['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'], 'aa'))
+ call assert_equal(256, matchfuzzy([repeat('a', 256)], repeat('a', 256))[0]->len())
+ call assert_equal([], matchfuzzy([repeat('a', 300)], repeat('a', 257)))
+ " matches with same score should not be reordered
+ let l = ['abc1', 'abc2', 'abc3']
+ call assert_equal(l, l->matchfuzzy('abc'))
+
+ " Tests for match preferences
+ " preference for camel case match
+ call assert_equal(['oneTwo', 'onetwo'], ['onetwo', 'oneTwo']->matchfuzzy('onetwo'))
+ " preference for match after a separator (_ or space)
+ call assert_equal(['onetwo', 'one_two', 'one two'], ['onetwo', 'one_two', 'one two']->matchfuzzy('onetwo'))
+ " preference for leading letter match
+ call assert_equal(['onetwo', 'xonetwo'], ['xonetwo', 'onetwo']->matchfuzzy('onetwo'))
+ " preference for sequential match
+ call assert_equal(['onetwo', 'oanbectdweo'], ['oanbectdweo', 'onetwo']->matchfuzzy('onetwo'))
+ " non-matching leading letter(s) penalty
+ call assert_equal(['xonetwo', 'xxonetwo'], ['xxonetwo', 'xonetwo']->matchfuzzy('onetwo'))
+ " total non-matching letter(s) penalty
+ call assert_equal(['one', 'onex', 'onexx'], ['onexx', 'one', 'onex']->matchfuzzy('one'))
+ " prefer complete matches over separator matches
+ call assert_equal(['.vim/vimrc', '.vim/vimrc_colors', '.vim/v_i_m_r_c'], ['.vim/vimrc', '.vim/vimrc_colors', '.vim/v_i_m_r_c']->matchfuzzy('vimrc'))
+ " gap penalty
+ call assert_equal(['xxayybxxxx', 'xxayyybxxx', 'xxayyyybxx'], ['xxayyyybxx', 'xxayyybxxx', 'xxayybxxxx']->matchfuzzy('ab'))
+ " path separator vs word separator
+ call assert_equal(['color/setup.vim', 'color\\setup.vim', 'color setup.vim', 'color_setup.vim', 'colorsetup.vim'], matchfuzzy(['colorsetup.vim', 'color setup.vim', 'color/setup.vim', 'color_setup.vim', 'color\\setup.vim'], 'setup.vim'))
+
+ " match multiple words (separated by space)
+ call assert_equal(['foo bar baz'], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzy('baz foo'))
+ call assert_equal([], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzy('one two'))
+ call assert_equal([], ['foo bar']->matchfuzzy(" \t "))
+
+ " test for matching a sequence of words
+ call assert_equal(['bar foo'], ['foo bar', 'bar foo', 'foobar', 'barfoo']->matchfuzzy('bar foo', {'matchseq' : 1}))
+ call assert_equal([#{text: 'two one'}], [#{text: 'one two'}, #{text: 'two one'}]->matchfuzzy('two one', #{key: 'text', matchseq: v:true}))
+
+ %bw!
+ eval ['somebuf', 'anotherone', 'needle', 'yetanotherone']->map({_, v -> bufadd(v) + bufload(v)})
+ let l = getbufinfo()->map({_, v -> v.name})->matchfuzzy('ndl')
+ call assert_equal(1, len(l))
+ call assert_match('needle', l[0])
+
+ " Test for fuzzy matching dicts
+ let l = [{'id' : 5, 'val' : 'crayon'}, {'id' : 6, 'val' : 'camera'}]
+ call assert_equal([{'id' : 6, 'val' : 'camera'}], matchfuzzy(l, 'cam', {'text_cb' : {v -> v.val}}))
+ call assert_equal([{'id' : 6, 'val' : 'camera'}], matchfuzzy(l, 'cam', {'key' : 'val'}))
+ call assert_equal([], matchfuzzy(l, 'day', {'text_cb' : {v -> v.val}}))
+ call assert_equal([], matchfuzzy(l, 'day', {'key' : 'val'}))
+ call assert_fails("let x = matchfuzzy(l, 'cam', 'random')", 'E715:')
+ call assert_equal([], matchfuzzy(l, 'day', {'text_cb' : {v -> []}}))
+ call assert_equal([], matchfuzzy(l, 'day', {'text_cb' : {v -> 1}}))
+ call assert_fails("let x = matchfuzzy(l, 'day', {'text_cb' : {a, b -> 1}})", 'E119:')
+ call assert_equal([], matchfuzzy(l, 'cam'))
+ " Nvim's callback implementation is different, so E6000 is expected instead,
+ " but we need v8.2.1183 to assert it
+ " call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E921:')
+ " call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E6000:')
+ call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E475:')
+ " call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : []})", 'E730:')
+ call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : []})", 'E475:')
+ call assert_fails("let x = matchfuzzy(l, 'cam', v:_null_dict)", 'E715:')
+ call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : v:_null_string})", 'E475:')
+ " Nvim doesn't have null functions
+ " call assert_fails("let x = matchfuzzy(l, 'foo', {'text_cb' : test_null_function()})", 'E475:')
+ " matches with same score should not be reordered
+ let l = [#{text: 'abc', id: 1}, #{text: 'abc', id: 2}, #{text: 'abc', id: 3}]
+ call assert_equal(l, l->matchfuzzy('abc', #{key: 'text'}))
+
+ let l = [{'id' : 5, 'name' : 'foo'}, {'id' : 6, 'name' : []}, {'id' : 7}]
+ call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : 'name'})", 'E730:')
+
+ " Test in latin1 encoding
+ let save_enc = &encoding
+ " Nvim supports utf-8 encoding only
+ " set encoding=latin1
+ call assert_equal(['abc'], matchfuzzy(['abc'], 'abc'))
+ let &encoding = save_enc
+endfunc
+
+" Test for the matchfuzzypos() function
+func Test_matchfuzzypos()
+ call assert_equal([['curl', 'world'], [[2,3], [2,3]], [128, 127]], matchfuzzypos(['world', 'curl'], 'rl'))
+ call assert_equal([['curl', 'world'], [[2,3], [2,3]], [128, 127]], matchfuzzypos(['world', 'one', 'curl'], 'rl'))
+ call assert_equal([['hello', 'hello world hello world'],
+ \ [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [275, 257]],
+ \ matchfuzzypos(['hello world hello world', 'hello', 'world'], 'hello'))
+ call assert_equal([['aaaaaaa'], [[0, 1, 2]], [191]], matchfuzzypos(['aaaaaaa'], 'aaa'))
+ call assert_equal([['a b'], [[0, 3]], [219]], matchfuzzypos(['a b'], 'a b'))
+ call assert_equal([['a b'], [[0, 3]], [219]], matchfuzzypos(['a b'], 'a b'))
+ call assert_equal([['a b'], [[0]], [112]], matchfuzzypos(['a b'], ' a '))
+ call assert_equal([[], [], []], matchfuzzypos(['a b'], ' '))
+ call assert_equal([[], [], []], matchfuzzypos(['world', 'curl'], 'ab'))
+ let x = matchfuzzypos([repeat('a', 256)], repeat('a', 256))
+ call assert_equal(range(256), x[1][0])
+ call assert_equal([[], [], []], matchfuzzypos([repeat('a', 300)], repeat('a', 257)))
+ call assert_equal([[], [], []], matchfuzzypos([], 'abc'))
+
+ " match in a long string
+ call assert_equal([[repeat('x', 300) .. 'abc'], [[300, 301, 302]], [-135]],
+ \ matchfuzzypos([repeat('x', 300) .. 'abc'], 'abc'))
+
+ " preference for camel case match
+ call assert_equal([['xabcxxaBc'], [[6, 7, 8]], [189]], matchfuzzypos(['xabcxxaBc'], 'abc'))
+ " preference for match after a separator (_ or space)
+ call assert_equal([['xabx_ab'], [[5, 6]], [145]], matchfuzzypos(['xabx_ab'], 'ab'))
+ " preference for leading letter match
+ call assert_equal([['abcxabc'], [[0, 1]], [150]], matchfuzzypos(['abcxabc'], 'ab'))
+ " preference for sequential match
+ call assert_equal([['aobncedone'], [[7, 8, 9]], [158]], matchfuzzypos(['aobncedone'], 'one'))
+ " best recursive match
+ call assert_equal([['xoone'], [[2, 3, 4]], [168]], matchfuzzypos(['xoone'], 'one'))
+
+ " match multiple words (separated by space)
+ call assert_equal([['foo bar baz'], [[8, 9, 10, 0, 1, 2]], [369]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('baz foo'))
+ call assert_equal([[], [], []], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('one two'))
+ call assert_equal([[], [], []], ['foo bar']->matchfuzzypos(" \t "))
+ call assert_equal([['grace'], [[1, 2, 3, 4, 2, 3, 4, 0, 1, 2, 3, 4]], [657]], ['grace']->matchfuzzypos('race ace grace'))
+
+ let l = [{'id' : 5, 'val' : 'crayon'}, {'id' : 6, 'val' : 'camera'}]
+ call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [192]],
+ \ matchfuzzypos(l, 'cam', {'text_cb' : {v -> v.val}}))
+ call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [192]],
+ \ matchfuzzypos(l, 'cam', {'key' : 'val'}))
+ call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> v.val}}))
+ call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'key' : 'val'}))
+ call assert_fails("let x = matchfuzzypos(l, 'cam', 'random')", 'E715:')
+ call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> []}}))
+ call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> 1}}))
+ call assert_fails("let x = matchfuzzypos(l, 'day', {'text_cb' : {a, b -> 1}})", 'E119:')
+ call assert_equal([[], [], []], matchfuzzypos(l, 'cam'))
+ " Nvim's callback implementation is different, so E6000 is expected instead,
+ " but we need v8.2.1183 to assert it
+ " call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E921:')
+ " call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E6000:')
+ call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E475:')
+ " call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : []})", 'E730:')
+ call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : []})", 'E475:')
+ call assert_fails("let x = matchfuzzypos(l, 'cam', v:_null_dict)", 'E715:')
+ call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : v:_null_string})", 'E475:')
+ " Nvim doesn't have null functions
+ " call assert_fails("let x = matchfuzzypos(l, 'foo', {'text_cb' : test_null_function()})", 'E475:')
+
+ let l = [{'id' : 5, 'name' : 'foo'}, {'id' : 6, 'name' : []}, {'id' : 7}]
+ call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : 'name'})", 'E730:')
+endfunc
+
+" Test for matchfuzzy() with multibyte characters
+func Test_matchfuzzy_mbyte()
+ CheckFeature multi_lang
+ call assert_equal(['ンヹㄇヺヴ'], matchfuzzy(['ンヹㄇヺヴ'], 'ヹヺ'))
+ " reverse the order of characters
+ call assert_equal([], matchfuzzy(['ンヹㄇヺヴ'], 'ヺヹ'))
+ call assert_equal(['αβΩxxx', 'xαxβxΩx'],
+ \ matchfuzzy(['αβΩxxx', 'xαxβxΩx'], 'αβΩ'))
+ call assert_equal(['ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ', 'πbπ'],
+ \ matchfuzzy(['πbπ', 'ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ'], 'ππ'))
+
+ " match multiple words (separated by space)
+ call assert_equal(['세 마리의 작은 돼지'], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzy('돼지 마리의'))
+ call assert_equal([], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzy('파란 하늘'))
+
+ " preference for camel case match
+ call assert_equal(['oneĄwo', 'oneąwo'],
+ \ ['oneąwo', 'oneĄwo']->matchfuzzy('oneąwo'))
+ " preference for complete match then match after separator (_ or space)
+ call assert_equal(['ⅠⅡabㄟㄠ'] + sort(['ⅠⅡa_bㄟㄠ', 'ⅠⅡa bㄟㄠ']),
+ \ ['ⅠⅡabㄟㄠ', 'ⅠⅡa bㄟㄠ', 'ⅠⅡa_bㄟㄠ']->matchfuzzy('ⅠⅡabㄟㄠ'))
+ " preference for match after a separator (_ or space)
+ call assert_equal(['ㄓㄔabㄟㄠ', 'ㄓㄔa_bㄟㄠ', 'ㄓㄔa bㄟㄠ'],
+ \ ['ㄓㄔa_bㄟㄠ', 'ㄓㄔa bㄟㄠ', 'ㄓㄔabㄟㄠ']->matchfuzzy('ㄓㄔabㄟㄠ'))
+ " preference for leading letter match
+ call assert_equal(['ŗŝţũŵż', 'xŗŝţũŵż'],
+ \ ['xŗŝţũŵż', 'ŗŝţũŵż']->matchfuzzy('ŗŝţũŵż'))
+ " preference for sequential match
+ call assert_equal(['ㄞㄡㄤfffifl', 'ㄞaㄡbㄤcffdfiefl'],
+ \ ['ㄞaㄡbㄤcffdfiefl', 'ㄞㄡㄤfffifl']->matchfuzzy('ㄞㄡㄤfffifl'))
+ " non-matching leading letter(s) penalty
+ call assert_equal(['xㄞㄡㄤfffifl', 'xxㄞㄡㄤfffifl'],
+ \ ['xxㄞㄡㄤfffifl', 'xㄞㄡㄤfffifl']->matchfuzzy('ㄞㄡㄤfffifl'))
+ " total non-matching letter(s) penalty
+ call assert_equal(['ŗŝţ', 'ŗŝţx', 'ŗŝţxx'],
+ \ ['ŗŝţxx', 'ŗŝţ', 'ŗŝţx']->matchfuzzy('ŗŝţ'))
+endfunc
+
+" Test for matchfuzzypos() with multibyte characters
+func Test_matchfuzzypos_mbyte()
+ CheckFeature multi_lang
+ call assert_equal([['こんにちは世界'], [[0, 1, 2, 3, 4]], [273]],
+ \ matchfuzzypos(['こんにちは世界'], 'こんにちは'))
+ call assert_equal([['ンヹㄇヺヴ'], [[1, 3]], [88]], matchfuzzypos(['ンヹㄇヺヴ'], 'ヹヺ'))
+ " reverse the order of characters
+ call assert_equal([[], [], []], matchfuzzypos(['ンヹㄇヺヴ'], 'ヺヹ'))
+ call assert_equal([['αβΩxxx', 'xαxβxΩx'], [[0, 1, 2], [1, 3, 5]], [222, 113]],
+ \ matchfuzzypos(['αβΩxxx', 'xαxβxΩx'], 'αβΩ'))
+ call assert_equal([['ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ', 'πbπ'],
+ \ [[0, 1], [0, 1], [0, 1], [0, 2]], [151, 148, 145, 110]],
+ \ matchfuzzypos(['πbπ', 'ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ'], 'ππ'))
+ call assert_equal([['ααααααα'], [[0, 1, 2]], [191]],
+ \ matchfuzzypos(['ααααααα'], 'ααα'))
+
+ call assert_equal([[], [], []], matchfuzzypos(['ンヹㄇ', 'ŗŝţ'], 'fffifl'))
+ let x = matchfuzzypos([repeat('Ψ', 256)], repeat('Ψ', 256))
+ call assert_equal(range(256), x[1][0])
+ call assert_equal([[], [], []], matchfuzzypos([repeat('✓', 300)], repeat('✓', 257)))
+
+ " match multiple words (separated by space)
+ call assert_equal([['세 마리의 작은 돼지'], [[9, 10, 2, 3, 4]], [328]], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzypos('돼지 마리의'))
+ call assert_equal([[], [], []], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzypos('파란 하늘'))
+
+ " match in a long string
+ call assert_equal([[repeat('ぶ', 300) .. 'ẼẼẼ'], [[300, 301, 302]], [-135]],
+ \ matchfuzzypos([repeat('ぶ', 300) .. 'ẼẼẼ'], 'ẼẼẼ'))
+ " preference for camel case match
+ call assert_equal([['xѳѵҁxxѳѴҁ'], [[6, 7, 8]], [189]], matchfuzzypos(['xѳѵҁxxѳѴҁ'], 'ѳѵҁ'))
+ " preference for match after a separator (_ or space)
+ call assert_equal([['xちだx_ちだ'], [[5, 6]], [145]], matchfuzzypos(['xちだx_ちだ'], 'ちだ'))
+ " preference for leading letter match
+ call assert_equal([['ѳѵҁxѳѵҁ'], [[0, 1]], [150]], matchfuzzypos(['ѳѵҁxѳѵҁ'], 'ѳѵ'))
+ " preference for sequential match
+ call assert_equal([['aンbヹcㄇdンヹㄇ'], [[7, 8, 9]], [158]], matchfuzzypos(['aンbヹcㄇdンヹㄇ'], 'ンヹㄇ'))
+ " best recursive match
+ call assert_equal([['xффйд'], [[2, 3, 4]], [168]], matchfuzzypos(['xффйд'], 'фйд'))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim
index e0286548d9..9c84d77dd2 100644
--- a/src/nvim/testdir/test_messages.vim
+++ b/src/nvim/testdir/test_messages.vim
@@ -40,7 +40,7 @@ endfunc
" indicator (e.g., "-- INSERT --") when ":stopinsert" is invoked. Message
" output could then be disturbed when 'cmdheight' was greater than one.
" This test ensures that the bugfix for this issue remains in place.
-function! Test_stopinsert_does_not_break_message_output()
+func Test_stopinsert_does_not_break_message_output()
set cmdheight=2
redraw!
@@ -55,7 +55,7 @@ function! Test_stopinsert_does_not_break_message_output()
redraw!
set cmdheight&
-endfunction
+endfunc
func Test_message_completion()
call feedkeys(":message \<C-A>\<C-B>\"\<CR>", 'tx')
diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim
index 057895047d..a8e50af510 100644
--- a/src/nvim/testdir/test_mksession.vim
+++ b/src/nvim/testdir/test_mksession.vim
@@ -309,6 +309,31 @@ func Test_mksession_buffer_count()
set nohidden
endfunc
+func Test_mksession_buffer_order()
+ %bwipe!
+ e Xfoo | e Xbar | e Xbaz | e Xqux
+ bufdo write
+ mksession! Xtest_mks.out
+
+ " Verify that loading the session preserves order of buffers
+ %bwipe!
+ source Xtest_mks.out
+
+ let s:buf_info = getbufinfo()
+ call assert_true(s:buf_info[0]['name'] =~# 'Xfoo$')
+ call assert_true(s:buf_info[1]['name'] =~# 'Xbar$')
+ call assert_true(s:buf_info[2]['name'] =~# 'Xbaz$')
+ call assert_true(s:buf_info[3]['name'] =~# 'Xqux$')
+
+ " Clean up.
+ call delete('Xfoo')
+ call delete('Xbar')
+ call delete('Xbaz')
+ call delete('Xqux')
+ call delete('Xtest_mks.out')
+ %bwipe!
+endfunc
+
if has('extra_search')
func Test_mksession_hlsearch()
@@ -696,6 +721,36 @@ func Test_mksession_foldopt()
set sessionoptions&
endfunc
+" Test for mksession with "help" but not "options" in 'sessionoptions'
+func Test_mksession_help_noopt()
+ set sessionoptions-=options
+ set sessionoptions+=help
+ help
+ let fname = expand('%')
+ mksession! Xtest_mks.out
+ bwipe
+
+ source Xtest_mks.out
+ call assert_equal('help', &buftype)
+ call assert_equal('help', &filetype)
+ call assert_equal(fname, expand('%'))
+ call assert_false(&modifiable)
+ call assert_true(&readonly)
+
+ helpclose
+ help index
+ let fname = expand('%')
+ mksession! Xtest_mks.out
+ bwipe
+
+ source Xtest_mks.out
+ call assert_equal('help', &buftype)
+ call assert_equal(fname, expand('%'))
+
+ call delete('Xtest_mks.out')
+ set sessionoptions&
+endfunc
+
" Test for mksession with window position
func Test_mksession_winpos()
if !has('gui_running')
diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim
index aff22f5d01..5b7cf6fee5 100644
--- a/src/nvim/testdir/test_normal.vim
+++ b/src/nvim/testdir/test_normal.vim
@@ -1812,7 +1812,15 @@ fun! Test_normal33_g_cmd2()
call assert_equal(87, col('.'))
call assert_equal('E', getreg(0))
+ " Test for gM with Tab characters
+ call setline('.', "\ta\tb\tc\td\te\tf")
+ norm! gMyl
+ call assert_equal(6, col('.'))
+ call assert_equal("c", getreg(0))
+
" Test for g Ctrl-G
+ call setline('.', lineC)
+ norm! 60gMyl
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)
@@ -2759,4 +2767,16 @@ func Test_normal_count_after_operator()
bw!
endfunc
+func Test_normal_gj_on_extra_wide_char()
+ new | 25vsp
+ let text='1 foooooooo ar e ins‍zwe1 foooooooo ins‍zwei' .
+ \ ' i drei vier fünf sechs sieben acht un zehn elf zwöfl' .
+ \ ' dreizehn v ierzehn fünfzehn'
+ put =text
+ call cursor(2,1)
+ norm! gj
+ call assert_equal([0,2,25,0], getpos('.'))
+ bw!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_number.vim b/src/nvim/testdir/test_number.vim
index d737ebe9f0..dfbdc0bffd 100644
--- a/src/nvim/testdir/test_number.vim
+++ b/src/nvim/testdir/test_number.vim
@@ -284,10 +284,10 @@ func Test_relativenumber_colors()
" Default colors
call VerifyScreenDump(buf, 'Test_relnr_colors_1', {})
- call term_sendkeys(buf, ":hi LineNrAbove ctermfg=blue\<CR>")
+ call term_sendkeys(buf, ":hi LineNrAbove ctermfg=blue\<CR>:\<CR>")
call VerifyScreenDump(buf, 'Test_relnr_colors_2', {})
- call term_sendkeys(buf, ":hi LineNrBelow ctermfg=green\<CR>")
+ call term_sendkeys(buf, ":hi LineNrBelow ctermfg=green\<CR>:\<CR>")
call VerifyScreenDump(buf, 'Test_relnr_colors_3', {})
call term_sendkeys(buf, ":hi clear LineNrAbove\<CR>")
diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim
index 5946732937..8612b7013b 100644
--- a/src/nvim/testdir/test_options.vim
+++ b/src/nvim/testdir/test_options.vim
@@ -22,16 +22,16 @@ func Test_whichwrap()
call assert_equal('h', &whichwrap)
set whichwrap&
-endfunction
+endfunc
-function! Test_isfname()
+func Test_isfname()
" This used to cause Vim to access uninitialized memory.
set isfname=
call assert_equal("~X", expand("~X"))
set isfname&
-endfunction
+endfunc
-function Test_wildchar()
+func Test_wildchar()
" Empty 'wildchar' used to access invalid memory.
call assert_fails('set wildchar=', 'E521:')
call assert_fails('set wildchar=abc', 'E521:')
@@ -42,7 +42,7 @@ function Test_wildchar()
let a=execute('set wildchar?')
call assert_equal("\n wildchar=<Esc>", a)
set wildchar&
-endfunction
+endfunc
func Test_wildoptions()
set wildoptions=
@@ -51,7 +51,7 @@ func Test_wildoptions()
call assert_equal('tagfile', &wildoptions)
endfunc
-function! Test_options()
+func Test_options_command()
let caught = 'ok'
try
options
@@ -88,9 +88,9 @@ function! Test_options()
" close option-window
close
-endfunction
+endfunc
-function! Test_path_keep_commas()
+func Test_path_keep_commas()
" Test that changing 'path' keeps two commas.
set path=foo,,bar
set path-=bar
@@ -98,7 +98,7 @@ function! Test_path_keep_commas()
call assert_equal('foo,,bar', &path)
set path&
-endfunction
+endfunc
func Test_filetype_valid()
set ft=valid_name
@@ -259,6 +259,8 @@ func Test_set_errors()
call assert_fails('set shiftwidth=-1', 'E487:')
call assert_fails('set sidescroll=-1', 'E487:')
call assert_fails('set tabstop=-1', 'E487:')
+ call assert_fails('set tabstop=10000', 'E474:')
+ call assert_fails('set tabstop=5500000000', 'E474:')
call assert_fails('set textwidth=-1', 'E487:')
call assert_fails('set timeoutlen=-1', 'E487:')
call assert_fails('set updatecount=-1', 'E487:')
@@ -312,21 +314,50 @@ func Test_set_errors()
set modifiable&
endfunc
+func CheckWasSet(name)
+ let verb_cm = execute('verbose set ' .. a:name .. '?')
+ call assert_match('Last set from.*test_options.vim', verb_cm)
+endfunc
+func CheckWasNotSet(name)
+ let verb_cm = execute('verbose set ' .. a:name .. '?')
+ call assert_notmatch('Last set from', verb_cm)
+endfunc
+
" Must be executed before other tests that set 'term'.
func Test_000_term_option_verbose()
if has('nvim') || has('gui_running')
return
endif
- let verb_cm = execute('verbose set t_cm')
- call assert_notmatch('Last set from', verb_cm)
+
+ call CheckWasNotSet('t_cm')
let term_save = &term
set term=ansi
- let verb_cm = execute('verbose set t_cm')
- call assert_match('Last set from.*test_options.vim', verb_cm)
+ call CheckWasSet('t_cm')
let &term = term_save
endfunc
+func Test_copy_context()
+ setlocal list
+ call CheckWasSet('list')
+ split
+ call CheckWasSet('list')
+ quit
+ setlocal nolist
+
+ set ai
+ call CheckWasSet('ai')
+ set filetype=perl
+ call CheckWasSet('filetype')
+ set fo=tcroq
+ call CheckWasSet('fo')
+
+ split Xsomebuf
+ call CheckWasSet('ai')
+ call CheckWasNotSet('filetype')
+ call CheckWasSet('fo')
+endfunc
+
func Test_set_ttytype()
" Nvim does not support 'ttytype'.
if !has('nvim') && !has('gui_running') && has('unix')
@@ -368,6 +399,13 @@ func Test_set_all()
set tw& iskeyword& splitbelow&
endfunc
+func Test_set_one_column()
+ let out_mult = execute('set all')->split("\n")
+ let out_one = execute('set! all')->split("\n")
+ " one column should be two to four times as many lines
+ call assert_inrange(len(out_mult) * 2, len(out_mult) * 4, len(out_one))
+endfunc
+
func Test_set_values()
" The file is only generated when running "make test" in the src directory.
if filereadable('opt_test.vim')
@@ -647,6 +685,19 @@ func Test_buftype()
close!
endfunc
+" Test for the 'shellquote' option
+func Test_shellquote()
+ CheckUnix
+ set shellquote=#
+ set verbose=20
+ redir => v
+ silent! !echo Hello
+ redir END
+ set verbose&
+ set shellquote&
+ call assert_match(': "#echo Hello#"', v)
+endfunc
+
" Test for setting option values using v:false and v:true
func Test_opt_boolean()
set number&
diff --git a/src/nvim/testdir/test_packadd.vim b/src/nvim/testdir/test_packadd.vim
new file mode 100644
index 0000000000..fcb8b8033b
--- /dev/null
+++ b/src/nvim/testdir/test_packadd.vim
@@ -0,0 +1,361 @@
+" Tests for 'packpath' and :packadd
+
+
+func SetUp()
+ let s:topdir = getcwd() . '/Xdir'
+ exe 'set packpath=' . s:topdir
+ let s:plugdir = s:topdir . '/pack/mine/opt/mytest'
+endfunc
+
+func TearDown()
+ call delete(s:topdir, 'rf')
+endfunc
+
+func Test_packadd()
+ if !exists('s:plugdir')
+ echomsg 'when running this test manually, call SetUp() first'
+ return
+ endif
+
+ call mkdir(s:plugdir . '/plugin/also', 'p')
+ call mkdir(s:plugdir . '/ftdetect', 'p')
+ call mkdir(s:plugdir . '/after', 'p')
+ set rtp&
+ let rtp = &rtp
+ filetype on
+
+ let rtp_entries = split(rtp, ',')
+ for entry in rtp_entries
+ if entry =~? '\<after\>'
+ let first_after_entry = entry
+ break
+ endif
+ endfor
+
+ exe 'split ' . s:plugdir . '/plugin/test.vim'
+ call setline(1, 'let g:plugin_works = 42')
+ wq
+
+ exe 'split ' . s:plugdir . '/plugin/also/loaded.vim'
+ call setline(1, 'let g:plugin_also_works = 77')
+ wq
+
+ exe 'split ' . s:plugdir . '/ftdetect/test.vim'
+ call setline(1, 'let g:ftdetect_works = 17')
+ wq
+
+ packadd mytest
+
+ call assert_equal(42, g:plugin_works)
+ call assert_equal(77, g:plugin_also_works)
+ call assert_equal(17, g:ftdetect_works)
+ call assert_true(len(&rtp) > len(rtp))
+ call assert_match('/testdir/Xdir/pack/mine/opt/mytest\($\|,\)', &rtp)
+
+ let new_after = match(&rtp, '/testdir/Xdir/pack/mine/opt/mytest/after,')
+ let forwarded = substitute(first_after_entry, '\\', '[/\\\\]', 'g')
+ let old_after = match(&rtp, ',' . forwarded . '\>')
+ call assert_true(new_after > 0, 'rtp is ' . &rtp)
+ call assert_true(old_after > 0, 'match ' . forwarded . ' in ' . &rtp)
+ call assert_true(new_after < old_after, 'rtp is ' . &rtp)
+
+ " NOTE: '/.../opt/myte' forwardly matches with '/.../opt/mytest'
+ call mkdir(fnamemodify(s:plugdir, ':h') . '/myte', 'p')
+ let rtp = &rtp
+ packadd myte
+
+ " Check the path of 'myte' is added
+ call assert_true(len(&rtp) > len(rtp))
+ call assert_match('/testdir/Xdir/pack/mine/opt/myte\($\|,\)', &rtp)
+
+ " Check exception
+ call assert_fails("packadd directorynotfound", 'E919:')
+ call assert_fails("packadd", 'E471:')
+endfunc
+
+func Test_packadd_start()
+ let plugdir = s:topdir . '/pack/mine/start/other'
+ call mkdir(plugdir . '/plugin', 'p')
+ set rtp&
+ let rtp = &rtp
+ filetype on
+
+ exe 'split ' . plugdir . '/plugin/test.vim'
+ call setline(1, 'let g:plugin_works = 24')
+ wq
+
+ packadd other
+
+ call assert_equal(24, g:plugin_works)
+ call assert_true(len(&rtp) > len(rtp))
+ call assert_match('/testdir/Xdir/pack/mine/start/other\($\|,\)', &rtp)
+endfunc
+
+func Test_packadd_noload()
+ call mkdir(s:plugdir . '/plugin', 'p')
+ call mkdir(s:plugdir . '/syntax', 'p')
+ set rtp&
+ let rtp = &rtp
+
+ exe 'split ' . s:plugdir . '/plugin/test.vim'
+ call setline(1, 'let g:plugin_works = 42')
+ wq
+ let g:plugin_works = 0
+
+ packadd! mytest
+
+ call assert_true(len(&rtp) > len(rtp))
+ call assert_match('testdir/Xdir/pack/mine/opt/mytest\($\|,\)', &rtp)
+ call assert_equal(0, g:plugin_works)
+
+ " check the path is not added twice
+ let new_rtp = &rtp
+ packadd! mytest
+ call assert_equal(new_rtp, &rtp)
+endfunc
+
+func Test_packadd_symlink_dir()
+ if !has('unix')
+ return
+ endif
+ let top2_dir = s:topdir . '/Xdir2'
+ let real_dir = s:topdir . '/Xsym'
+ call mkdir(real_dir, 'p')
+ exec "silent !ln -s Xsym" top2_dir
+ let &rtp = top2_dir . ',' . top2_dir . '/after'
+ let &packpath = &rtp
+
+ let s:plugdir = top2_dir . '/pack/mine/opt/mytest'
+ call mkdir(s:plugdir . '/plugin', 'p')
+
+ exe 'split ' . s:plugdir . '/plugin/test.vim'
+ call setline(1, 'let g:plugin_works = 44')
+ wq
+ let g:plugin_works = 0
+
+ packadd mytest
+
+ " Must have been inserted in the middle, not at the end
+ call assert_match('/pack/mine/opt/mytest,', &rtp)
+ call assert_equal(44, g:plugin_works)
+
+ " No change when doing it again.
+ let rtp_before = &rtp
+ packadd mytest
+ call assert_equal(rtp_before, &rtp)
+
+ set rtp&
+ let rtp = &rtp
+ exec "silent !rm" top2_dir
+endfunc
+
+func Test_packadd_symlink_dir2()
+ if !has('unix')
+ return
+ endif
+ let top2_dir = s:topdir . '/Xdir2'
+ let real_dir = s:topdir . '/Xsym/pack'
+ call mkdir(top2_dir, 'p')
+ call mkdir(real_dir, 'p')
+ let &rtp = top2_dir . ',' . top2_dir . '/after'
+ let &packpath = &rtp
+
+ exec "silent !ln -s ../Xsym/pack" top2_dir . '/pack'
+ let s:plugdir = top2_dir . '/pack/mine/opt/mytest'
+ call mkdir(s:plugdir . '/plugin', 'p')
+
+ exe 'split ' . s:plugdir . '/plugin/test.vim'
+ call setline(1, 'let g:plugin_works = 48')
+ wq
+ let g:plugin_works = 0
+
+ packadd mytest
+
+ " Must have been inserted in the middle, not at the end
+ call assert_match('/Xdir2/pack/mine/opt/mytest,', &rtp)
+ call assert_equal(48, g:plugin_works)
+
+ " No change when doing it again.
+ let rtp_before = &rtp
+ packadd mytest
+ call assert_equal(rtp_before, &rtp)
+
+ set rtp&
+ let rtp = &rtp
+ exec "silent !rm" top2_dir . '/pack'
+ exec "silent !rmdir" top2_dir
+endfunc
+
+" Check command-line completion for 'packadd'
+func Test_packadd_completion()
+ let optdir1 = &packpath . '/pack/mine/opt'
+ let optdir2 = &packpath . '/pack/candidate/opt'
+
+ call mkdir(optdir1 . '/pluginA', 'p')
+ call mkdir(optdir1 . '/pluginC', 'p')
+ call mkdir(optdir2 . '/pluginB', 'p')
+ call mkdir(optdir2 . '/pluginC', 'p')
+
+ let li = []
+ call feedkeys(":packadd \<Tab>')\<C-B>call add(li, '\<CR>", 't')
+ call feedkeys(":packadd " . repeat("\<Tab>", 2) . "')\<C-B>call add(li, '\<CR>", 't')
+ call feedkeys(":packadd " . repeat("\<Tab>", 3) . "')\<C-B>call add(li, '\<CR>", 't')
+ call feedkeys(":packadd " . repeat("\<Tab>", 4) . "')\<C-B>call add(li, '\<CR>", 'tx')
+ call assert_equal("packadd pluginA", li[0])
+ call assert_equal("packadd pluginB", li[1])
+ call assert_equal("packadd pluginC", li[2])
+ call assert_equal("packadd ", li[3])
+endfunc
+
+func Test_packloadall()
+ " plugin foo with an autoload directory
+ let fooplugindir = &packpath . '/pack/mine/start/foo/plugin'
+ call mkdir(fooplugindir, 'p')
+ call writefile(['let g:plugin_foo_number = 1234',
+ \ 'let g:plugin_foo_auto = bbb#value',
+ \ 'let g:plugin_extra_auto = extra#value'], fooplugindir . '/bar.vim')
+ let fooautodir = &packpath . '/pack/mine/start/foo/autoload'
+ call mkdir(fooautodir, 'p')
+ call writefile(['let bar#value = 77'], fooautodir . '/bar.vim')
+
+ " plugin aaa with an autoload directory
+ let aaaplugindir = &packpath . '/pack/mine/start/aaa/plugin'
+ call mkdir(aaaplugindir, 'p')
+ call writefile(['let g:plugin_aaa_number = 333',
+ \ 'let g:plugin_aaa_auto = bar#value'], aaaplugindir . '/bbb.vim')
+ let aaaautodir = &packpath . '/pack/mine/start/aaa/autoload'
+ call mkdir(aaaautodir, 'p')
+ call writefile(['let bbb#value = 55'], aaaautodir . '/bbb.vim')
+
+ " plugin extra with only an autoload directory
+ let extraautodir = &packpath . '/pack/mine/start/extra/autoload'
+ call mkdir(extraautodir, 'p')
+ call writefile(['let extra#value = 99'], extraautodir . '/extra.vim')
+
+ packloadall
+ call assert_equal(1234, g:plugin_foo_number)
+ call assert_equal(55, g:plugin_foo_auto)
+ call assert_equal(99, g:plugin_extra_auto)
+ call assert_equal(333, g:plugin_aaa_number)
+ call assert_equal(77, g:plugin_aaa_auto)
+
+ " only works once
+ call writefile(['let g:plugin_bar_number = 4321'], fooplugindir . '/bar2.vim')
+ packloadall
+ call assert_false(exists('g:plugin_bar_number'))
+
+ " works when ! used
+ packloadall!
+ call assert_equal(4321, g:plugin_bar_number)
+endfunc
+
+func Test_helptags()
+ let docdir1 = &packpath . '/pack/mine/start/foo/doc'
+ let docdir2 = &packpath . '/pack/mine/start/bar/doc'
+ call mkdir(docdir1, 'p')
+ call mkdir(docdir2, 'p')
+ call writefile(['look here: *look-here*'], docdir1 . '/bar.txt')
+ call writefile(['look away: *look-away*'], docdir2 . '/foo.txt')
+ exe 'set rtp=' . &packpath . '/pack/mine/start/foo,' . &packpath . '/pack/mine/start/bar'
+
+ helptags ALL
+
+ let tags1 = readfile(docdir1 . '/tags')
+ call assert_match('look-here', tags1[0])
+ let tags2 = readfile(docdir2 . '/tags')
+ call assert_match('look-away', tags2[0])
+
+ call assert_fails('helptags abcxyz', 'E150:')
+endfunc
+
+func Test_colorscheme()
+ let colordirrun = &packpath . '/runtime/colors'
+ let colordirstart = &packpath . '/pack/mine/start/foo/colors'
+ let colordiropt = &packpath . '/pack/mine/opt/bar/colors'
+ call mkdir(colordirrun, 'p')
+ call mkdir(colordirstart, 'p')
+ call mkdir(colordiropt, 'p')
+ call writefile(['let g:found_one = 1'], colordirrun . '/one.vim')
+ call writefile(['let g:found_two = 1'], colordirstart . '/two.vim')
+ call writefile(['let g:found_three = 1'], colordiropt . '/three.vim')
+ exe 'set rtp=' . &packpath . '/runtime'
+
+ colorscheme one
+ call assert_equal(1, g:found_one)
+ colorscheme two
+ call assert_equal(1, g:found_two)
+ colorscheme three
+ call assert_equal(1, g:found_three)
+endfunc
+
+func Test_colorscheme_completion()
+ let colordirrun = &packpath . '/runtime/colors'
+ let colordirstart = &packpath . '/pack/mine/start/foo/colors'
+ let colordiropt = &packpath . '/pack/mine/opt/bar/colors'
+ call mkdir(colordirrun, 'p')
+ call mkdir(colordirstart, 'p')
+ call mkdir(colordiropt, 'p')
+ call writefile(['let g:found_one = 1'], colordirrun . '/one.vim')
+ call writefile(['let g:found_two = 1'], colordirstart . '/two.vim')
+ call writefile(['let g:found_three = 1'], colordiropt . '/three.vim')
+ exe 'set rtp=' . &packpath . '/runtime'
+
+ let li=[]
+ call feedkeys(":colorscheme " . repeat("\<Tab>", 1) . "')\<C-B>call add(li, '\<CR>", 't')
+ call feedkeys(":colorscheme " . repeat("\<Tab>", 2) . "')\<C-B>call add(li, '\<CR>", 't')
+ call feedkeys(":colorscheme " . repeat("\<Tab>", 3) . "')\<C-B>call add(li, '\<CR>", 't')
+ call feedkeys(":colorscheme " . repeat("\<Tab>", 4) . "')\<C-B>call add(li, '\<CR>", 'tx')
+ call assert_equal("colorscheme one", li[0])
+ call assert_equal("colorscheme three", li[1])
+ call assert_equal("colorscheme two", li[2])
+ call assert_equal("colorscheme ", li[3])
+endfunc
+
+func Test_runtime()
+ let rundir = &packpath . '/runtime/extra'
+ let startdir = &packpath . '/pack/mine/start/foo/extra'
+ let optdir = &packpath . '/pack/mine/opt/bar/extra'
+ call mkdir(rundir, 'p')
+ call mkdir(startdir, 'p')
+ call mkdir(optdir, 'p')
+ call writefile(['let g:sequence .= "run"'], rundir . '/bar.vim')
+ call writefile(['let g:sequence .= "start"'], startdir . '/bar.vim')
+ call writefile(['let g:sequence .= "foostart"'], startdir . '/foo.vim')
+ call writefile(['let g:sequence .= "opt"'], optdir . '/bar.vim')
+ call writefile(['let g:sequence .= "xxxopt"'], optdir . '/xxx.vim')
+ exe 'set rtp=' . &packpath . '/runtime'
+
+ let g:sequence = ''
+ runtime extra/bar.vim
+ call assert_equal('run', g:sequence)
+ let g:sequence = ''
+ runtime START extra/bar.vim
+ call assert_equal('start', g:sequence)
+ let g:sequence = ''
+ runtime OPT extra/bar.vim
+ call assert_equal('opt', g:sequence)
+ let g:sequence = ''
+ runtime PACK extra/bar.vim
+ call assert_equal('start', g:sequence)
+ let g:sequence = ''
+ runtime! PACK extra/bar.vim
+ call assert_equal('startopt', g:sequence)
+ let g:sequence = ''
+ runtime PACK extra/xxx.vim
+ call assert_equal('xxxopt', g:sequence)
+
+ let g:sequence = ''
+ runtime ALL extra/bar.vim
+ call assert_equal('run', g:sequence)
+ let g:sequence = ''
+ runtime ALL extra/foo.vim
+ call assert_equal('foostart', g:sequence)
+ let g:sequence = ''
+ runtime! ALL extra/xxx.vim
+ call assert_equal('xxxopt', g:sequence)
+ let g:sequence = ''
+ runtime! ALL extra/bar.vim
+ call assert_equal('runstartopt', g:sequence)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_put.vim b/src/nvim/testdir/test_put.vim
index 440717eaa8..ed76709a56 100644
--- a/src/nvim/testdir/test_put.vim
+++ b/src/nvim/testdir/test_put.vim
@@ -1,5 +1,7 @@
" Tests for put commands, e.g. ":put", "p", "gp", "P", "gP", etc.
+source check.vim
+
func Test_put_block()
new
call feedkeys("i\<C-V>u2500\<CR>x\<ESC>", 'x')
@@ -112,6 +114,39 @@ func Test_put_p_indent_visual()
bwipe!
endfunc
+func Test_gp_with_count_leaves_cursor_at_end()
+ new
+ call setline(1, '<---->')
+ call setreg('@', "foo\nbar", 'c')
+ normal 1G3|3gp
+ call assert_equal([0, 4, 4, 0], getpos("."))
+ call assert_equal(['<--foo', 'barfoo', 'barfoo', 'bar-->'], getline(1, '$'))
+ call assert_equal([0, 4, 3, 0], getpos("']"))
+
+ bwipe!
+endfunc
+
+func Test_p_with_count_leaves_mark_at_end()
+ new
+ call setline(1, '<---->')
+ call setreg('@', "start\nend", 'c')
+ normal 1G3|3p
+ call assert_equal([0, 1, 4, 0], getpos("."))
+ call assert_equal(['<--start', 'endstart', 'endstart', 'end-->'], getline(1, '$'))
+ call assert_equal([0, 4, 3, 0], getpos("']"))
+
+ bwipe!
+endfunc
+
+func Test_put_above_first_line()
+ new
+ let @" = 'text'
+ silent! normal 0o00
+ 0put
+ call assert_equal('text', getline(1))
+ bwipe!
+endfunc
+
func Test_multibyte_op_end_mark()
new
call setline(1, 'тест')
diff --git a/src/nvim/testdir/test_python3.vim b/src/nvim/testdir/test_python3.vim
index 54da3d2eba..f6a1942e24 100644
--- a/src/nvim/testdir/test_python3.vim
+++ b/src/nvim/testdir/test_python3.vim
@@ -69,6 +69,7 @@ func Test_vim_function()
endfunc
func Test_skipped_python3_command_does_not_affect_pyxversion()
+ throw 'skipped: Nvim hardcodes pyxversion=3'
set pyxversion=0
if 0
python3 import vim
diff --git a/src/nvim/testdir/test_pyx2.vim b/src/nvim/testdir/test_pyx2.vim
index b6ed80f842..6a8ebf3da0 100644
--- a/src/nvim/testdir/test_pyx2.vim
+++ b/src/nvim/testdir/test_pyx2.vim
@@ -1,9 +1,9 @@
" Test for pyx* commands and functions with Python 2.
-set pyx=2
if !has('python')
finish
endif
+set pyx=2
let s:py2pattern = '^2\.[0-7]\.\d\+'
let s:py3pattern = '^3\.\d\+\.\d\+'
diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index 6db679c5f9..c4d70fb1de 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -32,7 +32,7 @@ func s:setup_commands(cchar)
command! -count -nargs=* -bang Xnfile <mods><count>cnfile<bang> <args>
command! -nargs=* -bang Xpfile <mods>cpfile<bang> <args>
command! -nargs=* Xexpr <mods>cexpr <args>
- command! -count -nargs=* Xvimgrep <mods> <count>vimgrep <args>
+ command! -count=999 -nargs=* Xvimgrep <mods> <count>vimgrep <args>
command! -nargs=* Xvimgrepadd <mods> vimgrepadd <args>
command! -nargs=* Xgrep <mods> grep <args>
command! -nargs=* Xgrepadd <mods> grepadd <args>
@@ -69,7 +69,7 @@ func s:setup_commands(cchar)
command! -count -nargs=* -bang Xnfile <mods><count>lnfile<bang> <args>
command! -nargs=* -bang Xpfile <mods>lpfile<bang> <args>
command! -nargs=* Xexpr <mods>lexpr <args>
- command! -count -nargs=* Xvimgrep <mods> <count>lvimgrep <args>
+ command! -count=999 -nargs=* Xvimgrep <mods> <count>lvimgrep <args>
command! -nargs=* Xvimgrepadd <mods> lvimgrepadd <args>
command! -nargs=* Xgrep <mods> lgrep <args>
command! -nargs=* Xgrepadd <mods> lgrepadd <args>
@@ -1384,6 +1384,29 @@ func Test_efm_error_type()
let &efm = save_efm
endfunc
+" Test for end_lnum ('%e') and end_col ('%k') fields in 'efm'
+func Test_efm_end_lnum_col()
+ let save_efm = &efm
+
+ " single line
+ set efm=%f:%l-%e:%c-%k:%t:%m
+ cexpr ["Xfile1:10-20:1-2:E:msg1", "Xfile1:20-30:2-3:W:msg2",]
+ let output = split(execute('clist'), "\n")
+ call assert_equal([
+ \ ' 1 Xfile1:10-20 col 1-2 error: msg1',
+ \ ' 2 Xfile1:20-30 col 2-3 warning: msg2'], output)
+
+ " multiple lines
+ set efm=%A%n)%m,%Z%f:%l-%e:%c-%k
+ cexpr ["1)msg1", "Xfile1:14-24:1-2",
+ \ "2)msg2", "Xfile1:24-34:3-4"]
+ let output = split(execute('clist'), "\n")
+ call assert_equal([
+ \ ' 1 Xfile1:14-24 col 1-2 error 1: msg1',
+ \ ' 2 Xfile1:24-34 col 3-4 error 2: msg2'], output)
+ let &efm = save_efm
+endfunc
+
func XquickfixChangedByAutocmd(cchar)
call s:setup_commands(a:cchar)
if a:cchar == 'c'
@@ -1914,6 +1937,7 @@ func Test_switchbuf()
" If opening a file changes 'switchbuf', then the new value should be
" retained.
+ set modeline&vim
call writefile(["vim: switchbuf=split"], 'Xqftestfile1')
enew | only
set switchbuf&vim
@@ -5027,6 +5051,52 @@ func Test_qfbuf_update()
call Xqfbuf_update('l')
endfunc
+" Test for the :vimgrep 'f' flag (fuzzy match)
+func Xvimgrep_fuzzy_match(cchar)
+ call s:setup_commands(a:cchar)
+
+ Xvimgrep /three one/f Xfile*
+ let l = g:Xgetlist()
+ call assert_equal(2, len(l))
+ call assert_equal(['Xfile1', 1, 9, 'one two three'],
+ \ [bufname(l[0].bufnr), l[0].lnum, l[0].col, l[0].text])
+ call assert_equal(['Xfile2', 2, 1, 'three one two'],
+ \ [bufname(l[1].bufnr), l[1].lnum, l[1].col, l[1].text])
+
+ Xvimgrep /the/f Xfile*
+ let l = g:Xgetlist()
+ call assert_equal(3, len(l))
+ call assert_equal(['Xfile1', 1, 9, 'one two three'],
+ \ [bufname(l[0].bufnr), l[0].lnum, l[0].col, l[0].text])
+ call assert_equal(['Xfile2', 2, 1, 'three one two'],
+ \ [bufname(l[1].bufnr), l[1].lnum, l[1].col, l[1].text])
+ call assert_equal(['Xfile2', 4, 4, 'aaathreeaaa'],
+ \ [bufname(l[2].bufnr), l[2].lnum, l[2].col, l[2].text])
+
+ Xvimgrep /aaa/fg Xfile*
+ let l = g:Xgetlist()
+ call assert_equal(4, len(l))
+ call assert_equal(['Xfile1', 2, 1, 'aaaaaa'],
+ \ [bufname(l[0].bufnr), l[0].lnum, l[0].col, l[0].text])
+ call assert_equal(['Xfile1', 2, 4, 'aaaaaa'],
+ \ [bufname(l[1].bufnr), l[1].lnum, l[1].col, l[1].text])
+ call assert_equal(['Xfile2', 4, 1, 'aaathreeaaa'],
+ \ [bufname(l[2].bufnr), l[2].lnum, l[2].col, l[2].text])
+ call assert_equal(['Xfile2', 4, 9, 'aaathreeaaa'],
+ \ [bufname(l[3].bufnr), l[3].lnum, l[3].col, l[3].text])
+
+ call assert_fails('Xvimgrep /xyz/fg Xfile*', 'E480:')
+endfunc
+
+func Test_vimgrep_fuzzy_match()
+ call writefile(['one two three', 'aaaaaa'], 'Xfile1')
+ call writefile(['one', 'three one two', 'two', 'aaathreeaaa'], 'Xfile2')
+ call Xvimgrep_fuzzy_match('c')
+ call Xvimgrep_fuzzy_match('l')
+ call delete('Xfile1')
+ call delete('Xfile2')
+endfunc
+
" Test for getting a specific item from a quickfix list
func Xtest_getqflist_by_idx(cchar)
call s:setup_commands(a:cchar)
diff --git a/src/nvim/testdir/test_random.vim b/src/nvim/testdir/test_random.vim
new file mode 100644
index 0000000000..6d3f7dcfd9
--- /dev/null
+++ b/src/nvim/testdir/test_random.vim
@@ -0,0 +1,51 @@
+" Tests for srand() and rand()
+
+func Test_Rand()
+ let r = srand(123456789)
+ call assert_equal([1573771921, 319883699, 2742014374, 1324369493], r)
+ call assert_equal(4284103975, rand(r))
+ call assert_equal(1001954530, rand(r))
+ call assert_equal(2701803082, rand(r))
+ call assert_equal(2658065534, rand(r))
+ call assert_equal(3104308804, rand(r))
+
+ " Nvim does not support test_settime
+ " call test_settime(12341234)
+ let s = srand()
+ if !has('win32') && filereadable('/dev/urandom')
+ " using /dev/urandom
+ call assert_notequal(s, srand())
+ " else
+ " " using time()
+ " call assert_equal(s, srand())
+ " call test_settime(12341235)
+ " call assert_notequal(s, srand())
+ endif
+
+ " Nvim does not support test_srand_seed
+ " call test_srand_seed(123456789)
+ " call assert_equal(4284103975, rand())
+ " call assert_equal(1001954530, rand())
+ " call test_srand_seed()
+
+ if has('float')
+ call assert_fails('echo srand(1.2)', 'E805:')
+ endif
+ call assert_fails('echo srand([1])', 'E745:')
+ call assert_fails('echo rand("burp")', 'E475:')
+ call assert_fails('echo rand([1, 2, 3])', 'E475:')
+ call assert_fails('echo rand([[1], 2, 3, 4])', 'E475:')
+ call assert_fails('echo rand([1, [2], 3, 4])', 'E475:')
+ call assert_fails('echo rand([1, 2, [3], 4])', 'E475:')
+ call assert_fails('echo rand([1, 2, 3, [4]])', 'E475:')
+
+ " call test_settime(0)
+endfunc
+
+func Test_issue_5587()
+ call rand()
+ call garbagecollect()
+ call rand()
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim
index 712f1e6025..a92f7e1192 100644
--- a/src/nvim/testdir/test_regexp_latin.vim
+++ b/src/nvim/testdir/test_regexp_latin.vim
@@ -787,4 +787,12 @@ func Test_regexp_error()
set re&
endfunc
+func Test_using_mark_position()
+ " this was using freed memory
+ new
+ norm O0
+ call assert_fails("s/\\%')", 'E486:')
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim
index d8d5797dcf..b640a6d043 100644
--- a/src/nvim/testdir/test_regexp_utf8.vim
+++ b/src/nvim/testdir/test_regexp_utf8.vim
@@ -152,9 +152,6 @@ func s:classes_test()
if has('win32')
let identchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§µÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ'
let kwordchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzµÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
- elseif has('ebcdic')
- let identchars_ok = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz€ŒŽœž¬®µº¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
- let kwordchars_ok = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz€ŒŽœž¬®µº¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
else
let identchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzµÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
let kwordchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzµÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
@@ -166,8 +163,6 @@ func s:classes_test()
let fnamechars_ok = '$+,-./0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
elseif has('vms')
let fnamechars_ok = '#$%+,-./0123456789:;<>ABCDEFGHIJKLMNOPQRSTUVWXYZ[]_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
- elseif has('ebcdic')
- let fnamechars_ok = '#$%+,-./=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
else
let fnamechars_ok = '#$%+,-./0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
endif
@@ -590,4 +585,12 @@ func Test_match_char_class_upper()
bwipe!
endfunc
+func Test_match_invalid_byte()
+ call writefile(0z630a.765d30aa0a.2e0a.790a.4030, 'Xinvalid')
+ new
+ source Xinvalid
+ bwipe!
+ call delete('Xinvalid')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim
index 84a5aca3d5..2e92d9aa2f 100644
--- a/src/nvim/testdir/test_registers.vim
+++ b/src/nvim/testdir/test_registers.vim
@@ -1,6 +1,4 @@
-"
" Tests for register operations
-"
source check.vim
source view_util.vim
@@ -13,6 +11,8 @@ func Test_aaa_empty_reg_test()
call assert_fails('normal @!', 'E354:')
call assert_fails('normal @:', 'E30:')
call assert_fails('normal @.', 'E29:')
+ call assert_fails('put /', 'E35:')
+ call assert_fails('put .', 'E29:')
endfunc
func Test_yank_shows_register()
@@ -119,6 +119,17 @@ func Test_recording_esc_sequence()
endif
endfunc
+func Test_recording_with_select_mode()
+ new
+ call feedkeys("qacc12345\<Esc>gH98765\<Esc>q", "tx")
+ call assert_equal("98765", getline(1))
+ call assert_equal("cc12345\<Esc>gH98765\<Esc>", @a)
+ call setline(1, 'asdf')
+ normal! @a
+ call assert_equal("98765", getline(1))
+ bwipe!
+endfunc
+
" Test for executing the last used register (@)
func Test_last_used_exec_reg()
" Test for the @: command
@@ -141,6 +152,14 @@ func Test_last_used_exec_reg()
normal @@
call assert_equal('EditEdit', a)
+ " Test for repeating the last command-line in visual mode
+ call append(0, 'register')
+ normal gg
+ let @r = ''
+ call feedkeys("v:yank R\<CR>", 'xt')
+ call feedkeys("v@:", 'xt')
+ call assert_equal("\nregister\nregister\n", @r)
+
enew!
endfunc
@@ -164,6 +183,19 @@ func Test_get_register()
call assert_equal('', getregtype('!'))
+ " Test for inserting an invalid register content
+ call assert_beeps('exe "normal i\<C-R>!"')
+
+ " Test for inserting a register with multiple lines
+ call deletebufline('', 1, '$')
+ call setreg('r', ['a', 'b'])
+ exe "normal i\<C-R>r"
+ call assert_equal(['a', 'b', ''], getline(1, '$'))
+
+ " Test for inserting a multi-line register in the command line
+ call feedkeys(":\<C-R>r\<Esc>", 'xt')
+ call assert_equal("a\rb", histget(':', -1)) " Modified because of #6137
+
enew!
endfunc
@@ -187,9 +219,200 @@ func Test_set_register()
call setreg('=', 'b', 'a')
call assert_equal('regwrite', getreg('='))
+ " Test for settting a list of lines to special registers
+ call setreg('/', [])
+ call assert_equal('', @/)
+ call setreg('=', [])
+ call assert_equal('', @=)
+ call assert_fails("call setreg('/', ['a', 'b'])", 'E883:')
+ call assert_fails("call setreg('=', ['a', 'b'])", 'E883:')
+ call assert_equal(0, setreg('_', ['a', 'b']))
+
+ " Test for recording to a invalid register
+ call assert_beeps('normal q$')
+
+ " Appending to a register when recording
+ call append(0, "text for clipboard test")
+ normal gg
+ call feedkeys('qrllq', 'xt')
+ call feedkeys('qRhhq', 'xt')
+ call assert_equal('llhh', getreg('r'))
+
+ " Appending a list of characters to a register from different lines
+ let @r = ''
+ call append(0, ['abcdef', '123456'])
+ normal gg"ry3l
+ call cursor(2, 4)
+ normal "Ry3l
+ call assert_equal('abc456', @r)
+
+ " Test for gP with multiple lines selected using characterwise motion
+ %delete
+ call append(0, ['vim editor', 'vim editor'])
+ let @r = ''
+ exe "normal ggwy/vim /e\<CR>gP"
+ call assert_equal(['vim editor', 'vim editor', 'vim editor'], getline(1, 3))
+
+ " Test for gP with . register
+ %delete
+ normal iabc
+ normal ".gp
+ call assert_equal('abcabc', getline(1))
+ normal 0".gP
+ call assert_equal('abcabcabc', getline(1))
+
enew!
endfunc
+" Test for clipboard registers (* and +)
+func Test_clipboard_regs()
+ throw 'skipped: needs clipboard=autoselect,autoselectplus'
+
+ CheckNotGui
+ CheckFeature clipboard_working
+
+ new
+ call append(0, "text for clipboard test")
+ normal gg"*yiw
+ call assert_equal('text', getreg('*'))
+ normal gg2w"+yiw
+ call assert_equal('clipboard', getreg('+'))
+
+ " Test for replacing the clipboard register contents
+ set clipboard=unnamed
+ let @* = 'food'
+ normal ggviw"*p
+ call assert_equal('text', getreg('*'))
+ call assert_equal('food for clipboard test', getline(1))
+ normal ggviw"*p
+ call assert_equal('food', getreg('*'))
+ call assert_equal('text for clipboard test', getline(1))
+
+ " Test for replacing the selection register contents
+ set clipboard=unnamedplus
+ let @+ = 'food'
+ normal ggviw"+p
+ call assert_equal('text', getreg('+'))
+ call assert_equal('food for clipboard test', getline(1))
+ normal ggviw"+p
+ call assert_equal('food', getreg('+'))
+ call assert_equal('text for clipboard test', getline(1))
+
+ " Test for auto copying visually selected text to clipboard register
+ call setline(1, "text for clipboard test")
+ let @* = ''
+ set clipboard=autoselect
+ normal ggwwviwy
+ call assert_equal('clipboard', @*)
+
+ " Test for auto copying visually selected text to selection register
+ let @+ = ''
+ set clipboard=autoselectplus
+ normal ggwviwy
+ call assert_equal('for', @+)
+
+ set clipboard&vim
+ bwipe!
+endfunc
+
+" Test for restarting the current mode (insert or virtual replace) after
+" executing the contents of a register
+func Test_put_reg_restart_mode()
+ new
+ call append(0, 'editor')
+ normal gg
+ let @r = "ivim \<Esc>"
+ call feedkeys("i\<C-O>@r\<C-R>=mode()\<CR>", 'xt')
+ call assert_equal('vimi editor', getline(1))
+
+ call setline(1, 'editor')
+ normal gg
+ call feedkeys("gR\<C-O>@r\<C-R>=mode()\<CR>", 'xt')
+ call assert_equal('vimReditor', getline(1))
+
+ bwipe!
+endfunc
+
+" Test for getting register info
+func Test_get_reginfo()
+ enew
+ call setline(1, ['foo', 'bar'])
+
+ exe 'norm! "zyy'
+ let info = getreginfo('"')
+ call assert_equal('z', info.points_to)
+ call setreg('y', 'baz')
+ call assert_equal('z', getreginfo('').points_to)
+ call setreg('y', { 'isunnamed': v:true })
+ call assert_equal('y', getreginfo('"').points_to)
+
+ exe '$put'
+ call assert_equal(getreg('y'), getline(3))
+ call setreg('', 'qux')
+ call assert_equal('0', getreginfo('').points_to)
+ call setreg('x', 'quux')
+ call assert_equal('0', getreginfo('').points_to)
+
+ let info = getreginfo('')
+ call assert_equal(getreg('', 1, 1), info.regcontents)
+ call assert_equal(getregtype(''), info.regtype)
+
+ exe "norm! 0\<c-v>e" .. '"zy'
+ let info = getreginfo('z')
+ call assert_equal(getreg('z', 1, 1), info.regcontents)
+ call assert_equal(getregtype('z'), info.regtype)
+ call assert_equal(1, +info.isunnamed)
+
+ let info = getreginfo('"')
+ call assert_equal('z', info.points_to)
+
+ bwipe!
+endfunc
+
+" Test for restoring register with dict from getreginfo
+func Test_set_register_dict()
+ enew!
+
+ call setreg('"', #{ regcontents: ['one', 'two'],
+ \ regtype: 'V', points_to: 'z' })
+ call assert_equal(['one', 'two'], getreg('"', 1, 1))
+ let info = getreginfo('"')
+ call assert_equal('z', info.points_to)
+ call assert_equal('V', info.regtype)
+ call assert_equal(1, +getreginfo('z').isunnamed)
+
+ call setreg('x', #{ regcontents: ['three', 'four'],
+ \ regtype: 'v', isunnamed: v:true })
+ call assert_equal(['three', 'four'], getreg('"', 1, 1))
+ let info = getreginfo('"')
+ call assert_equal('x', info.points_to)
+ call assert_equal('v', info.regtype)
+ call assert_equal(1, +getreginfo('x').isunnamed)
+
+ call setreg('y', #{ regcontents: 'five',
+ \ regtype: "\<c-v>", isunnamed: v:false })
+ call assert_equal("\<c-v>4", getreginfo('y').regtype)
+ call assert_equal(0, +getreginfo('y').isunnamed)
+ call assert_equal(['three', 'four'], getreg('"', 1, 1))
+ call assert_equal('x', getreginfo('"').points_to)
+
+ call setreg('"', #{ regcontents: 'six' })
+ call assert_equal('0', getreginfo('"').points_to)
+ call assert_equal(1, +getreginfo('0').isunnamed)
+ call assert_equal(['six'], getreginfo('0').regcontents)
+ call assert_equal(['six'], getreginfo('"').regcontents)
+
+ let @x = 'one'
+ call setreg('x', {})
+ call assert_equal(1, len(split(execute('reg x'), '\n')))
+
+ call assert_fails("call setreg('0', #{regtype: 'V'}, 'v')", 'E118:')
+ call assert_fails("call setreg('0', #{regtype: 'X'})", 'E475:')
+ call assert_fails("call setreg('0', #{regtype: 'vy'})", 'E475:')
+
+ bwipe!
+endfunc
+
func Test_v_register()
enew
call setline(1, 'nothing')
@@ -288,84 +511,25 @@ func Test_insert_small_delete()
bwipe!
endfunc
-" Test for getting register info
-func Test_get_reginfo()
- enew
- call setline(1, ['foo', 'bar'])
-
- exe 'norm! "zyy'
- let info = getreginfo('"')
- call assert_equal('z', info.points_to)
- call setreg('y', 'baz')
- call assert_equal('z', getreginfo('').points_to)
- call setreg('y', { 'isunnamed': v:true })
- call assert_equal('y', getreginfo('"').points_to)
-
- exe '$put'
- call assert_equal(getreg('y'), getline(3))
- call setreg('', 'qux')
- call assert_equal('0', getreginfo('').points_to)
- call setreg('x', 'quux')
- call assert_equal('0', getreginfo('').points_to)
-
- let info = getreginfo('')
- call assert_equal(getreg('', 1, 1), info.regcontents)
- call assert_equal(getregtype(''), info.regtype)
-
- exe "norm! 0\<c-v>e" .. '"zy'
- let info = getreginfo('z')
- call assert_equal(getreg('z', 1, 1), info.regcontents)
- call assert_equal(getregtype('z'), info.regtype)
- call assert_equal(1, +info.isunnamed)
-
- let info = getreginfo('"')
- call assert_equal('z', info.points_to)
+func Test_record_in_select_mode()
+ new
+ call setline(1, 'text')
+ sil norm q00
+ sil norm q
+ call assert_equal('0ext', getline(1))
+
+ %delete
+ let @r = ''
+ call setline(1, ['abc', 'abc', 'abc'])
+ smap <F2> <Right><Right>,
+ call feedkeys("qrgh\<F2>Dk\<Esc>q", 'xt')
+ call assert_equal("gh\<F2>Dk\<Esc>", @r)
+ norm j0@rj0@@
+ call assert_equal([',Dk', ',Dk', ',Dk'], getline(1, 3))
+ sunmap <F2>
bwipe!
endfunc
-" Test for restoring register with dict from getreginfo
-func Test_set_register_dict()
- enew!
-
- call setreg('"', #{ regcontents: ['one', 'two'],
- \ regtype: 'V', points_to: 'z' })
- call assert_equal(['one', 'two'], getreg('"', 1, 1))
- let info = getreginfo('"')
- call assert_equal('z', info.points_to)
- call assert_equal('V', info.regtype)
- call assert_equal(1, +getreginfo('z').isunnamed)
-
- call setreg('x', #{ regcontents: ['three', 'four'],
- \ regtype: 'v', isunnamed: v:true })
- call assert_equal(['three', 'four'], getreg('"', 1, 1))
- let info = getreginfo('"')
- call assert_equal('x', info.points_to)
- call assert_equal('v', info.regtype)
- call assert_equal(1, +getreginfo('x').isunnamed)
-
- call setreg('y', #{ regcontents: 'five',
- \ regtype: "\<c-v>", isunnamed: v:false })
- call assert_equal("\<c-v>4", getreginfo('y').regtype)
- call assert_equal(0, +getreginfo('y').isunnamed)
- call assert_equal(['three', 'four'], getreg('"', 1, 1))
- call assert_equal('x', getreginfo('"').points_to)
-
- call setreg('"', #{ regcontents: 'six' })
- call assert_equal('0', getreginfo('"').points_to)
- call assert_equal(1, +getreginfo('0').isunnamed)
- call assert_equal(['six'], getreginfo('0').regcontents)
- call assert_equal(['six'], getreginfo('"').regcontents)
-
- let @x = 'one'
- call setreg('x', {})
- call assert_equal(1, len(split(execute('reg x'), '\n')))
-
- call assert_fails("call setreg('0', #{regtype: 'V'}, 'v')", 'E118:')
- call assert_fails("call setreg('0', #{regtype: 'X'})", 'E475:')
- call assert_fails("call setreg('0', #{regtype: 'vy'})", 'E475:')
-
- bwipe!
-endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_rename.vim b/src/nvim/testdir/test_rename.vim
index 3887fcfabf..5359b84923 100644
--- a/src/nvim/testdir/test_rename.vim
+++ b/src/nvim/testdir/test_rename.vim
@@ -1,5 +1,7 @@
" Test rename()
+source shared.vim
+
func Test_rename_file_to_file()
call writefile(['foo'], 'Xrename1')
@@ -81,7 +83,7 @@ func Test_rename_copy()
call assert_equal(0, rename('Xrenamedir/Xrenamefile', 'Xrenamefile'))
- if !has('win32')
+ if !has('win32') && !IsRoot()
" On Windows, the source file is removed despite
" its directory being made not writable.
call assert_equal(['foo'], readfile('Xrenamedir/Xrenamefile'))
diff --git a/src/nvim/testdir/test_retab.vim b/src/nvim/testdir/test_retab.vim
index f11a32bade..1650a03876 100644
--- a/src/nvim/testdir/test_retab.vim
+++ b/src/nvim/testdir/test_retab.vim
@@ -69,9 +69,33 @@ func Test_retab()
call assert_equal(" a b c ", Retab('!', 3))
call assert_equal(" a b c ", Retab('', 5))
call assert_equal(" a b c ", Retab('!', 5))
+
+ set tabstop& expandtab&
endfunc
func Test_retab_error()
call assert_fails('retab -1', 'E487:')
call assert_fails('retab! -1', 'E487:')
+ call assert_fails('ret -1000', 'E487:')
+ call assert_fails('ret 10000', 'E475:')
+ call assert_fails('ret 80000000000000000000', 'E475:')
endfunc
+
+func Test_retab_endless()
+ new
+ call setline(1, "\t0\t")
+ let caught = 'no'
+ try
+ while 1
+ set ts=4000
+ retab 4
+ endwhile
+ catch /E1240/
+ let caught = 'yes'
+ endtry
+ bwipe!
+ set tabstop&
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_scriptnames.vim b/src/nvim/testdir/test_scriptnames.vim
index fc6c910bfa..44ec146666 100644
--- a/src/nvim/testdir/test_scriptnames.vim
+++ b/src/nvim/testdir/test_scriptnames.vim
@@ -23,4 +23,10 @@ func Test_scriptnames()
bwipe
call delete('Xscripting')
+
+ let msgs = execute('messages')
+ scriptnames
+ call assert_equal(msgs, execute('messages'))
endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim
index c796f1f676..a3d5ca96a1 100644
--- a/src/nvim/testdir/test_search.vim
+++ b/src/nvim/testdir/test_search.vim
@@ -292,15 +292,84 @@ endfunc
func Test_searchpair()
new
- call setline(1, ['other code here', '', '[', '" cursor here', ']'])
+ call setline(1, ['other code', 'here [', ' [', ' " cursor here', ' ]]'])
+
+ 4
+ call assert_equal(3, searchpair('\[', '', ']', 'bW'))
+ call assert_equal([0, 3, 2, 0], getpos('.'))
+ 4
+ call assert_equal(2, searchpair('\[', '', ']', 'bWr'))
+ call assert_equal([0, 2, 6, 0], getpos('.'))
+ 4
+ call assert_equal(1, searchpair('\[', '', ']', 'bWm'))
+ call assert_equal([0, 3, 2, 0], getpos('.'))
+ 4|norm ^
+ call assert_equal(5, searchpair('\[', '', ']', 'Wn'))
+ call assert_equal([0, 4, 2, 0], getpos('.'))
+ 4
+ call assert_equal(2, searchpair('\[', '', ']', 'bW',
+ \ 'getline(".") =~ "^\\s*\["'))
+ call assert_equal([0, 2, 6, 0], getpos('.'))
+ set nomagic
+ 4
+ call assert_equal(3, searchpair('\[', '', ']', 'bW'))
+ call assert_equal([0, 3, 2, 0], getpos('.'))
+ set magic
+ 4|norm ^
+ call assert_equal(0, searchpair('{', '', '}', 'bW'))
+ call assert_equal([0, 4, 2, 0], getpos('.'))
+
+ %d
+ call setline(1, ['if 1', ' if 2', ' else', ' endif 2', 'endif 1'])
+
+ /\<if 1
+ call assert_equal(5, searchpair('\<if\>', '\<else\>', '\<endif\>', 'W'))
+ call assert_equal([0, 5, 1, 0], getpos('.'))
+ /\<if 2
+ call assert_equal(3, searchpair('\<if\>', '\<else\>', '\<endif\>', 'W'))
+ call assert_equal([0, 3, 3, 0], getpos('.'))
+
+ q!
+endfunc
+
+func Test_searchpairpos()
+ new
+ call setline(1, ['other code', 'here [', ' [', ' " cursor here', ' ]]'])
+
+ 4
+ call assert_equal([3, 2], searchpairpos('\[', '', ']', 'bW'))
+ call assert_equal([0, 3, 2, 0], getpos('.'))
4
- let a = searchpair('\[','',']','bW')
- call assert_equal(3, a)
+ call assert_equal([2, 6], searchpairpos('\[', '', ']', 'bWr'))
+ call assert_equal([0, 2, 6, 0], getpos('.'))
+ 4|norm ^
+ call assert_equal([5, 2], searchpairpos('\[', '', ']', 'Wn'))
+ call assert_equal([0, 4, 2, 0], getpos('.'))
+ 4
+ call assert_equal([2, 6], searchpairpos('\[', '', ']', 'bW',
+ \ 'getline(".") =~ "^\\s*\["'))
+ call assert_equal([0, 2, 6, 0], getpos('.'))
+ 4
+ call assert_equal([2, 6], searchpairpos('\[', '', ']', 'bWr'))
+ call assert_equal([0, 2, 6, 0], getpos('.'))
set nomagic
4
- let a = searchpair('\[','',']','bW')
- call assert_equal(3, a)
+ call assert_equal([3, 2], searchpairpos('\[', '', ']', 'bW'))
+ call assert_equal([0, 3, 2, 0], getpos('.'))
set magic
+ 4|norm ^
+ call assert_equal([0, 0], searchpairpos('{', '', '}', 'bW'))
+ call assert_equal([0, 4, 2, 0], getpos('.'))
+
+ %d
+ call setline(1, ['if 1', ' if 2', ' else', ' endif 2', 'endif 1'])
+ /\<if 1
+ call assert_equal([5, 1], searchpairpos('\<if\>', '\<else\>', '\<endif\>', 'W'))
+ call assert_equal([0, 5, 1, 0], getpos('.'))
+ /\<if 2
+ call assert_equal([3, 3], searchpairpos('\<if\>', '\<else\>', '\<endif\>', 'W'))
+ call assert_equal([0, 3, 3, 0], getpos('.'))
+
q!
endfunc
@@ -309,17 +378,29 @@ func Test_searchpair_errors()
call assert_fails("call searchpair('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: using Funcref as a String')
call assert_fails("call searchpair('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: using Dictionary as a String')
call assert_fails("call searchpair('start', 'middle', 'end', 'flags', 'skip', 99, 100)", 'E475: Invalid argument: flags')
- call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 0, 99, 100)", 'E475: Invalid argument: 0')
call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', -99, 100)", 'E475: Invalid argument: -99')
call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', 99, -100)", 'E475: Invalid argument: -100')
+ call assert_fails("call searchpair('start', 'middle', 'end', 'e')", 'E475: Invalid argument: e')
+ call assert_fails("call searchpair('start', 'middle', 'end', 'sn')", 'E475: Invalid argument: sn')
+endfunc
+
+func Test_searchpairpos_errors()
+ call assert_fails("call searchpairpos([0], 'middle', 'end', 'bW', 'skip', 99, 100)", 'E730: using List as a String')
+ call assert_fails("call searchpairpos('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: using Funcref as a String')
+ call assert_fails("call searchpairpos('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: using Dictionary as a String')
+ call assert_fails("call searchpairpos('start', 'middle', 'end', 'flags', 'skip', 99, 100)", 'E475: Invalid argument: flags')
+ call assert_fails("call searchpairpos('start', 'middle', 'end', 'bW', 'func', -99, 100)", 'E475: Invalid argument: -99')
+ call assert_fails("call searchpairpos('start', 'middle', 'end', 'bW', 'func', 99, -100)", 'E475: Invalid argument: -100')
+ call assert_fails("call searchpairpos('start', 'middle', 'end', 'e')", 'E475: Invalid argument: e')
+ call assert_fails("call searchpairpos('start', 'middle', 'end', 'sn')", 'E475: Invalid argument: sn')
endfunc
func Test_searchpair_skip()
func Zero()
- return 0
+ return 0
endfunc
func Partial(x)
- return a:x
+ return a:x
endfunc
new
call setline(1, ['{', 'foo', 'foo', 'foo', '}'])
@@ -1192,7 +1273,7 @@ endfunc
" This was causing E874. Also causes an invalid read?
func Test_look_behind()
new
- call setline(1, '0\|\&\n\@<=')
+ call setline(1, '0\|\&\n\@<=')
call search(getline("."))
bwipe!
endfunc
@@ -1236,11 +1317,11 @@ endfunc
func Test_search_Ctrl_L_combining()
" Make sure, that Ctrl-L works correctly with combining characters.
" It uses an artificial example of an 'a' with 4 combining chars:
- " 'a' U+0061 Dec:97 LATIN SMALL LETTER A &#x61; /\%u61\Z "\u0061"
+ " 'a' U+0061 Dec:97 LATIN SMALL LETTER A &#x61; /\%u61\Z "\u0061"
" ' ̀' U+0300 Dec:768 COMBINING GRAVE ACCENT &#x300; /\%u300\Z "\u0300"
" ' ́' U+0301 Dec:769 COMBINING ACUTE ACCENT &#x301; /\%u301\Z "\u0301"
" ' ̇' U+0307 Dec:775 COMBINING DOT ABOVE &#x307; /\%u307\Z "\u0307"
- " ' ̣' U+0323 Dec:803 COMBINING DOT BELOW &#x323; /\%u323 "\u0323"
+ " ' ̣' U+0323 Dec:803 COMBINING DOT BELOW &#x323; /\%u323 "\u0323"
" Those should also appear on the commandline
CheckOption incsearch
diff --git a/src/nvim/testdir/test_search_stat.vim b/src/nvim/testdir/test_search_stat.vim
index 335a51268d..d950626615 100644
--- a/src/nvim/testdir/test_search_stat.vim
+++ b/src/nvim/testdir/test_search_stat.vim
@@ -1,14 +1,15 @@
" Tests for search_stats, when "S" is not in 'shortmess'
-source screendump.vim
source check.vim
+source screendump.vim
func Test_search_stat()
new
set shortmess-=S
" Append 50 lines with text to search for, "foobar" appears 20 times
call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 10))
- call nvim_win_set_cursor(0, [1, 0])
+
+ call cursor(1, 1)
" searchcount() returns an empty dictionary when previous pattern was not set
call assert_equal({}, searchcount(#{pattern: ''}))
@@ -45,7 +46,6 @@ func Test_search_stat()
\ searchcount(#{pattern: 'fooooobar', maxcount: 1}))
" match at second line
- call cursor(1, 1)
let messages_before = execute('messages')
let @/ = 'fo*\(bar\?\)\?'
let g:a = execute(':unsilent :norm! n')
@@ -73,7 +73,6 @@ func Test_search_stat()
let stat = '\[2/50\]'
let g:a = execute(':unsilent :norm! n')
call assert_notmatch(pat .. stat, g:a)
- call writefile(getline(1, '$'), 'sample.txt')
" n does not update search stat
call assert_equal(
\ #{current: 50, exact_match: 1, total: 50, incomplete: 0, maxcount: 99},
@@ -263,6 +262,34 @@ func Test_searchcount_fails()
call assert_fails('echo searchcount("boo!")', 'E715:')
endfunc
+func Test_searchcount_in_statusline()
+ CheckScreendump
+
+ let lines =<< trim END
+ set shortmess-=S
+ call append(0, 'this is something')
+ function TestSearchCount() abort
+ let search_count = searchcount()
+ if !empty(search_count)
+ return '[' . search_count.current . '/' . search_count.total . ']'
+ else
+ return ''
+ endif
+ endfunction
+ set hlsearch
+ set laststatus=2 statusline+=%{TestSearchCount()}
+ END
+ call writefile(lines, 'Xsearchstatusline')
+ let buf = RunVimInTerminal('-S Xsearchstatusline', #{rows: 10})
+ call TermWait(buf)
+ call term_sendkeys(buf, "/something")
+ call VerifyScreenDump(buf, 'Test_searchstat_4', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+ call delete('Xsearchstatusline')
+endfunc
+
func Test_search_stat_foldopen()
CheckScreendump
@@ -320,30 +347,29 @@ func! Test_search_stat_screendump()
call delete('Xsearchstat')
endfunc
-func Test_searchcount_in_statusline()
+func Test_search_stat_then_gd()
CheckScreendump
let lines =<< trim END
+ call setline(1, ['int cat;', 'int dog;', 'cat = dog;'])
set shortmess-=S
- call append(0, 'this is something')
- function TestSearchCount() abort
- let search_count = searchcount()
- if !empty(search_count)
- return '[' . search_count.current . '/' . search_count.total . ']'
- else
- return ''
- endif
- endfunction
set hlsearch
- set laststatus=2 statusline+=%{TestSearchCount()}
END
- call writefile(lines, 'Xsearchstatusline')
- let buf = RunVimInTerminal('-S Xsearchstatusline', #{rows: 10})
+ call writefile(lines, 'Xsearchstatgd')
+
+ let buf = RunVimInTerminal('-S Xsearchstatgd', #{rows: 10})
+ call term_sendkeys(buf, "/dog\<CR>")
call TermWait(buf)
- call term_sendkeys(buf, "/something")
- call VerifyScreenDump(buf, 'Test_searchstat_4', {})
+ call VerifyScreenDump(buf, 'Test_searchstatgd_1', {})
+
+ call term_sendkeys(buf, "G0gD")
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_searchstatgd_2', {})
- call term_sendkeys(buf, "\<Esc>")
call StopVimInTerminal(buf)
- call delete('Xsearchstatusline')
+ call delete('Xsearchstatgd')
endfunc
+
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_selectmode.vim b/src/nvim/testdir/test_selectmode.vim
new file mode 100644
index 0000000000..b483841060
--- /dev/null
+++ b/src/nvim/testdir/test_selectmode.vim
@@ -0,0 +1,57 @@
+" Test for Select-mode
+
+source shared.vim
+
+" Test for selecting a register with CTRL-R
+func Test_selectmode_register()
+ new
+
+ " Default behavior: use unnamed register
+ call setline(1, 'foo')
+ call setreg('"', 'bar')
+ call setreg('a', 'baz')
+ exe ":norm! v\<c-g>a"
+ call assert_equal(getline('.'), 'aoo')
+ call assert_equal('f', getreg('"'))
+ call assert_equal('baz', getreg('a'))
+
+ " Use the black hole register
+ call setline(1, 'foo')
+ call setreg('"', 'bar')
+ call setreg('a', 'baz')
+ exe ":norm! v\<c-g>\<c-r>_a"
+ call assert_equal(getline('.'), 'aoo')
+ call assert_equal('bar', getreg('"'))
+ call assert_equal('baz', getreg('a'))
+
+ " Invalid register: use unnamed register
+ call setline(1, 'foo')
+ call setreg('"', 'bar')
+ call setreg('a', 'baz')
+ exe ":norm! v\<c-g>\<c-r>?a"
+ call assert_equal(getline('.'), 'aoo')
+ call assert_equal('f', getreg('"'))
+ call assert_equal('baz', getreg('a'))
+
+ " Use unnamed register
+ call setline(1, 'foo')
+ call setreg('"', 'bar')
+ call setreg('a', 'baz')
+ exe ":norm! v\<c-g>\<c-r>\"a"
+ call assert_equal(getline('.'), 'aoo')
+ call assert_equal('f', getreg('"'))
+ call assert_equal('baz', getreg('a'))
+
+ " use specicifed register, unnamed register is also written
+ call setline(1, 'foo')
+ call setreg('"', 'bar')
+ call setreg('a', 'baz')
+ exe ":norm! v\<c-g>\<c-r>aa"
+ call assert_equal(getline('.'), 'aoo')
+ call assert_equal('f', getreg('"'))
+ call assert_equal('f', getreg('a'))
+
+ bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_source_utf8.vim b/src/nvim/testdir/test_source_utf8.vim
index e93ea29dff..66fabe0442 100644
--- a/src/nvim/testdir/test_source_utf8.vim
+++ b/src/nvim/testdir/test_source_utf8.vim
@@ -1,4 +1,5 @@
" Test the :source! command
+source check.vim
func Test_source_utf8()
" check that sourcing a script with 0x80 as second byte works
@@ -31,24 +32,24 @@ endfunc
" Test for sourcing a file with CTRL-V's at the end of the line
func Test_source_ctrl_v()
- call writefile(['map __1 afirst',
- \ 'map __2 asecond',
- \ 'map __3 athird',
- \ 'map __4 afourth',
- \ 'map __5 afifth',
- \ "map __1 asd\<C-V>",
- \ "map __2 asd\<C-V>\<C-V>",
- \ "map __3 asd\<C-V>\<C-V>",
- \ "map __4 asd\<C-V>\<C-V>\<C-V>",
- \ "map __5 asd\<C-V>\<C-V>\<C-V>",
- \ ], 'Xtestfile')
+ call writefile(['map __1 afirst',
+ \ 'map __2 asecond',
+ \ 'map __3 athird',
+ \ 'map __4 afourth',
+ \ 'map __5 afifth',
+ \ "map __1 asd\<C-V>",
+ \ "map __2 asd\<C-V>\<C-V>",
+ \ "map __3 asd\<C-V>\<C-V>",
+ \ "map __4 asd\<C-V>\<C-V>\<C-V>",
+ \ "map __5 asd\<C-V>\<C-V>\<C-V>",
+ \ ], 'Xtestfile')
source Xtestfile
enew!
exe "normal __1\<Esc>\<Esc>__2\<Esc>__3\<Esc>\<Esc>__4\<Esc>__5\<Esc>"
exe "%s/\<C-J>/0/g"
call assert_equal(['sd',
- \ "map __2 asd\<Esc>secondsd\<Esc>sd0map __5 asd0fifth"],
- \ getline(1, 2))
+ \ "map __2 asd\<Esc>secondsd\<Esc>sd0map __5 asd0fifth"],
+ \ getline(1, 2))
enew!
call delete('Xtestfile')
diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim
index cf0faeee31..1ecb5c8070 100644
--- a/src/nvim/testdir/test_spell.vim
+++ b/src/nvim/testdir/test_spell.vim
@@ -768,6 +768,14 @@ func Test_spell_screendump()
call delete('XtestSpell')
endfunc
+func Test_spell_single_word()
+ new
+ silent! norm 0R00
+ spell!
+ silent 0norm 0r$ Dvz=
+ bwipe!
+endfunc
+
let g:test_data_aff1 = [
\"SET ISO8859-1",
\"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ",
diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim
index a3e4dcdd25..fad13e3340 100644
--- a/src/nvim/testdir/test_statusline.vim
+++ b/src/nvim/testdir/test_statusline.vim
@@ -186,7 +186,16 @@ func Test_statusline()
set virtualedit=all
norm 10|
call assert_match('^10,-10\s*$', s:get_statusline())
+ set list
+ call assert_match('^10,-10\s*$', s:get_statusline())
set virtualedit&
+ exe "norm A\<Tab>\<Tab>a\<Esc>"
+ " In list mode a <Tab> is shown as "^I", which is 2-wide.
+ call assert_match('^9,-9\s*$', s:get_statusline())
+ set list&
+ " Now the second <Tab> ends at the 16th screen column.
+ call assert_match('^17,-17\s*$', s:get_statusline())
+ undo
" %w: Preview window flag, text is "[Preview]".
" %W: Preview window flag, text is ",PRV".
@@ -498,5 +507,20 @@ func Test_statusline_after_split_vsplit()
set ls& stl&
endfunc
+" Test using a multibyte character for 'stl' and 'stlnc' items in 'fillchars'
+" with a custom 'statusline'
+func Test_statusline_mbyte_fillchar()
+ only
+ set laststatus=2
+ set fillchars=vert:\|,fold:-,stl:━,stlnc:═
+ set statusline=a%=b
+ call assert_match('^a\+━\+b$', s:get_statusline())
+ vnew
+ call assert_match('^a\+━\+b━a\+═\+b$', s:get_statusline())
+ wincmd w
+ call assert_match('^a\+═\+b═a\+━\+b$', s:get_statusline())
+ set statusline& fillchars& laststatus&
+ %bw!
+endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim
index 20b760ac15..dbb792d2b0 100644
--- a/src/nvim/testdir/test_substitute.vim
+++ b/src/nvim/testdir/test_substitute.vim
@@ -51,10 +51,12 @@ func Test_substitute_variants()
\ { 'cmd': ':s/t/r/cg', 'exp': 'Tesring srring', 'prompt': 'a' },
\ { 'cmd': ':s/t/r/ci', 'exp': 'resting string', 'prompt': 'y' },
\ { 'cmd': ':s/t/r/cI', 'exp': 'Tesring string', 'prompt': 'y' },
+ \ { 'cmd': ':s/t/r/c', 'exp': 'Testing string', 'prompt': 'n' },
\ { 'cmd': ':s/t/r/cn', 'exp': ln },
\ { 'cmd': ':s/t/r/cp', 'exp': 'Tesring string', 'prompt': 'y' },
\ { 'cmd': ':s/t/r/cl', 'exp': 'Tesring string', 'prompt': 'y' },
\ { 'cmd': ':s/t/r/gc', 'exp': 'Tesring srring', 'prompt': 'a' },
+ \ { 'cmd': ':s/i/I/gc', 'exp': 'TestIng string', 'prompt': 'l' },
\ { 'cmd': ':s/foo/bar/ge', 'exp': ln },
\ { 'cmd': ':s/t/r/g', 'exp': 'Tesring srring' },
\ { 'cmd': ':s/t/r/gi', 'exp': 'resring srring' },
@@ -86,6 +88,7 @@ func Test_substitute_variants()
\ { 'cmd': ':s//r/rp', 'exp': 'Testr string' },
\ { 'cmd': ':s//r/rl', 'exp': 'Testr string' },
\ { 'cmd': ':s//r/r', 'exp': 'Testr string' },
+ \ { 'cmd': ':s/i/I/gc', 'exp': 'Testing string', 'prompt': 'q' },
\]
for var in variants
@@ -105,7 +108,7 @@ func Test_substitute_variants()
call assert_equal(var.exp, getline('.'), msg)
endfor
endfor
-endfunction
+endfunc
" Test the l, p, # flags.
func Test_substitute_flags_lp()
@@ -384,6 +387,10 @@ func Test_substitute_join()
call assert_equal(["foo\tbarbar\<C-H>foo"], getline(1, '$'))
call assert_equal('\n', histget("search", -1))
+ call setline(1, ['foo', 'bar', 'baz', 'qux'])
+ call execute('1,2s/\n//')
+ call assert_equal(['foobarbaz', 'qux'], getline(1, '$'))
+
bwipe!
endfunc
@@ -398,6 +405,11 @@ func Test_substitute_count()
call assert_fails('s/foo/bar/0', 'E939:')
+ call setline(1, ['foo foo', 'foo foo', 'foo foo', 'foo foo', 'foo foo'])
+ 2,4s/foo/bar/ 10
+ call assert_equal(['foo foo', 'foo foo', 'foo foo', 'bar foo', 'bar foo'],
+ \ getline(1, '$'))
+
bwipe!
endfunc
@@ -416,6 +428,10 @@ func Test_substitute_flag_n()
" No substitution should have been done.
call assert_equal(lines, getline(1, '$'))
+ %delete _
+ call setline(1, ['A', 'Bar', 'Baz'])
+ call assert_equal("\n1 match on 1 line", execute('s/\nB\@=//gn'))
+
bwipe!
endfunc
@@ -749,6 +765,45 @@ func Test_sub_beyond_end()
bwipe!
endfunc
+" Test for repeating last substitution using :~ and :&r
+func Test_repeat_last_sub()
+ new
+ call setline(1, ['blue green yellow orange white'])
+ s/blue/red/
+ let @/ = 'yellow'
+ ~
+ let @/ = 'white'
+ :&r
+ let @/ = 'green'
+ s//gray
+ call assert_equal('red gray red orange red', getline(1))
+ close!
+endfunc
+
+" Test for Vi compatible substitution:
+" \/{string}/, \?{string}? and \&{string}&
+func Test_sub_vi_compatibility()
+ new
+ call setline(1, ['blue green yellow orange blue'])
+ let @/ = 'orange'
+ s\/white/
+ let @/ = 'blue'
+ s\?amber?
+ let @/ = 'white'
+ s\&green&
+ call assert_equal('amber green yellow white green', getline(1))
+ close!
+endfunc
+
+" Test for substitute with the new text longer than the original text
+func Test_sub_expand_text()
+ new
+ call setline(1, 'abcabcabcabcabcabcabcabc')
+ s/b/\=repeat('B', 10)/g
+ call assert_equal(repeat('aBBBBBBBBBBc', 8), getline(1))
+ close!
+endfunc
+
func Test_submatch_list_concatenate()
let pat = 'A\(.\)'
let Rep = {-> string([submatch(0, 1)] + [[submatch(1)]])}
diff --git a/src/nvim/testdir/test_swap.vim b/src/nvim/testdir/test_swap.vim
index b3018b2b0d..b180f27685 100644
--- a/src/nvim/testdir/test_swap.vim
+++ b/src/nvim/testdir/test_swap.vim
@@ -1,6 +1,7 @@
" Tests for the swap feature
source check.vim
+source shared.vim
func s:swapname()
return trim(execute('swapname'))
@@ -198,14 +199,17 @@ func Test_swapfile_delete()
quit
call assert_equal(fnamemodify(swapfile_name, ':t'), fnamemodify(s:swapname, ':t'))
- " Write the swapfile with a modified PID, now it will be automatically
- " deleted. Process one should never be Vim.
- let swapfile_bytes[24:27] = 0z01000000
- call writefile(swapfile_bytes, swapfile_name)
- let s:swapname = ''
- split XswapfileText
- quit
- call assert_equal('', s:swapname)
+ " This test won't work as root because root can successfully run kill(1, 0)
+ if !IsRoot()
+ " Write the swapfile with a modified PID, now it will be automatically
+ " deleted. Process one should never be Vim.
+ let swapfile_bytes[24:27] = 0z01000000
+ call writefile(swapfile_bytes, swapfile_name)
+ let s:swapname = ''
+ split XswapfileText
+ quit
+ call assert_equal('', s:swapname)
+ endif
" Now set the modified flag, the swap file will not be deleted
let swapfile_bytes[28 + 80 + 899] = 0x55
diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim
index 757866f5dc..b047b53b6f 100644
--- a/src/nvim/testdir/test_syntax.vim
+++ b/src/nvim/testdir/test_syntax.vim
@@ -728,6 +728,56 @@ func Test_syntax_foldlevel()
quit!
endfunc
+func Test_search_syntax_skip()
+ new
+ let lines =<< trim END
+
+ /* This is VIM */
+ Another Text for VIM
+ let a = "VIM"
+ END
+ call setline(1, lines)
+ syntax on
+ syntax match Comment "^/\*.*\*/"
+ syntax match String '".*"'
+
+ " Skip argument using string evaluation.
+ 1
+ call search('VIM', 'w', '', 0, 'synIDattr(synID(line("."), col("."), 1), "name") =~? "comment"')
+ call assert_equal('Another Text for VIM', getline('.'))
+ 1
+ call search('VIM', 'w', '', 0, 'synIDattr(synID(line("."), col("."), 1), "name") !~? "string"')
+ call assert_equal(' let a = "VIM"', getline('.'))
+
+ " Skip argument using Lambda.
+ 1
+ call search('VIM', 'w', '', 0, { -> synIDattr(synID(line("."), col("."), 1), "name") =~? "comment"})
+ call assert_equal('Another Text for VIM', getline('.'))
+
+ 1
+ call search('VIM', 'w', '', 0, { -> synIDattr(synID(line("."), col("."), 1), "name") !~? "string"})
+ call assert_equal(' let a = "VIM"', getline('.'))
+
+ " Skip argument using funcref.
+ func InComment()
+ return synIDattr(synID(line("."), col("."), 1), "name") =~? "comment"
+ endfunc
+ func InString()
+ return synIDattr(synID(line("."), col("."), 1), "name") !~? "string"
+ endfunc
+ 1
+ call search('VIM', 'w', '', 0, function('InComment'))
+ call assert_equal('Another Text for VIM', getline('.'))
+
+ 1
+ call search('VIM', 'w', '', 0, function('InString'))
+ call assert_equal(' let a = "VIM"', getline('.'))
+
+ delfunc InComment
+ delfunc InString
+ bwipe!
+endfunc
+
func Test_syn_include_contains_TOP()
let l:case = "TOP in included syntax means its group list name"
new
diff --git a/src/nvim/testdir/test_system.vim b/src/nvim/testdir/test_system.vim
index 1858b48807..5b8079d7b6 100644
--- a/src/nvim/testdir/test_system.vim
+++ b/src/nvim/testdir/test_system.vim
@@ -46,9 +46,9 @@ func Test_System()
bwipe!
call assert_fails('call system("wc -l", 99999)', 'E86:')
-endfunction
+endfunc
-function! Test_system_exmode()
+func Test_system_exmode()
if has('unix') " echo $? only works on Unix
let cmd = ' -es --headless -u NONE -c "source Xscript" +q; echo "result=$?"'
" Need to put this in a script, "catch" isn't found after an unknown
diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim
index 2aa04df42a..e0b05edf15 100644
--- a/src/nvim/testdir/test_tagjump.vim
+++ b/src/nvim/testdir/test_tagjump.vim
@@ -641,7 +641,7 @@ func Test_tag_envvar()
endfunc
" Test for :ptag
-func Test_ptag()
+func Test_tag_preview()
call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
\ "second\tXfile1\t2",
\ "third\tXfile1\t3",],
@@ -655,11 +655,19 @@ func Test_ptag()
call assert_equal(2, winnr('$'))
call assert_equal(1, getwinvar(1, '&previewwindow'))
call assert_equal(0, getwinvar(2, '&previewwindow'))
- wincmd w
+ wincmd P
call assert_equal(3, line('.'))
" jump to the tag again
+ wincmd w
ptag third
+ wincmd P
+ call assert_equal(3, line('.'))
+
+ " jump to the newer tag
+ wincmd w
+ ptag
+ wincmd P
call assert_equal(3, line('.'))
" close the preview window
@@ -829,8 +837,8 @@ func Test_tag_last_search_pat()
%bwipe
endfunc
-" Test for jumping to a tag when the tag stack is full
-func Test_tag_stack_full()
+" Tag stack tests
+func Test_tag_stack()
let l = []
for i in range(10, 31)
let l += ["var" .. i .. "\tXfoo\t/^int var" .. i .. ";$/"]
@@ -844,6 +852,7 @@ func Test_tag_stack_full()
endfor
call writefile(l, 'Xfoo')
+ " Jump to a tag when the tag stack is full. Oldest entry should be removed.
enew
for i in range(10, 30)
exe "tag var" .. i
@@ -856,9 +865,15 @@ func Test_tag_stack_full()
call assert_equal('var12', l.items[0].tagname)
call assert_equal('var31', l.items[19].tagname)
- " Jump from the top of the stack
+ " Use tnext with a single match
+ call assert_fails('tnext', 'E427:')
+
+ " Jump to newest entry from the top of the stack
call assert_fails('tag', 'E556:')
+ " Pop with zero count from the top of the stack
+ call assert_fails('0pop', 'E556:')
+
" Pop from an unsaved buffer
enew!
call append(1, "sample text")
@@ -869,6 +884,13 @@ func Test_tag_stack_full()
" Pop all the entries in the tag stack
call assert_fails('30pop', 'E555:')
+ " Pop with a count when already at the bottom of the stack
+ call assert_fails('exe "normal 4\<C-T>"', 'E555:')
+ call assert_equal(1, gettagstack().curidx)
+
+ " Jump to newest entry from the bottom of the stack with zero count
+ call assert_fails('0tag', 'E555:')
+
" Pop the tag stack when it is empty
call settagstack(1, {'items' : []})
call assert_fails('pop', 'E73:')
@@ -895,6 +917,7 @@ func Test_tag_multimatch()
[CODE]
call writefile(code, 'Xfoo')
+ call settagstack(1, {'items' : []})
tag first
tlast
call assert_equal(3, line('.'))
@@ -903,6 +926,151 @@ func Test_tag_multimatch()
call assert_equal(1, line('.'))
call assert_fails('tprev', 'E425:')
+ tlast
+ call feedkeys("5\<CR>", 't')
+ tselect first
+ call assert_equal(2, gettagstack().curidx)
+
+ set ignorecase
+ tag FIRST
+ tnext
+ call assert_equal(2, line('.'))
+ set ignorecase&
+
+ call delete('Xtags')
+ call delete('Xfoo')
+ set tags&
+ %bwipe
+endfunc
+
+" Test for previewing multiple matching tags
+func Test_preview_tag_multimatch()
+ call writefile([
+ \ "!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "first\tXfoo\t1",
+ \ "first\tXfoo\t2",
+ \ "first\tXfoo\t3"],
+ \ 'Xtags')
+ set tags=Xtags
+ let code =<< trim [CODE]
+ int first() {}
+ int first() {}
+ int first() {}
+ [CODE]
+ call writefile(code, 'Xfoo')
+
+ enew | only
+ ptag first
+ ptlast
+ wincmd P
+ call assert_equal(3, line('.'))
+ wincmd w
+ call assert_fails('ptnext', 'E428:')
+ ptprev
+ wincmd P
+ call assert_equal(2, line('.'))
+ wincmd w
+ ptfirst
+ wincmd P
+ call assert_equal(1, line('.'))
+ wincmd w
+ call assert_fails('ptprev', 'E425:')
+ ptnext
+ wincmd P
+ call assert_equal(2, line('.'))
+ wincmd w
+ ptlast
+ call feedkeys("5\<CR>", 't')
+ ptselect first
+ wincmd P
+ call assert_equal(3, line('.'))
+
+ pclose
+
+ call delete('Xtags')
+ call delete('Xfoo')
+ set tags&
+ %bwipe
+endfunc
+
+" Test for jumping to multiple matching tags across multiple :tags commands
+func Test_tnext_multimatch()
+ call writefile([
+ \ "!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "first\tXfoo1\t1",
+ \ "first\tXfoo2\t1",
+ \ "first\tXfoo3\t1"],
+ \ 'Xtags')
+ set tags=Xtags
+ let code =<< trim [CODE]
+ int first() {}
+ [CODE]
+ call writefile(code, 'Xfoo1')
+ call writefile(code, 'Xfoo2')
+ call writefile(code, 'Xfoo3')
+
+ tag first
+ tag first
+ pop
+ tnext
+ tnext
+ call assert_fails('tnext', 'E428:')
+
+ call delete('Xtags')
+ call delete('Xfoo1')
+ call delete('Xfoo2')
+ call delete('Xfoo3')
+ set tags&
+ %bwipe
+endfunc
+
+" Test for jumping to multiple matching tags in non-existing files
+func Test_multimatch_non_existing_files()
+ call writefile([
+ \ "!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "first\tXfoo1\t1",
+ \ "first\tXfoo2\t1",
+ \ "first\tXfoo3\t1"],
+ \ 'Xtags')
+ set tags=Xtags
+
+ call settagstack(1, {'items' : []})
+ call assert_fails('tag first', 'E429:')
+ call assert_equal(3, gettagstack().items[0].matchnr)
+
+ call delete('Xtags')
+ set tags&
+ %bwipe
+endfunc
+
+func Test_tselect_listing()
+ call writefile([
+ \ "!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "first\tXfoo\t1" .. ';"' .. "\tv\ttyperef:typename:int\tfile:",
+ \ "first\tXfoo\t2" .. ';"' .. "\tv\ttyperef:typename:char\tfile:"],
+ \ 'Xtags')
+ set tags=Xtags
+
+ let code =<< trim [CODE]
+ static int first;
+ static char first;
+ [CODE]
+ call writefile(code, 'Xfoo')
+
+ call feedkeys("\<CR>", "t")
+ let l = split(execute("tselect first"), "\n")
+ let expected =<< [DATA]
+ # pri kind tag file
+ 1 FS v first Xfoo
+ typeref:typename:int
+ 1
+ 2 FS v first Xfoo
+ typeref:typename:char
+ 2
+Type number and <Enter> (q or empty cancels):
+[DATA]
+ call assert_equal(expected, l)
+
call delete('Xtags')
call delete('Xfoo')
set tags&
diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim
index bf0706a0c2..e9f846af7b 100644
--- a/src/nvim/testdir/test_textformat.vim
+++ b/src/nvim/testdir/test_textformat.vim
@@ -196,6 +196,130 @@ func Test_text_format()
enew!
endfunc
+func Test_format_c_comment()
+ new
+ setl ai cindent tw=40 et fo=croql
+ let text =<< trim END
+ var = 2345; // asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf
+ END
+ call setline(1, text)
+ normal gql
+ let expected =<< trim END
+ var = 2345; // asdf asdf asdf asdf asdf
+ // asdf asdf asdf asdf asdf
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ %del
+ let text =<< trim END
+ var = 2345; // asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf
+ END
+ call setline(1, text)
+ normal gql
+ let expected =<< trim END
+ var = 2345; // asdf asdf asdf asdf asdf
+ // asdf asdf asdf asdf asdf
+ // asdf asdf
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ %del
+ let text =<< trim END
+ #if 0 // This is another long end of
+ // line comment that
+ // wraps.
+ END
+ call setline(1, text)
+ normal gq2j
+ let expected =<< trim END
+ #if 0 // This is another long
+ // end of line comment
+ // that wraps.
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ " Using either "o" or "O" repeats a line comment occupying a whole line.
+ %del
+ let text =<< trim END
+ nop;
+ // This is a comment
+ val = val;
+ END
+ call setline(1, text)
+ normal 2Go
+ let expected =<< trim END
+ nop;
+ // This is a comment
+ //
+ val = val;
+ END
+ call assert_equal(expected, getline(1, '$'))
+ normal 2GO
+ let expected =<< trim END
+ nop;
+ //
+ // This is a comment
+ //
+ val = val;
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ " Using "o" repeats a line comment after a statement, "O" does not.
+ %del
+ let text =<< trim END
+ nop;
+ val = val; // This is a comment
+ END
+ call setline(1, text)
+ normal 2Go
+ let expected =<< trim END
+ nop;
+ val = val; // This is a comment
+ //
+ END
+ call assert_equal(expected, getline(1, '$'))
+ normal 2GO
+ let expected =<< trim END
+ nop;
+
+ val = val; // This is a comment
+ //
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ " Using "o" does not repeat a comment in a string
+ %del
+ let text =<< trim END
+ nop;
+ val = " // This is not a comment";
+ END
+ call setline(1, text)
+ normal 2Gox
+ let expected =<< trim END
+ nop;
+ val = " // This is not a comment";
+ x
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ " Using CTRL-U after "o" fixes the indent
+ %del
+ let text =<< trim END
+ {
+ val = val; // This is a comment
+ END
+ call setline(1, text)
+ exe "normal! 2Go\<C-U>x\<Esc>"
+ let expected =<< trim END
+ {
+ val = val; // This is a comment
+ x
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+endfunc
+
" Tests for :right, :center and :left on text with embedded TAB.
func Test_format_align()
enew!
@@ -433,6 +557,21 @@ func Test_format_align()
call assert_equal("\t\t Vim", getline(1))
q!
+ " align text with 'rightleft'
+ if has('rightleft')
+ new
+ call setline(1, 'Vim')
+ setlocal rightleft
+ left 20
+ setlocal norightleft
+ call assert_equal("\t\t Vim", getline(1))
+ setlocal rightleft
+ right
+ setlocal norightleft
+ call assert_equal("Vim", getline(1))
+ close!
+ endif
+
set tw&
endfunc
diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim
index aae315b2c5..09eed4e10d 100644
--- a/src/nvim/testdir/test_timers.vim
+++ b/src/nvim/testdir/test_timers.vim
@@ -379,4 +379,27 @@ func Test_timer_invalid_callback()
call assert_fails('call timer_start(0, "0")', 'E921')
endfunc
+func Test_timer_using_win_execute_undo_sync()
+ let bufnr1 = bufnr()
+ new
+ let g:bufnr2 = bufnr()
+ let g:winid = win_getid()
+ exe "buffer " .. bufnr1
+ wincmd w
+ call setline(1, ['test'])
+ autocmd InsertEnter * call timer_start(100, { -> win_execute(g:winid, 'buffer ' .. g:bufnr2) })
+ call timer_start(200, { -> feedkeys("\<CR>bbbb\<Esc>") })
+ call feedkeys("Oaaaa", 'x!t')
+ " will hang here until the second timer fires
+ call assert_equal(['aaaa', 'bbbb', 'test'], getline(1, '$'))
+ undo
+ call assert_equal(['test'], getline(1, '$'))
+
+ bwipe!
+ bwipe!
+ unlet g:winid
+ unlet g:bufnr2
+ au! InsertEnter
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_usercommands.vim b/src/nvim/testdir/test_usercommands.vim
index 29e578ac6d..967ad85a64 100644
--- a/src/nvim/testdir/test_usercommands.vim
+++ b/src/nvim/testdir/test_usercommands.vim
@@ -269,10 +269,10 @@ endfunc
func Test_CmdCompletion()
call feedkeys(":com -\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"com -addr bang bar buffer complete count nargs range register', @:)
+ call assert_equal('"com -addr bang bar buffer complete count keepscript nargs range register', @:)
call feedkeys(":com -nargs=0 -\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"com -nargs=0 -addr bang bar buffer complete count nargs range register', @:)
+ call assert_equal('"com -nargs=0 -addr bang bar buffer complete count keepscript nargs range register', @:)
call feedkeys(":com -nargs=\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"com -nargs=* + 0 1 ?', @:)
@@ -350,7 +350,6 @@ func Test_use_execute_in_completion()
endfunc
func Test_addr_all()
- throw 'skipped: requires patch v8.1.0341 to pass'
command! -addr=lines DoSomething let g:a1 = <line1> | let g:a2 = <line2>
%DoSomething
call assert_equal(1, g:a1)
@@ -551,3 +550,26 @@ func Test_command_list()
call assert_equal("\nNo user-defined commands found", execute(':command Xxx'))
call assert_equal("\nNo user-defined commands found", execute('command'))
endfunc
+
+func Test_delcommand_buffer()
+ command Global echo 'global'
+ command -buffer OneBuffer echo 'one'
+ new
+ command -buffer TwoBuffer echo 'two'
+ call assert_equal(0, exists(':OneBuffer'))
+ call assert_equal(2, exists(':Global'))
+ call assert_equal(2, exists(':TwoBuffer'))
+ delcommand -buffer TwoBuffer
+ call assert_equal(0, exists(':TwoBuffer'))
+ call assert_fails('delcommand -buffer Global', 'E1237:')
+ call assert_fails('delcommand -buffer OneBuffer', 'E1237:')
+ bwipe!
+ call assert_equal(2, exists(':OneBuffer'))
+ delcommand -buffer OneBuffer
+ call assert_equal(0, exists(':OneBuffer'))
+ call assert_fails('delcommand -buffer Global', 'E1237:')
+ delcommand Global
+ call assert_equal(0, exists(':Global'))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_utf8.vim b/src/nvim/testdir/test_utf8.vim
index 0818c2e4b0..36776d5a64 100644
--- a/src/nvim/testdir/test_utf8.vim
+++ b/src/nvim/testdir/test_utf8.vim
@@ -1,5 +1,6 @@
" Tests for Unicode manipulations
+source check.vim
source view_util.vim
" Visual block Insert adjusts for multi-byte char
@@ -148,4 +149,55 @@ func Test_print_overlong()
bwipe!
endfunc
+func Test_recording_with_select_mode_utf8()
+ call Run_test_recording_with_select_mode_utf8()
+endfunc
+
+func Run_test_recording_with_select_mode_utf8()
+ new
+
+ " No escaping
+ call feedkeys("qacc12345\<Esc>gH哦\<Esc>q", "tx")
+ call assert_equal("哦", getline(1))
+ call assert_equal("cc12345\<Esc>gH哦\<Esc>", @a)
+ call setline(1, 'asdf')
+ normal! @a
+ call assert_equal("哦", getline(1))
+
+ " 固 is 0xE5 0x9B 0xBA where 0x9B is CSI
+ call feedkeys("qacc12345\<Esc>gH固\<Esc>q", "tx")
+ call assert_equal("固", getline(1))
+ call assert_equal("cc12345\<Esc>gH固\<Esc>", @a)
+ call setline(1, 'asdf')
+ normal! @a
+ call assert_equal("固", getline(1))
+
+ " 四 is 0xE5 0x9B 0x9B where 0x9B is CSI
+ call feedkeys("qacc12345\<Esc>gH四\<Esc>q", "tx")
+ call assert_equal("四", getline(1))
+ call assert_equal("cc12345\<Esc>gH四\<Esc>", @a)
+ call setline(1, 'asdf')
+ normal! @a
+ call assert_equal("四", getline(1))
+
+ " 倒 is 0xE5 0x80 0x92 where 0x80 is K_SPECIAL
+ call feedkeys("qacc12345\<Esc>gH倒\<Esc>q", "tx")
+ call assert_equal("倒", getline(1))
+ call assert_equal("cc12345\<Esc>gH倒\<Esc>", @a)
+ call setline(1, 'asdf')
+ normal! @a
+ call assert_equal("倒", getline(1))
+
+ bwipe!
+endfunc
+
+" This must be done as one of the last tests, because it starts the GUI, which
+" cannot be undone.
+func Test_zz_recording_with_select_mode_utf8_gui()
+ CheckCanRunGui
+
+ gui -f
+ call Run_test_recording_with_select_mode_utf8()
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_utf8_comparisons.vim b/src/nvim/testdir/test_utf8_comparisons.vim
index fdf9d80802..f3c86b44fb 100644
--- a/src/nvim/testdir/test_utf8_comparisons.vim
+++ b/src/nvim/testdir/test_utf8_comparisons.vim
@@ -1,12 +1,12 @@
" Tests for case-insensitive UTF-8 comparisons (utf_strnicmp() in mbyte.c)
" Also test "g~ap".
-function! Ch(a, op, b, expected)
+func Ch(a, op, b, expected)
call assert_equal(eval(printf('"%s" %s "%s"', a:a, a:op, a:b)), a:expected,
\ printf('"%s" %s "%s" should return %d', a:a, a:op, a:b, a:expected))
-endfunction
+endfunc
-function! Chk(a, b, result)
+func Chk(a, b, result)
if a:result == 0
call Ch(a:a, '==?', a:b, 1)
call Ch(a:a, '!=?', a:b, 0)
@@ -86,6 +86,9 @@ endfunc
" test that g~ap changes one paragraph only.
func Test_gap()
new
- call feedkeys("iabcd\n\ndefggg0g~ap", "tx")
+ " setup text
+ call feedkeys("iabcd\<cr>\<cr>defg", "tx")
+ " modify only first line
+ call feedkeys("gg0g~ap", "tx")
call assert_equal(["ABCD", "", "defg"], getline(1,3))
endfunc
diff --git a/src/nvim/testdir/test_vartabs.vim b/src/nvim/testdir/test_vartabs.vim
index 46e0d62313..017bb6675d 100644
--- a/src/nvim/testdir/test_vartabs.vim
+++ b/src/nvim/testdir/test_vartabs.vim
@@ -135,7 +135,17 @@ func Test_vartabs()
bwipeout!
endfunc
-func! Test_vartabs_breakindent()
+func Test_retab_invalid_arg()
+ new
+ call setline(1, "\ttext")
+ retab 0
+ call assert_fails("retab -8", 'E487: Argument must be positive')
+ call assert_fails("retab 10000", 'E475:')
+ call assert_fails("retab 720575940379279360", 'E475:')
+ bwipe!
+endfunc
+
+func Test_vartabs_breakindent()
if !exists("+breakindent")
return
endif
diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim
index 34733f127f..75a965f16d 100644
--- a/src/nvim/testdir/test_vimscript.vim
+++ b/src/nvim/testdir/test_vimscript.vim
@@ -25,7 +25,7 @@ com! -nargs=1 Xout call Xout(<args>)
" in the variable argument list. This function is useful if similar tests are
" to be made for a ":return" from a function call or a ":finish" in a script
" file.
-function! MakeScript(funcname, ...)
+func MakeScript(funcname, ...)
let script = tempname()
execute "redir! >" . script
execute "function" a:funcname
@@ -1156,6 +1156,82 @@ func Test_type()
call assert_equal(v:t_list, type(v:_null_list))
call assert_equal(v:t_dict, type(v:_null_dict))
call assert_equal(v:t_blob, type(v:_null_blob))
+
+ call assert_equal(0, 0 + v:false)
+ call assert_equal(1, 0 + v:true)
+ " call assert_equal(0, 0 + v:none)
+ call assert_equal(0, 0 + v:null)
+
+ call assert_equal('false', '' . v:false)
+ call assert_equal('true', '' . v:true)
+ " call assert_equal('none', '' . v:none)
+ call assert_equal('null', '' . v:null)
+
+ call assert_true(v:false == 0)
+ call assert_false(v:false != 0)
+ call assert_true(v:true == 1)
+ call assert_false(v:true != 1)
+ call assert_false(v:true == v:false)
+ call assert_true(v:true != v:false)
+
+ call assert_true(v:null == 0)
+ call assert_false(v:null != 0)
+ " call assert_true(v:none == 0)
+ " call assert_false(v:none != 0)
+
+ call assert_true(v:false is v:false)
+ call assert_true(v:true is v:true)
+ " call assert_true(v:none is v:none)
+ call assert_true(v:null is v:null)
+
+ call assert_false(v:false isnot v:false)
+ call assert_false(v:true isnot v:true)
+ " call assert_false(v:none isnot v:none)
+ call assert_false(v:null isnot v:null)
+
+ call assert_false(v:false is 0)
+ call assert_false(v:true is 1)
+ call assert_false(v:true is v:false)
+ " call assert_false(v:none is 0)
+ call assert_false(v:null is 0)
+ " call assert_false(v:null is v:none)
+
+ call assert_true(v:false isnot 0)
+ call assert_true(v:true isnot 1)
+ call assert_true(v:true isnot v:false)
+ " call assert_true(v:none isnot 0)
+ call assert_true(v:null isnot 0)
+ " call assert_true(v:null isnot v:none)
+
+ call assert_equal(v:false, eval(string(v:false)))
+ call assert_equal(v:true, eval(string(v:true)))
+ " call assert_equal(v:none, eval(string(v:none)))
+ call assert_equal(v:null, eval(string(v:null)))
+
+ call assert_equal(v:false, copy(v:false))
+ call assert_equal(v:true, copy(v:true))
+ " call assert_equal(v:none, copy(v:none))
+ call assert_equal(v:null, copy(v:null))
+
+ call assert_equal([v:false], deepcopy([v:false]))
+ call assert_equal([v:true], deepcopy([v:true]))
+ " call assert_equal([v:none], deepcopy([v:none]))
+ call assert_equal([v:null], deepcopy([v:null]))
+
+ call assert_true(empty(v:false))
+ call assert_false(empty(v:true))
+ call assert_true(empty(v:null))
+ " call assert_true(empty(v:none))
+
+ func ChangeYourMind()
+ try
+ return v:true
+ finally
+ return 'something else'
+ endtry
+ endfunc
+
+ call ChangeYourMind()
endfunc
"-------------------------------------------------------------------------------
@@ -1692,6 +1768,26 @@ func Test_function_defined_line()
call delete('Xtest.vim')
endfunc
+func Test_for_over_string()
+ let res = ''
+ for c in 'aéc̀d'
+ let res ..= c .. '-'
+ endfor
+ call assert_equal('a-é-c̀-d-', res)
+
+ let res = ''
+ for c in ''
+ let res ..= c .. '-'
+ endfor
+ call assert_equal('', res)
+
+ let res = ''
+ for c in v:_null_string
+ let res ..= c .. '-'
+ endfor
+ call assert_equal('', res)
+endfunc
+
"-------------------------------------------------------------------------------
" Modelines {{{1
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/nvim/testdir/test_virtualedit.vim b/src/nvim/testdir/test_virtualedit.vim
index 8f992f7501..250b896532 100644
--- a/src/nvim/testdir/test_virtualedit.vim
+++ b/src/nvim/testdir/test_virtualedit.vim
@@ -81,6 +81,133 @@ func Test_edit_change()
normal Cx
call assert_equal('x', getline(1))
bwipe!
+ set virtualedit=
+endfunc
+
+" Tests for pasting at the beginning, end and middle of a tab character
+" in virtual edit mode.
+func Test_paste_in_tab()
+ new
+ call append(0, '')
+ set virtualedit=all
+
+ " Tests for pasting a register with characterwise mode type
+ call setreg('"', 'xyz', 'c')
+
+ " paste (p) unnamed register at the beginning of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 0)
+ normal p
+ call assert_equal('a xyz b', getline(1))
+
+ " paste (P) unnamed register at the beginning of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 0)
+ normal P
+ call assert_equal("axyz\tb", getline(1))
+
+ " paste (p) unnamed register at the end of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 6)
+ normal p
+ call assert_equal("a\txyzb", getline(1))
+
+ " paste (P) unnamed register at the end of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 6)
+ normal P
+ call assert_equal('a xyz b', getline(1))
+
+ " Tests for pasting a register with blockwise mode type
+ call setreg('"', 'xyz', 'b')
+
+ " paste (p) unnamed register at the beginning of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 0)
+ normal p
+ call assert_equal('a xyz b', getline(1))
+
+ " paste (P) unnamed register at the beginning of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 0)
+ normal P
+ call assert_equal("axyz\tb", getline(1))
+
+ " paste (p) unnamed register at the end of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 6)
+ normal p
+ call assert_equal("a\txyzb", getline(1))
+
+ " paste (P) unnamed register at the end of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 6)
+ normal P
+ call assert_equal('a xyz b', getline(1))
+
+ " Tests for pasting with gp and gP in virtual edit mode
+
+ " paste (gp) unnamed register at the beginning of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 0)
+ normal gp
+ call assert_equal('a xyz b', getline(1))
+ call assert_equal([0, 1, 12, 0, 12], getcurpos())
+
+ " paste (gP) unnamed register at the beginning of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 0)
+ normal gP
+ call assert_equal("axyz\tb", getline(1))
+ call assert_equal([0, 1, 5, 0, 5], getcurpos())
+
+ " paste (gp) unnamed register at the end of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 6)
+ normal gp
+ call assert_equal("a\txyzb", getline(1))
+ call assert_equal([0, 1, 6, 0, 12], getcurpos())
+
+ " paste (gP) unnamed register at the end of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 6)
+ normal gP
+ call assert_equal('a xyz b', getline(1))
+ call assert_equal([0, 1, 12, 0, 12], getcurpos())
+
+ " Tests for pasting a named register
+ let @r = 'xyz'
+
+ " paste (gp) named register in the middle of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 2)
+ normal "rgp
+ call assert_equal('a xyz b', getline(1))
+ call assert_equal([0, 1, 8, 0, 8], getcurpos())
+
+ " paste (gP) named register in the middle of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 2)
+ normal "rgP
+ call assert_equal('a xyz b', getline(1))
+ call assert_equal([0, 1, 7, 0, 7], getcurpos())
+
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test for yanking a few spaces within a tab to a register
+func Test_yank_in_tab()
+ new
+ let @r = ''
+ call setline(1, "a\tb")
+ set virtualedit=all
+ call cursor(1, 2, 2)
+ normal "ry5l
+ call assert_equal(' ', @r)
+
+ bwipe!
+ set virtualedit=
endfunc
" Insert "keyword keyw", ESC, C CTRL-N, shows "keyword ykeyword".
@@ -216,4 +343,139 @@ func Test_yank_paste_small_del_reg()
set virtualedit=
endfunc
+" After calling s:TryVirtualeditReplace(), line 1 will contain one of these
+" two strings, depending on whether virtual editing is on or off.
+let s:result_ve_on = 'a x'
+let s:result_ve_off = 'x'
+
+" Utility function for Test_global_local_virtualedit()
+func s:TryVirtualeditReplace()
+ call setline(1, 'a')
+ normal gg7l
+ normal rx
+endfunc
+
+" Test for :set and :setlocal
+func Test_global_local_virtualedit()
+ new
+
+ " Verify that 'virtualedit' is initialized to empty, can be set globally to
+ " all and to empty, and can be set locally to all and to empty.
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ set ve=all
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ set ve=
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ setlocal ve=all
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ setlocal ve=
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+
+ " Verify that :set affects multiple windows.
+ split
+ set ve=all
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ wincmd p
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ set ve=
+ wincmd p
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ bwipe!
+
+ " Verify that :setlocal affects only the current window.
+ new
+ split
+ setlocal ve=all
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ wincmd p
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ bwipe!
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+
+ " Verify that the buffer 'virtualedit' state follows the global value only
+ " when empty and that "none" works as expected.
+ "
+ " 'virtualedit' State
+ " +--------+--------------------------+
+ " | Local | Global |
+ " | | |
+ " +--------+--------+--------+--------+
+ " | | "" | "all" | "none" |
+ " +--------+--------+--------+--------+
+ " | "" | off | on | off |
+ " | "all" | on | on | on |
+ " | "none" | off | off | off |
+ " +--------+--------+--------+--------+
+ new
+
+ setglobal ve=
+ setlocal ve=
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ setlocal ve=all
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ setlocal ve=none
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+
+ setglobal ve=all
+ setlocal ve=
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ setlocal ve=all
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ setlocal ve=none
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ setlocal ve=NONE
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+
+ setglobal ve=none
+ setlocal ve=
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ setlocal ve=all
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ setlocal ve=none
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+
+ bwipe!
+
+ " Verify that the 'virtualedit' state is copied to new windows.
+ new
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ split
+ setlocal ve=all
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ split
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ setlocal ve=
+ split
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ bwipe!
+
+ setlocal virtualedit&
+ set virtualedit&
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim
index 8344598486..099a90643f 100644
--- a/src/nvim/testdir/test_visual.vim
+++ b/src/nvim/testdir/test_visual.vim
@@ -433,6 +433,19 @@ func Test_Visual_Block()
close!
endfunc
+" Test for 'p'ut in visual block mode
+func Test_visual_block_put()
+ enew
+
+ call append(0, ['One', 'Two', 'Three'])
+ normal gg
+ yank
+ call feedkeys("jl\<C-V>ljp", 'xt')
+ call assert_equal(['One', 'T', 'Tee', 'One', ''], getline(1, '$'))
+
+ enew!
+endfunc
+
func Test_visual_put_in_block()
new
call setline(1, ['xxxx', 'y∞yy', 'zzzz'])
@@ -1090,6 +1103,13 @@ func Test_visual_put_blockedit_zy_and_zp()
bw!
endfunc
+func Test_visual_block_yank_zy()
+ new
+ " this was reading before the start of the line
+ exe "norm o\<C-T>\<Esc>\<C-V>zy"
+ bwipe!
+endfunc
+
func Test_visual_block_with_virtualedit()
CheckScreendump
@@ -1104,11 +1124,126 @@ func Test_visual_block_with_virtualedit()
call term_sendkeys(buf, "\<C-V>gg$")
call VerifyScreenDump(buf, 'Test_visual_block_with_virtualedit', {})
+ call term_sendkeys(buf, "\<Esc>gg\<C-V>G$")
+ call VerifyScreenDump(buf, 'Test_visual_block_with_virtualedit2', {})
+
" clean up
call term_sendkeys(buf, "\<Esc>")
call StopVimInTerminal(buf)
- call delete('XTest_beval')
+ call delete('XTest_block')
+endfunc
+
+func Test_visual_block_ctrl_w_f()
+ " Emtpy block selected in new buffer should not result in an error.
+ au! BufNew foo sil norm f
+ edit foo
+
+ au! BufNew
+endfunc
+
+func Test_visual_reselect_with_count()
+ " this was causing an illegal memory access
+ let lines =<< trim END
+
+
+
+ :
+ r<sfile>
+ exe "%norm e3\<c-v>kr\t"
+ :
+
+ :
+ END
+ call writefile(lines, 'XvisualReselect')
+ source XvisualReselect
+
+ bwipe!
+ call delete('XvisualReselect')
endfunc
+" this was leaving the end of the Visual area beyond the end of a line
+func Test_visual_ex_copy_line()
+ new
+ call setline(1, ["aaa", "bbbbbbbbbxbb"])
+ /x
+ exe "normal ggvjfxO"
+ t0
+ normal gNU
+ bwipe!
+endfunc
+
+" This was leaving the end of the Visual area beyond the end of a line.
+" Set 'undolevels' to start a new undo block.
+func Test_visual_undo_deletes_last_line()
+ new
+ call setline(1, ["aaa", "ccc", "dyd"])
+ set undolevels=100
+ exe "normal obbbbbbbbbxbb\<Esc>"
+ set undolevels=100
+ /y
+ exe "normal ggvjfxO"
+ undo
+ normal gNU
+
+ bwipe!
+endfunc
+
+func Test_visual_paste()
+ new
+
+ " v_p overwrites unnamed register.
+ call setline(1, ['xxxx'])
+ call setreg('"', 'foo')
+ call setreg('-', 'bar')
+ normal gg0vp
+ call assert_equal('x', @")
+ call assert_equal('x', @-)
+ call assert_equal('fooxxx', getline(1))
+ normal $vp
+ call assert_equal('x', @")
+ call assert_equal('x', @-)
+ call assert_equal('fooxxx', getline(1))
+ " Test with a different register as unnamed register.
+ call setline(2, ['baz'])
+ normal 2gg0"rD
+ call assert_equal('baz', @")
+ normal gg0vp
+ call assert_equal('f', @")
+ call assert_equal('f', @-)
+ call assert_equal('bazooxxx', getline(1))
+ normal $vp
+ call assert_equal('x', @")
+ call assert_equal('x', @-)
+ call assert_equal('bazooxxf', getline(1))
+
+ if has('clipboard')
+ " v_P does not overwrite unnamed register.
+ call setline(1, ['xxxx'])
+ call setreg('"', 'foo')
+ call setreg('-', 'bar')
+ normal gg0vP
+ call assert_equal('foo', @")
+ call assert_equal('x', @-)
+ call assert_equal('fooxxx', getline(1))
+ normal $vP
+ call assert_equal('foo', @")
+ call assert_equal('x', @-)
+ call assert_equal('fooxxfoo', getline(1))
+ " Test with a different register as unnamed register.
+ call setline(2, ['baz'])
+ normal 2gg0"rD
+ call assert_equal('baz', @")
+ normal gg0vP
+ call assert_equal('baz', @")
+ call assert_equal('f', @-)
+ call assert_equal('bazooxxfoo', getline(1))
+ normal $vP
+ call assert_equal('baz', @")
+ call assert_equal('o', @-)
+ call assert_equal('bazooxxfobaz', getline(1))
+ endif
+
+ bwipe!
+endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_winbuf_close.vim b/src/nvim/testdir/test_winbuf_close.vim
index 7f5b80e8d3..f4878c2397 100644
--- a/src/nvim/testdir/test_winbuf_close.vim
+++ b/src/nvim/testdir/test_winbuf_close.vim
@@ -194,3 +194,22 @@ func Test_tabwin_close()
call assert_true(v:true)
%bwipe!
endfunc
+
+" Test when closing a split window (above/below) restores space to the window
+" below when 'noequalalways' and 'splitright' are set.
+func Test_window_close_splitright_noequalalways()
+ set noequalalways
+ set splitright
+ new
+ let w1 = win_getid()
+ new
+ let w2 = win_getid()
+ execute "normal \<c-w>b"
+ let h = winheight(0)
+ let w = win_getid()
+ new
+ q
+ call assert_equal(h, winheight(0), "Window height does not match eight before opening and closing another window")
+ call assert_equal(w, win_getid(), "Did not return to original window after opening and closing a window")
+endfunc
+
diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim
index a200bf7d42..ef6dec580f 100644
--- a/src/nvim/testdir/test_window_cmd.vim
+++ b/src/nvim/testdir/test_window_cmd.vim
@@ -575,7 +575,7 @@ func Test_winrestcmd()
only
endfunc
-function! Fun_RenewFile()
+func Fun_RenewFile()
" Need to wait a bit for the timestamp to be older.
let old_ftime = getftime("tmp.txt")
while getftime("tmp.txt") == old_ftime
@@ -585,7 +585,7 @@ function! Fun_RenewFile()
sp
wincmd p
edit! tmp.txt
-endfunction
+endfunc
func Test_window_prevwin()
" Can we make this work on MS-Windows?
@@ -939,4 +939,124 @@ func Test_window_resize()
%bwipe!
endfunc
+func Test_win_move_separator()
+ edit a
+ leftabove vsplit b
+ let w = winwidth(0)
+ " check win_move_separator from left window on left window
+ call assert_equal(1, winnr())
+ for offset in range(5)
+ call assert_true(win_move_separator(0, offset))
+ call assert_equal(w + offset, winwidth(0))
+ call assert_true(0->win_move_separator(-offset))
+ call assert_equal(w, winwidth(0))
+ endfor
+ " check win_move_separator from right window on left window number
+ wincmd l
+ call assert_notequal(1, winnr())
+ for offset in range(5)
+ call assert_true(1->win_move_separator(offset))
+ call assert_equal(w + offset, winwidth(1))
+ call assert_true(win_move_separator(1, -offset))
+ call assert_equal(w, winwidth(1))
+ endfor
+ " check win_move_separator from right window on left window ID
+ let id = win_getid(1)
+ for offset in range(5)
+ call assert_true(win_move_separator(id, offset))
+ call assert_equal(w + offset, winwidth(id))
+ call assert_true(id->win_move_separator(-offset))
+ call assert_equal(w, winwidth(id))
+ endfor
+ " check win_move_separator from right window on right window is no-op
+ let w0 = winwidth(0)
+ call assert_true(win_move_separator(0, 1))
+ call assert_equal(w0, winwidth(0))
+ call assert_true(win_move_separator(0, -1))
+ call assert_equal(w0, winwidth(0))
+ " check that win_move_separator doesn't error with offsets beyond moving
+ " possibility
+ call assert_true(win_move_separator(id, 5000))
+ call assert_true(winwidth(id) > w)
+ call assert_true(win_move_separator(id, -5000))
+ call assert_true(winwidth(id) < w)
+ " check that win_move_separator returns false for an invalid window
+ wincmd =
+ let w = winwidth(0)
+ call assert_false(win_move_separator(-1, 1))
+ call assert_equal(w, winwidth(0))
+ " check that win_move_separator returns false for a floating window
+ let id = nvim_open_win(
+ \ 0, 0, #{relative: 'editor', row: 2, col: 2, width: 5, height: 3})
+ let w = winwidth(id)
+ call assert_false(win_move_separator(id, 1))
+ call assert_equal(w, winwidth(id))
+ call nvim_win_close(id, 1)
+ %bwipe!
+endfunc
+
+func Test_win_move_statusline()
+ redraw " This test fails in Nvim without a redraw to clear messages.
+ edit a
+ leftabove split b
+ let h = winheight(0)
+ " check win_move_statusline from top window on top window
+ call assert_equal(1, winnr())
+ for offset in range(5)
+ call assert_true(win_move_statusline(0, offset))
+ call assert_equal(h + offset, winheight(0))
+ call assert_true(0->win_move_statusline(-offset))
+ call assert_equal(h, winheight(0))
+ endfor
+ " check win_move_statusline from bottom window on top window number
+ wincmd j
+ call assert_notequal(1, winnr())
+ for offset in range(5)
+ call assert_true(1->win_move_statusline(offset))
+ call assert_equal(h + offset, winheight(1))
+ call assert_true(win_move_statusline(1, -offset))
+ call assert_equal(h, winheight(1))
+ endfor
+ " check win_move_statusline from bottom window on bottom window
+ let h0 = winheight(0)
+ for offset in range(5)
+ call assert_true(0->win_move_statusline(-offset))
+ call assert_equal(h0 - offset, winheight(0))
+ call assert_equal(1 + offset, &cmdheight)
+ call assert_true(win_move_statusline(0, offset))
+ call assert_equal(h0, winheight(0))
+ call assert_equal(1, &cmdheight)
+ endfor
+ call assert_true(win_move_statusline(0, 1))
+ call assert_equal(h0, winheight(0))
+ call assert_equal(1, &cmdheight)
+ " check win_move_statusline from bottom window on top window ID
+ let id = win_getid(1)
+ for offset in range(5)
+ call assert_true(win_move_statusline(id, offset))
+ call assert_equal(h + offset, winheight(id))
+ call assert_true(id->win_move_statusline(-offset))
+ call assert_equal(h, winheight(id))
+ endfor
+ " check that win_move_statusline doesn't error with offsets beyond moving
+ " possibility
+ call assert_true(win_move_statusline(id, 5000))
+ call assert_true(winheight(id) > h)
+ call assert_true(win_move_statusline(id, -5000))
+ call assert_true(winheight(id) < h)
+ " check that win_move_statusline returns false for an invalid window
+ wincmd =
+ let h = winheight(0)
+ call assert_false(win_move_statusline(-1, 1))
+ call assert_equal(h, winheight(0))
+ " check that win_move_statusline returns false for a floating window
+ let id = nvim_open_win(
+ \ 0, 0, #{relative: 'editor', row: 2, col: 2, width: 5, height: 3})
+ let h = winheight(id)
+ call assert_false(win_move_statusline(id, 1))
+ call assert_equal(h, winheight(id))
+ call nvim_win_close(id, 1)
+ %bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim
index aa7882d129..b42665c9b5 100644
--- a/src/nvim/testdir/test_writefile.vim
+++ b/src/nvim/testdir/test_writefile.vim
@@ -43,7 +43,7 @@ func Test_writefile_fails_gently()
endfunc
func Test_writefile_fails_conversion()
- if !has('iconv') || system('uname -s') =~ 'SunOS'
+ if !has('iconv') || has('sun')
return
endif
" Without a backup file the write won't happen if there is a conversion
@@ -169,9 +169,7 @@ endfunc
" Test for ':w !<cmd>' to pipe lines from the current buffer to an external
" command.
func Test_write_pipe_to_cmd()
- if !has('unix')
- return
- endif
+ CheckUnix
new
call setline(1, ['L1', 'L2', 'L3', 'L4'])
2,3w !cat > Xfile
@@ -371,6 +369,154 @@ func Test_write_file_encoding()
%bw!
endfunc
+" Test for writing and reading a file starting with a BOM.
+" Byte Order Mark (BOM) character for various encodings is below:
+" UTF-8 : EF BB BF
+" UTF-16 (BE): FE FF
+" UTF-16 (LE): FF FE
+" UTF-32 (BE): 00 00 FE FF
+" UTF-32 (LE): FF FE 00 00
+func Test_readwrite_file_with_bom()
+ let utf8_bom = "\xEF\xBB\xBF"
+ let utf16be_bom = "\xFE\xFF"
+ let utf16le_bom = "\xFF\xFE"
+ let utf32be_bom = "\n\n\xFE\xFF"
+ let utf32le_bom = "\xFF\xFE\n\n"
+ let save_fileencoding = &fileencoding
+ set cpoptions+=S
+
+ " Check that editing a latin1 file doesn't see a BOM
+ call writefile(["\xFE\xFElatin-1"], 'Xtest1')
+ edit Xtest1
+ call assert_equal('latin1', &fileencoding)
+ call assert_equal(0, &bomb)
+ set fenc=latin1
+ write Xfile2
+ call assert_equal(["\xFE\xFElatin-1", ''], readfile('Xfile2', 'b'))
+ set bomb fenc=latin1
+ write Xtest3
+ call assert_equal(["\xFE\xFElatin-1", ''], readfile('Xtest3', 'b'))
+ set bomb&
+
+ " Check utf-8 BOM
+ %bw!
+ call writefile([utf8_bom .. "utf-8"], 'Xtest1')
+ edit! Xtest1
+ call assert_equal('utf-8', &fileencoding)
+ call assert_equal(1, &bomb)
+ call assert_equal('utf-8', getline(1))
+ set fenc=latin1
+ write! Xfile2
+ call assert_equal(['utf-8', ''], readfile('Xfile2', 'b'))
+ set fenc=utf-8
+ w! Xtest3
+ call assert_equal([utf8_bom .. "utf-8", ''], readfile('Xtest3', 'b'))
+
+ " Check utf-8 with an error (will fall back to latin-1)
+ %bw!
+ call writefile([utf8_bom .. "utf-8\x80err"], 'Xtest1')
+ edit! Xtest1
+ call assert_equal('latin1', &fileencoding)
+ call assert_equal(0, &bomb)
+ call assert_equal("\xC3\xAF\xC2\xBB\xC2\xBFutf-8\xC2\x80err", getline(1))
+ set fenc=latin1
+ write! Xfile2
+ call assert_equal([utf8_bom .. "utf-8\x80err", ''], readfile('Xfile2', 'b'))
+ set fenc=utf-8
+ w! Xtest3
+ call assert_equal(["\xC3\xAF\xC2\xBB\xC2\xBFutf-8\xC2\x80err", ''],
+ \ readfile('Xtest3', 'b'))
+
+ " Check ucs-2 BOM
+ %bw!
+ call writefile([utf16be_bom .. "\nu\nc\ns\n-\n2\n"], 'Xtest1')
+ edit! Xtest1
+ call assert_equal('utf-16', &fileencoding)
+ call assert_equal(1, &bomb)
+ call assert_equal('ucs-2', getline(1))
+ set fenc=latin1
+ write! Xfile2
+ call assert_equal(["ucs-2", ''], readfile('Xfile2', 'b'))
+ set fenc=ucs-2
+ w! Xtest3
+ call assert_equal([utf16be_bom .. "\nu\nc\ns\n-\n2\n", ''],
+ \ readfile('Xtest3', 'b'))
+
+ " Check ucs-2le BOM
+ %bw!
+ call writefile([utf16le_bom .. "u\nc\ns\n-\n2\nl\ne\n"], 'Xtest1')
+ " Need to add a NUL byte after the NL byte
+ call writefile(0z00, 'Xtest1', 'a')
+ edit! Xtest1
+ call assert_equal('utf-16le', &fileencoding)
+ call assert_equal(1, &bomb)
+ call assert_equal('ucs-2le', getline(1))
+ set fenc=latin1
+ write! Xfile2
+ call assert_equal(["ucs-2le", ''], readfile('Xfile2', 'b'))
+ set fenc=ucs-2le
+ w! Xtest3
+ call assert_equal([utf16le_bom .. "u\nc\ns\n-\n2\nl\ne\n", "\n"],
+ \ readfile('Xtest3', 'b'))
+
+ " Check ucs-4 BOM
+ %bw!
+ call writefile([utf32be_bom .. "\n\n\nu\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\n"], 'Xtest1')
+ edit! Xtest1
+ call assert_equal('ucs-4', &fileencoding)
+ call assert_equal(1, &bomb)
+ call assert_equal('ucs-4', getline(1))
+ set fenc=latin1
+ write! Xfile2
+ call assert_equal(["ucs-4", ''], readfile('Xfile2', 'b'))
+ set fenc=ucs-4
+ w! Xtest3
+ call assert_equal([utf32be_bom .. "\n\n\nu\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\n", ''], readfile('Xtest3', 'b'))
+
+ " Check ucs-4le BOM
+ %bw!
+ call writefile([utf32le_bom .. "u\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\nl\n\n\ne\n\n\n"], 'Xtest1')
+ " Need to add three NUL bytes after the NL byte
+ call writefile(0z000000, 'Xtest1', 'a')
+ edit! Xtest1
+ call assert_equal('ucs-4le', &fileencoding)
+ call assert_equal(1, &bomb)
+ call assert_equal('ucs-4le', getline(1))
+ set fenc=latin1
+ write! Xfile2
+ call assert_equal(["ucs-4le", ''], readfile('Xfile2', 'b'))
+ set fenc=ucs-4le
+ w! Xtest3
+ call assert_equal([utf32le_bom .. "u\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\nl\n\n\ne\n\n\n", "\n\n\n"], readfile('Xtest3', 'b'))
+
+ set cpoptions-=S
+ let &fileencoding = save_fileencoding
+ call delete('Xtest1')
+ call delete('Xfile2')
+ call delete('Xtest3')
+ %bw!
+endfunc
+
+func Test_read_write_bin()
+ " write file missing EOL
+ call writefile(['noeol'], "XNoEolSetEol", 'bS')
+ call assert_equal(0z6E6F656F6C, readfile('XNoEolSetEol', 'B'))
+
+ " when file is read 'eol' is off
+ set nofixeol
+ e! ++ff=unix XNoEolSetEol
+ call assert_equal(0, &eol)
+
+ " writing with 'eol' set adds the newline
+ setlocal eol
+ w
+ call assert_equal(0z6E6F656F6C0A, readfile('XNoEolSetEol', 'B'))
+
+ call delete('XNoEolSetEol')
+ set ff&
+ bwipe! XNoEolSetEol
+endfunc
+
" Check that buffer is written before triggering QuitPre
func Test_wq_quitpre_autocommand()
edit Xsomefile
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index 5fec41f9a5..6b889cf97c 100644
--- a/src/nvim/tui/input.c
+++ b/src/nvim/tui/input.c
@@ -227,7 +227,7 @@ static void forward_modified_utf8(TermInput *input, TermKeyKey *key)
&& !(key->modifiers & TERMKEY_KEYMOD_SHIFT)
&& ASCII_ISUPPER(key->code.codepoint)) {
assert(len <= 62);
- // Make remove for the S-
+ // Make room for the S-
memmove(buf + 3, buf + 1, len - 1);
buf[1] = 'S';
buf[2] = '-';
@@ -315,7 +315,6 @@ static TermKeyResult tk_getkey(TermKey *tk, TermKeyKey *key, bool force)
return force ? termkey_getkey_force(tk, key) : termkey_getkey(tk, key);
}
-static void tinput_timer_cb(TimeWatcher *watcher, void *data);
static void tk_getkeys(TermInput *input, bool force)
{
diff --git a/src/nvim/types.h b/src/nvim/types.h
index 604155c33e..73cd2204d6 100644
--- a/src/nvim/types.h
+++ b/src/nvim/types.h
@@ -32,4 +32,6 @@ typedef enum {
kTrue = 1,
} TriState;
+typedef struct Decoration Decoration;
+
#endif // NVIM_TYPES_H
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index d18f35a43a..2d8df4cad8 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -2633,6 +2633,10 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet)
}
}
+ if (VIsual_active) {
+ check_pos(curbuf, &VIsual);
+ }
+
smsg_attr_keep(0,
_("%" PRId64 " %s; %s #%" PRId64 " %s"),
u_oldcount < 0 ? (int64_t)-u_oldcount : (int64_t)u_oldcount,
diff --git a/src/nvim/version.c b/src/nvim/version.c
index 5e2a81795a..71cca52773 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -2093,7 +2093,7 @@ void list_in_columns(char_u **items, int size, int current)
// The rightmost column doesn't need a separator.
// Sacrifice it to fit in one more column if possible.
- int ncol = (int)(Columns + 1) / width;
+ int ncol = (Columns + 1) / width;
int nrow = item_count / ncol + (item_count % ncol ? 1 : 0);
int cur_row = 1;
diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c
index ba6cfab98b..8a14710351 100644
--- a/src/nvim/viml/parser/expressions.c
+++ b/src/nvim/viml/parser/expressions.c
@@ -1536,7 +1536,7 @@ static inline void east_set_error(const ParserState *const pstate, ExprASTError
/* TODO(ZyX-I): Extend syntax to allow ${expr}. This is needed to */ \
/* handle environment variables like those bash uses for */ \
/* `export -f`: their names consist not only of alphanumeric */ \
- /* characetrs. */ \
+ /* characters. */ \
case kExprNodeComplexIdentifier: \
case kExprNodePlainIdentifier: \
case kExprNodeCurlyBracesIdentifier: { \
diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h
index fe9327b27d..9d0bc9d468 100644
--- a/src/nvim/viml/parser/expressions.h
+++ b/src/nvim/viml/parser/expressions.h
@@ -57,7 +57,7 @@ typedef enum {
} LexExprTokenType;
typedef enum {
- kExprCmpEqual, ///< Equality, unequality.
+ kExprCmpEqual, ///< Equality, inequality.
kExprCmpMatches, ///< Matches regex, not matches regex.
kExprCmpGreater, ///< `>` or `<=`
kExprCmpGreaterOrEqual, ///< `>=` or `<`.
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 1e747a67e9..e09af7a7bb 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -304,7 +304,7 @@ newwindow:
newtab = curtab;
goto_tabpage_tp(oldtab, true, true);
if (curwin == wp) {
- win_close(curwin, false);
+ win_close(curwin, false, false);
}
if (valid_tabpage(newtab)) {
goto_tabpage_tp(newtab, true, true);
@@ -449,7 +449,7 @@ wingotofile:
RESET_BINDING(curwin);
if (do_ecmd(0, ptr, NULL, NULL, ECMD_LASTL, ECMD_HIDE, NULL) == FAIL) {
// Failed to open the file, close the window opened for it.
- win_close(curwin, false);
+ win_close(curwin, false, false);
goto_tabpage_win(oldtab, oldwin);
} else if (nchar == 'F' && lnum >= 0) {
curwin->w_cursor.lnum = lnum;
@@ -577,18 +577,19 @@ static void cmd_with_count(char *cmd, char_u *bufp, size_t bufsize, int64_t Pren
void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err)
{
- win_T *win = find_window_by_handle(window, err), *save_curwin = curwin;
+ win_T *win = find_window_by_handle(window, err);
buf_T *buf = find_buffer_by_handle(buffer, err);
- tabpage_T *tab = win_find_tabpage(win), *save_curtab = curtab;
+ tabpage_T *tab = win_find_tabpage(win);
if (!win || !buf) {
return;
}
-
if (noautocmd) {
block_autocmds();
}
- if (switch_win_noblock(&save_curwin, &save_curtab, win, tab, false) == FAIL) {
+
+ switchwin_T switchwin;
+ if (switch_win_noblock(&switchwin, win, tab, false) == FAIL) {
api_set_error(err,
kErrorTypeException,
"Failed to switch to window %d",
@@ -608,7 +609,7 @@ void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err)
// So do it now.
validate_cursor();
- restore_win_noblock(save_curwin, save_curtab, false);
+ restore_win_noblock(&switchwin, false);
if (noautocmd) {
unblock_autocmds();
}
@@ -1481,8 +1482,6 @@ static void win_init(win_T *newp, win_T *oldp, int flags)
copyFoldingState(oldp, newp);
win_init_some(newp, oldp);
-
- didset_window_options(newp);
}
/*
@@ -2289,7 +2288,7 @@ void close_windows(buf_T *buf, int keep_curwin)
for (win_T *wp = firstwin; wp != NULL && !ONE_WINDOW;) {
if (wp->w_buffer == buf && (!keep_curwin || wp != curwin)
&& !(wp->w_closing || wp->w_buffer->b_locked > 0)) {
- if (win_close(wp, false) == FAIL) {
+ if (win_close(wp, false, false) == FAIL) {
// If closing the window fails give up, to avoid looping forever.
break;
}
@@ -2367,6 +2366,22 @@ bool last_nonfloat(win_T *wp) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
return wp != NULL && firstwin == wp && !(wp->w_next && !wp->w_floating);
}
+/// Check if floating windows can be closed.
+///
+/// @return true if all floating windows can be closed
+static bool can_close_floating_windows(tabpage_T *tab)
+{
+ FOR_ALL_WINDOWS_IN_TAB(wp, tab) {
+ buf_T *buf = wp->w_buffer;
+ int need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1);
+
+ if (need_hide && !buf_hide(buf)) {
+ return false;
+ }
+ }
+ return true;
+}
+
/// Close the possibly last window in a tab page.
///
/// @param win window to close
@@ -2431,7 +2446,7 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, tabpage_T *prev
//
// Called by :quit, :close, :xit, :wq and findtag().
// Returns FAIL when the window was not closed.
-int win_close(win_T *win, bool free_buf)
+int win_close(win_T *win, bool free_buf, bool force)
{
win_T *wp;
bool other_buffer = false;
@@ -2461,9 +2476,18 @@ int win_close(win_T *win, bool free_buf)
}
if ((firstwin == win && lastwin_nofloating() == win)
&& lastwin->w_floating) {
- // TODO(bfredl): we might close the float also instead
- emsg(e_floatonly);
- return FAIL;
+ if (force || can_close_floating_windows(curtab)) {
+ win_T *nextwp;
+ for (win_T *wpp = firstwin; wpp != NULL; wpp = nextwp) {
+ nextwp = wpp->w_next;
+ if (wpp->w_floating) {
+ win_close(wpp, free_buf, force);
+ }
+ }
+ } else {
+ emsg(e_floatonly);
+ return FAIL;
+ }
}
// When closing the last window in a tab page first go to another tab page
@@ -3055,9 +3079,21 @@ static frame_T *win_altframe(win_T *win, tabpage_T *tp)
return frp->fr_prev;
}
+ // By default the next window will get the space that was abandoned by this
+ // window
frame_T *target_fr = frp->fr_next;
frame_T *other_fr = frp->fr_prev;
- if (p_spr || p_sb) {
+
+ // If this is part of a column of windows and 'splitbelow' is true then the
+ // previous window will get the space.
+ if (frp->fr_parent != NULL && frp->fr_parent->fr_layout == FR_COL && p_sb) {
+ target_fr = frp->fr_prev;
+ other_fr = frp->fr_next;
+ }
+
+ // If this is part of a row of windows, and 'splitright' is true then the
+ // previous window will get the space.
+ if (frp->fr_parent != NULL && frp->fr_parent->fr_layout == FR_ROW && p_spr) {
target_fr = frp->fr_prev;
other_fr = frp->fr_next;
}
@@ -3610,7 +3646,9 @@ void close_others(int message, int forceit)
continue;
}
}
- win_close(wp, !buf_hide(wp->w_buffer) && !bufIsChanged(wp->w_buffer));
+ win_close(wp,
+ !buf_hide(wp->w_buffer) && !bufIsChanged(wp->w_buffer),
+ false);
}
if (message && !ONE_WINDOW) {
@@ -5795,7 +5833,6 @@ void win_drag_vsep_line(win_T *dragwin, int offset)
}
fr = curfr; // put fr at window that grows
}
- assert(fr);
// Not enough room
if (room < offset) {
@@ -5808,7 +5845,9 @@ void win_drag_vsep_line(win_T *dragwin, int offset)
}
if (fr == NULL) {
- return; // Safety check, should not happen.
+ // This can happen when calling win_move_separator() on the rightmost
+ // window. Just don't do anything.
+ return;
}
// grow frame fr by offset lines
@@ -6630,20 +6669,27 @@ static win_T *get_snapshot_focus(int idx)
/// triggered, another tabpage access is limited.
///
/// @return FAIL if switching to "win" failed.
-int switch_win(win_T **save_curwin, tabpage_T **save_curtab, win_T *win, tabpage_T *tp,
- bool no_display)
+int switch_win(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_display)
{
block_autocmds();
- return switch_win_noblock(save_curwin, save_curtab, win, tp, no_display);
+ return switch_win_noblock(switchwin, win, tp, no_display);
}
// As switch_win() but without blocking autocommands.
-int switch_win_noblock(win_T **save_curwin, tabpage_T **save_curtab, win_T *win, tabpage_T *tp,
- bool no_display)
+int switch_win_noblock(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_display)
{
- *save_curwin = curwin;
+ memset(switchwin, 0, sizeof(switchwin_T));
+ switchwin->sw_curwin = curwin;
+ if (win == curwin) {
+ switchwin->sw_same_win = true;
+ } else {
+ // Disable Visual selection, because redrawing may fail.
+ switchwin->sw_visual_active = VIsual_active;
+ VIsual_active = false;
+ }
+
if (tp != NULL) {
- *save_curtab = curtab;
+ switchwin->sw_curtab = curtab;
if (no_display) {
curtab->tp_firstwin = firstwin;
curtab->tp_lastwin = lastwin;
@@ -6665,28 +6711,33 @@ int switch_win_noblock(win_T **save_curwin, tabpage_T **save_curtab, win_T *win,
// Restore current tabpage and window saved by switch_win(), if still valid.
// When "no_display" is true the display won't be affected, no redraw is
// triggered.
-void restore_win(win_T *save_curwin, tabpage_T *save_curtab, bool no_display)
+void restore_win(switchwin_T *switchwin, bool no_display)
{
- restore_win_noblock(save_curwin, save_curtab, no_display);
+ restore_win_noblock(switchwin, no_display);
unblock_autocmds();
}
// As restore_win() but without unblocking autocommands.
-void restore_win_noblock(win_T *save_curwin, tabpage_T *save_curtab, bool no_display)
+void restore_win_noblock(switchwin_T *switchwin, bool no_display)
{
- if (save_curtab != NULL && valid_tabpage(save_curtab)) {
+ if (switchwin->sw_curtab != NULL && valid_tabpage(switchwin->sw_curtab)) {
if (no_display) {
curtab->tp_firstwin = firstwin;
curtab->tp_lastwin = lastwin;
- curtab = save_curtab;
+ curtab = switchwin->sw_curtab;
firstwin = curtab->tp_firstwin;
lastwin = curtab->tp_lastwin;
} else {
- goto_tabpage_tp(save_curtab, false, false);
+ goto_tabpage_tp(switchwin->sw_curtab, false, false);
}
}
- if (win_valid(save_curwin)) {
- curwin = save_curwin;
+
+ if (!switchwin->sw_same_win) {
+ VIsual_active = switchwin->sw_visual_active;
+ }
+
+ if (win_valid(switchwin->sw_curwin)) {
+ curwin = switchwin->sw_curwin;
curbuf = curwin->w_buffer;
}
// If called by win_execute() and executing the command changed the
diff --git a/src/nvim/window.h b/src/nvim/window.h
index 7e465a9f08..e2fd2c515d 100644
--- a/src/nvim/window.h
+++ b/src/nvim/window.h
@@ -4,6 +4,7 @@
#include <stdbool.h>
#include "nvim/buffer_defs.h"
+#include "nvim/mark.h"
// Values for file_name_in_line()
#define FNAME_MESS 1 // give error message
@@ -32,6 +33,38 @@
#define MIN_COLUMNS 12 // minimal columns for screen
#define MIN_LINES 2 // minimal lines for screen
+/// Structure used by switch_win() to pass values to restore_win()
+typedef struct {
+ win_T *sw_curwin;
+ tabpage_T *sw_curtab;
+ bool sw_same_win; ///< VIsual_active was not reset
+ bool sw_visual_active;
+} switchwin_T;
+
+/// Execute a block of code in the context of window `wp` in tabpage `tp`.
+/// Ensures the status line is redrawn and cursor position is valid if it is moved.
+#define WIN_EXECUTE(wp, tp, block) \
+ do { \
+ win_T *const wp_ = (wp); \
+ const pos_T curpos_ = wp_->w_cursor; \
+ switchwin_T switchwin_; \
+ if (switch_win_noblock(&switchwin_, wp_, (tp), true) == OK) { \
+ check_cursor(); \
+ block; \
+ } \
+ restore_win_noblock(&switchwin_, true); \
+ /* Update the status line if the cursor moved. */ \
+ if (win_valid(wp_) && !equalpos(curpos_, wp_->w_cursor)) { \
+ wp_->w_redr_status = true; \
+ } \
+ /* In case the command moved the cursor or changed the Visual area, */ \
+ /* check it is valid. */ \
+ check_cursor(); \
+ if (VIsual_active) { \
+ check_pos(curbuf, &VIsual); \
+ } \
+ } while (false)
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "window.h.generated.h"
#endif