aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/CMakeLists.txt41
-rw-r--r--src/nvim/api/buffer.c255
-rw-r--r--src/nvim/api/private/helpers.c381
-rw-r--r--src/nvim/api/vim.c171
-rw-r--r--src/nvim/api/window.c29
-rw-r--r--src/nvim/ascii.h2
-rw-r--r--src/nvim/autocmd.c58
-rw-r--r--src/nvim/buffer.c116
-rw-r--r--src/nvim/buffer.h3
-rw-r--r--src/nvim/buffer_defs.h41
-rw-r--r--src/nvim/buffer_updates.c43
-rw-r--r--src/nvim/change.c44
-rw-r--r--src/nvim/channel.c67
-rw-r--r--src/nvim/charset.c17
-rw-r--r--src/nvim/context.c2
-rw-r--r--src/nvim/decoration.c169
-rw-r--r--src/nvim/decoration.h28
-rw-r--r--src/nvim/diff.c23
-rw-r--r--src/nvim/edit.c146
-rw-r--r--src/nvim/edit.h1
-rw-r--r--src/nvim/eval.c646
-rw-r--r--src/nvim/eval.h33
-rw-r--r--src/nvim/eval.lua10
-rw-r--r--src/nvim/eval/decode.c4
-rw-r--r--src/nvim/eval/encode.c4
-rw-r--r--src/nvim/eval/executor.c2
-rw-r--r--src/nvim/eval/funcs.c732
-rw-r--r--src/nvim/eval/typval.c76
-rw-r--r--src/nvim/eval/typval.h7
-rw-r--r--src/nvim/eval/userfunc.c102
-rw-r--r--src/nvim/event/libuv_process.c16
-rw-r--r--src/nvim/event/multiqueue.c6
-rw-r--r--src/nvim/event/process.c3
-rw-r--r--src/nvim/event/process.h3
-rw-r--r--src/nvim/ex_cmds.c198
-rw-r--r--src/nvim/ex_cmds.h2
-rw-r--r--src/nvim/ex_cmds.lua16
-rw-r--r--src/nvim/ex_cmds2.c236
-rw-r--r--src/nvim/ex_cmds_defs.h4
-rw-r--r--src/nvim/ex_docmd.c74
-rw-r--r--src/nvim/ex_eval.c9
-rw-r--r--src/nvim/ex_getln.c90
-rw-r--r--src/nvim/ex_getln.h1
-rw-r--r--src/nvim/ex_session.c17
-rw-r--r--src/nvim/extmark.c1
-rw-r--r--src/nvim/file_search.c2
-rw-r--r--src/nvim/fileio.c18
-rw-r--r--src/nvim/fold.c7
-rw-r--r--src/nvim/generators/gen_api_dispatch.lua14
-rw-r--r--src/nvim/generators/gen_eval.lua2
-rw-r--r--src/nvim/getchar.c502
-rw-r--r--src/nvim/globals.h22
-rw-r--r--src/nvim/grid_defs.h23
-rw-r--r--src/nvim/hardcopy.c7
-rw-r--r--src/nvim/highlight.c28
-rw-r--r--src/nvim/highlight_defs.h2
-rw-r--r--src/nvim/if_cscope.c2
-rw-r--r--src/nvim/indent.c102
-rw-r--r--src/nvim/indent_c.c628
-rw-r--r--src/nvim/lib/queue.h16
-rw-r--r--src/nvim/log.h9
-rw-r--r--src/nvim/lua/converter.c8
-rw-r--r--src/nvim/lua/converter.h1
-rw-r--r--src/nvim/lua/executor.c281
-rw-r--r--src/nvim/lua/executor.h2
-rw-r--r--src/nvim/lua/treesitter.c17
-rw-r--r--src/nvim/lua/vim.lua198
-rw-r--r--src/nvim/main.c6
-rw-r--r--src/nvim/mbyte.c3
-rw-r--r--src/nvim/memline.c6
-rw-r--r--src/nvim/memline_defs.h8
-rw-r--r--src/nvim/memory.c6
-rw-r--r--src/nvim/menu.c8
-rw-r--r--src/nvim/message.c37
-rw-r--r--src/nvim/misc1.c2
-rw-r--r--src/nvim/mouse.c82
-rw-r--r--src/nvim/move.c4
-rw-r--r--src/nvim/msgpack_rpc/channel.c2
-rw-r--r--src/nvim/normal.c77
-rw-r--r--src/nvim/ops.c112
-rw-r--r--src/nvim/option.c578
-rw-r--r--src/nvim/option_defs.h19
-rw-r--r--src/nvim/options.lua26
-rw-r--r--src/nvim/os/input.c20
-rw-r--r--src/nvim/os/pty_process_unix.c39
-rw-r--r--src/nvim/os/pty_process_unix.h1
-rw-r--r--src/nvim/os/pty_process_win.c98
-rw-r--r--src/nvim/os/pty_process_win.h1
-rw-r--r--src/nvim/os/shell.c8
-rw-r--r--src/nvim/os/time.c19
-rw-r--r--src/nvim/path.c2
-rw-r--r--src/nvim/popupmnu.c20
-rw-r--r--src/nvim/pos.h7
-rw-r--r--src/nvim/quickfix.c19
-rw-r--r--src/nvim/regexp.c10
-rw-r--r--src/nvim/regexp_nfa.c4
-rw-r--r--src/nvim/screen.c439
-rw-r--r--src/nvim/search.c40
-rw-r--r--src/nvim/shada.c16
-rw-r--r--src/nvim/sign.c714
-rw-r--r--src/nvim/sign_defs.h46
-rw-r--r--src/nvim/spell.c13
-rw-r--r--src/nvim/spellfile.c3
-rw-r--r--src/nvim/state.c28
-rw-r--r--src/nvim/syntax.c86
-rw-r--r--src/nvim/syntax.h2
-rw-r--r--src/nvim/tag.c38
-rw-r--r--src/nvim/terminal.c100
-rw-r--r--src/nvim/testdir/Makefile11
-rw-r--r--src/nvim/testdir/check.vim27
-rw-r--r--src/nvim/testdir/runtest.vim13
-rw-r--r--src/nvim/testdir/script_util.vim69
-rw-r--r--src/nvim/testdir/test_alot.vim4
-rw-r--r--src/nvim/testdir/test_alot_latin.vim3
-rw-r--r--src/nvim/testdir/test_alot_utf8.vim1
-rw-r--r--src/nvim/testdir/test_arglist.vim18
-rw-r--r--src/nvim/testdir/test_assert.vim31
-rw-r--r--src/nvim/testdir/test_autochdir.vim8
-rw-r--r--src/nvim/testdir/test_autocmd.vim86
-rw-r--r--src/nvim/testdir/test_backspace_opt.vim20
-rw-r--r--src/nvim/testdir/test_breakindent.vim618
-rw-r--r--src/nvim/testdir/test_buffer.vim33
-rw-r--r--src/nvim/testdir/test_bufline.vim11
-rw-r--r--src/nvim/testdir/test_cmdline.vim16
-rw-r--r--src/nvim/testdir/test_command_count.vim4
-rw-r--r--src/nvim/testdir/test_compiler.vim22
-rw-r--r--src/nvim/testdir/test_conceal.vim282
-rw-r--r--src/nvim/testdir/test_cursor_func.vim5
-rw-r--r--src/nvim/testdir/test_debugger.vim819
-rw-r--r--src/nvim/testdir/test_diffmode.vim84
-rw-r--r--src/nvim/testdir/test_digraph.vim2
-rw-r--r--src/nvim/testdir/test_eval_stuff.vim11
-rw-r--r--src/nvim/testdir/test_excmd.vim8
-rw-r--r--src/nvim/testdir/test_exit.vim29
-rw-r--r--src/nvim/testdir/test_expr.vim15
-rw-r--r--src/nvim/testdir/test_filetype.vim14
-rw-r--r--src/nvim/testdir/test_fold.vim29
-rw-r--r--src/nvim/testdir/test_functions.vim87
-rw-r--r--src/nvim/testdir/test_gn.vim4
-rw-r--r--src/nvim/testdir/test_highlight.vim100
-rw-r--r--src/nvim/testdir/test_ins_complete.vim28
-rw-r--r--src/nvim/testdir/test_listchars.vim29
-rw-r--r--src/nvim/testdir/test_listdict.vim63
-rw-r--r--src/nvim/testdir/test_listlbr.vim6
-rw-r--r--src/nvim/testdir/test_mapping.vim24
-rw-r--r--src/nvim/testdir/test_matchadd_conceal.vim91
-rw-r--r--src/nvim/testdir/test_menu.vim6
-rw-r--r--src/nvim/testdir/test_messages.vim5
-rw-r--r--src/nvim/testdir/test_mksession.vim129
-rw-r--r--src/nvim/testdir/test_options.vim107
-rw-r--r--src/nvim/testdir/test_popup.vim2
-rw-r--r--src/nvim/testdir/test_quickfix.vim21
-rw-r--r--src/nvim/testdir/test_quotestar.vim2
-rw-r--r--src/nvim/testdir/test_regexp_latin.vim16
-rw-r--r--src/nvim/testdir/test_registers.vim2
-rw-r--r--src/nvim/testdir/test_rename.vim119
-rw-r--r--src/nvim/testdir/test_search.vim602
-rw-r--r--src/nvim/testdir/test_shift.vim117
-rw-r--r--src/nvim/testdir/test_signs.vim174
-rw-r--r--src/nvim/testdir/test_startup.vim250
-rw-r--r--src/nvim/testdir/test_statusline.vim38
-rw-r--r--src/nvim/testdir/test_substitute.vim9
-rw-r--r--src/nvim/testdir/test_syntax.vim49
-rw-r--r--src/nvim/testdir/test_system.vim4
-rw-r--r--src/nvim/testdir/test_tab.vim45
-rw-r--r--src/nvim/testdir/test_tabpage.vim3
-rw-r--r--src/nvim/testdir/test_tagfunc.vim38
-rw-r--r--src/nvim/testdir/test_tagjump.vim289
-rw-r--r--src/nvim/testdir/test_taglist.vim96
-rw-r--r--src/nvim/testdir/test_textformat.vim8
-rw-r--r--src/nvim/testdir/test_timers.vim4
-rw-r--r--src/nvim/testdir/test_undo.vim6
-rw-r--r--src/nvim/testdir/test_user_func.vim53
-rw-r--r--src/nvim/testdir/test_utf8.vim2
-rw-r--r--src/nvim/testdir/test_vartabs.vim381
-rw-r--r--src/nvim/testdir/test_version.vim14
-rw-r--r--src/nvim/testdir/test_visual.vim2
-rw-r--r--src/nvim/testdir/test_window_cmd.vim70
-rw-r--r--src/nvim/testdir/test_writefile.vim120
-rw-r--r--src/nvim/tui/input.c6
-rw-r--r--src/nvim/tui/tui.c22
-rw-r--r--src/nvim/ui.c1
-rw-r--r--src/nvim/ui_compositor.c34
-rw-r--r--src/nvim/undo.c128
-rw-r--r--src/nvim/undo_defs.h7
-rw-r--r--src/nvim/version.c6
-rw-r--r--src/nvim/vim.h2
-rw-r--r--src/nvim/viml/parser/expressions.c6
-rw-r--r--src/nvim/window.c357
189 files changed, 11168 insertions, 3478 deletions
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index db77931c16..2c9d655a15 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -284,7 +284,7 @@ foreach(sfile ${NVIM_SOURCES}
endif()
add_custom_command(
OUTPUT "${gf_c_h}" "${gf_h_h}"
- COMMAND ${CMAKE_C_COMPILER} ${sfile} ${PREPROC_OUTPUT} ${gen_cflags} ${C_FLAGS_ARRAY}
+ COMMAND ${CMAKE_C_COMPILER} ${sfile} ${PREPROC_OUTPUT} ${gen_cflags}
COMMAND "${LUA_PRG}" "${HEADER_GENERATOR}" "${sfile}" "${gf_c_h}" "${gf_h_h}" "${gf_i}"
DEPENDS ${depends})
list(APPEND NVIM_GENERATED_FOR_SOURCES "${gf_c_h}")
@@ -513,8 +513,44 @@ if(WIN32)
tidy.exe
win32yank.exe
winpty-agent.exe
+ winpty.dll
xxd.exe
+ # Dependencies for neovim-qt
+ bearer/qgenericbearer.dll
+ iconengines/qsvgicon.dll
+ imageformats/qgif.dll
+ imageformats/qicns.dll
+ imageformats/qico.dll
+ imageformats/qjpeg.dll
+ imageformats/qsvg.dll
+ imageformats/qtga.dll
+ imageformats/qtiff.dll
+ imageformats/qwbmp.dll
+ imageformats/qwebp.dll
+ platforms/qwindows.dll
+ styles/qwindowsvistastyle.dll
+ translations/qt_ar.qm
+ translations/qt_bg.qm
+ translations/qt_ca.qm
+ translations/qt_cs.qm
+ translations/qt_da.qm
+ translations/qt_de.qm
+ translations/qt_en.qm
+ translations/qt_es.qm
+ translations/qt_fi.qm
+ translations/qt_fr.qm
+ translations/qt_gd.qm
+ translations/qt_he.qm
+ translations/qt_hu.qm
+ translations/qt_it.qm
+ translations/qt_ja.qm
+ translations/qt_ko.qm
+ translations/qt_lv.qm
+ translations/qt_pl.qm
+ translations/qt_ru.qm
+ translations/qt_sk.qm
+ translations/qt_uk.qm
D3Dcompiler_47.dll
libEGL.dll
libgcc_s_dw2-1.dll
@@ -522,14 +558,13 @@ if(WIN32)
libstdc++-6.dll
libwinpthread-1.dll
nvim-qt.exe
+ opengl32sw.dll
Qt5Core.dll
Qt5Gui.dll
Qt5Network.dll
Qt5Svg.dll
Qt5Widgets.dll
- winpty.dll
- platforms/qwindows.dll
)
get_filename_component(DEP_FILE_DIR ${DEP_FILE} DIRECTORY)
set(EXTERNAL_BLOBS_SCRIPT "${EXTERNAL_BLOBS_SCRIPT}\n"
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 2890d89bc0..e79a7a2de2 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -111,6 +111,24 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err)
/// - byte count of previous contents
/// - deleted_codepoints (if `utf_sizes` is true)
/// - deleted_codeunits (if `utf_sizes` is true)
+/// - on_bytes: lua callback invoked on change.
+/// This callback receives more granular information about the
+/// change compared to on_lines.
+/// Return `true` to detach.
+/// Args:
+/// - the string "bytes"
+/// - buffer handle
+/// - b:changedtick
+/// - start row of the changed text (zero-indexed)
+/// - start column of the changed text
+/// - byte offset of the changed text (from the start of
+/// the buffer)
+/// - old end row of the changed text
+/// - old end column of the changed text
+/// - old end byte length of the changed text
+/// - new end row of the changed text
+/// - new end column of the changed text
+/// - new end byte length of the changed text
/// - on_changedtick: Lua callback invoked on changedtick
/// increment without text change. Args:
/// - the string "changedtick"
@@ -119,6 +137,10 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err)
/// - on_detach: Lua callback invoked on detach. Args:
/// - the string "detach"
/// - buffer handle
+/// - on_reload: Lua callback invoked on reload. The entire buffer
+/// content should be considered changed. Args:
+/// - the string "detach"
+/// - buffer handle
/// - utf_sizes: include UTF-32 and UTF-16 size of the replaced
/// region, as args to `on_lines`.
/// - preview: also attach to command preview (i.e. 'inccommand')
@@ -141,50 +163,57 @@ Boolean nvim_buf_attach(uint64_t channel_id,
bool is_lua = (channel_id == LUA_INTERNAL_CALL);
BufUpdateCallbacks cb = BUF_UPDATE_CALLBACKS_INIT;
+ struct {
+ const char *name;
+ LuaRef *dest;
+ } cbs[] = {
+ { "on_lines", &cb.on_lines },
+ { "on_bytes", &cb.on_bytes },
+ { "on_changedtick", &cb.on_changedtick },
+ { "on_detach", &cb.on_detach },
+ { "on_reload", &cb.on_reload },
+ { NULL, NULL },
+ };
+
for (size_t i = 0; i < opts.size; i++) {
String k = opts.items[i].key;
Object *v = &opts.items[i].value;
- if (is_lua && strequal("on_lines", k.data)) {
- if (v->type != kObjectTypeLuaRef) {
- api_set_error(err, kErrorTypeValidation, "callback is not a function");
- goto error;
- }
- cb.on_lines = v->data.luaref;
- v->data.luaref = LUA_NOREF;
- } else if (is_lua && strequal("on_bytes", k.data)) {
- if (v->type != kObjectTypeLuaRef) {
- api_set_error(err, kErrorTypeValidation, "callback is not a function");
- goto error;
- }
- cb.on_bytes = v->data.luaref;
- v->data.luaref = LUA_NOREF;
- } else if (is_lua && strequal("on_changedtick", k.data)) {
- if (v->type != kObjectTypeLuaRef) {
- api_set_error(err, kErrorTypeValidation, "callback is not a function");
- goto error;
- }
- cb.on_changedtick = v->data.luaref;
- v->data.luaref = LUA_NOREF;
- } else if (is_lua && strequal("on_detach", k.data)) {
- if (v->type != kObjectTypeLuaRef) {
- api_set_error(err, kErrorTypeValidation, "callback is not a function");
- goto error;
- }
- cb.on_detach = v->data.luaref;
- v->data.luaref = LUA_NOREF;
- } else if (is_lua && strequal("utf_sizes", k.data)) {
- if (v->type != kObjectTypeBoolean) {
- api_set_error(err, kErrorTypeValidation, "utf_sizes must be boolean");
- goto error;
+ bool key_used = false;
+ if (is_lua) {
+ for (size_t j = 0; cbs[j].name; j++) {
+ if (strequal(cbs[j].name, k.data)) {
+ if (v->type != kObjectTypeLuaRef) {
+ api_set_error(err, kErrorTypeValidation,
+ "%s is not a function", cbs[j].name);
+ goto error;
+ }
+ *(cbs[j].dest) = v->data.luaref;
+ v->data.luaref = LUA_NOREF;
+ key_used = true;
+ break;
+ }
}
- cb.utf_sizes = v->data.boolean;
- } else if (is_lua && strequal("preview", k.data)) {
- if (v->type != kObjectTypeBoolean) {
- api_set_error(err, kErrorTypeValidation, "preview must be boolean");
- goto error;
+
+ if (key_used) {
+ continue;
+ } else if (strequal("utf_sizes", k.data)) {
+ if (v->type != kObjectTypeBoolean) {
+ api_set_error(err, kErrorTypeValidation, "utf_sizes must be boolean");
+ goto error;
+ }
+ cb.utf_sizes = v->data.boolean;
+ key_used = true;
+ } else if (strequal("preview", k.data)) {
+ if (v->type != kObjectTypeBoolean) {
+ api_set_error(err, kErrorTypeValidation, "preview must be boolean");
+ goto error;
+ }
+ cb.preview = v->data.boolean;
+ key_used = true;
}
- cb.preview = v->data.boolean;
- } else {
+ }
+
+ if (!key_used) {
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
goto error;
}
@@ -193,11 +222,7 @@ Boolean nvim_buf_attach(uint64_t channel_id,
return buf_updates_register(buf, channel_id, cb, send_buffer);
error:
- // TODO(bfredl): ASAN build should check that the ref table is empty?
- api_free_luaref(cb.on_lines);
- api_free_luaref(cb.on_bytes);
- api_free_luaref(cb.on_changedtick);
- api_free_luaref(cb.on_detach);
+ buffer_update_callbacks_free(cb);
return false;
}
@@ -722,7 +747,8 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer,
kExtmarkUndo);
- changed_lines((linenr_T)start_row, 0, (linenr_T)end_row, (long)extra, true);
+ changed_lines((linenr_T)start_row, 0, (linenr_T)end_row + 1,
+ (long)extra, true);
// adjust cursor like an extmark ( i e it was inside last_part_len)
if (curwin->w_cursor.lnum == end_row && curwin->w_cursor.col > end_col) {
@@ -1200,8 +1226,7 @@ static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict)
/// @param ns_id Namespace id from |nvim_create_namespace()|
/// @param id Extmark id
/// @param opts Optional parameters. Keys:
-/// - limit: Maximum number of marks to return
-/// - details Whether to include the details dict
+/// - details: Whether to include the details dict
/// @param[out] err Error details, if any
/// @return (row, col) tuple or empty list () if extmark id was absent
ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
@@ -1396,6 +1421,27 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id,
/// - hl_group : name of the highlight group used to highlight
/// this mark.
/// - virt_text : virtual text to link to this mark.
+/// - virt_text_pos : positioning of virtual text. Possible
+/// values:
+/// - "eol": right after eol character (default)
+/// - "overlay": display over the specified column, without
+/// shifting the underlying text.
+/// - virt_text_hide : hide the virtual text when the background
+/// text is selected or hidden due to
+/// horizontal scroll 'nowrap'
+/// - hl_mode : control how highlights are combined with the
+/// highlights of the text. Currently only affects
+/// virt_text highlights, but might affect `hl_group`
+/// in later versions.
+/// - "replace": only show the virt_text color. This is the
+/// default
+/// - "combine": combine with background text color
+/// - "blend": blend with background text color.
+/// - hl_eol : when true, for a multiline highlight covering the
+/// EOL of a line, continue the highlight for the rest
+/// of the screen line (just like for diff and
+/// cursorline highlight).
+///
/// - ephemeral : for use with |nvim_set_decoration_provider|
/// callbacks. The mark will only be used for the current
/// redraw cycle, and not be permantently stored in the
@@ -1407,6 +1453,8 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id,
/// the extmark end position (if it exists) will be shifted
/// in when new text is inserted (true for right, false
/// for left). Defaults to false.
+/// - priority: a priority value for the highlight group. For
+/// example treesitter highlighting uses a value of 100.
/// @param[out] err Error details, if any
/// @return Id of the created/updated extmark
Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id,
@@ -1443,10 +1491,10 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id,
bool ephemeral = false;
uint64_t id = 0;
- int line2 = -1, hl_id = 0;
- DecorPriority priority = DECOR_PRIORITY_BASE;
- colnr_T col2 = 0;
- VirtText virt_text = KV_INITIAL_VALUE;
+ int line2 = -1;
+ Decoration decor = DECORATION_INIT;
+ colnr_T col2 = -1;
+
bool right_gravity = true;
bool end_right_gravity = false;
bool end_gravity_set = false;
@@ -1493,12 +1541,12 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id,
switch (v->type) {
case kObjectTypeString:
hl_group = v->data.string;
- hl_id = syn_check_group(
+ decor.hl_id = syn_check_group(
(char_u *)(hl_group.data),
(int)hl_group.size);
break;
case kObjectTypeInteger:
- hl_id = (int)v->data.integer;
+ decor.hl_id = (int)v->data.integer;
break;
default:
api_set_error(err, kErrorTypeValidation,
@@ -1511,10 +1559,55 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id,
"virt_text is not an Array");
goto error;
}
- virt_text = parse_virt_text(v->data.array, err);
+ decor.virt_text = parse_virt_text(v->data.array, err);
if (ERROR_SET(err)) {
goto error;
}
+ } else if (strequal("virt_text_pos", k.data)) {
+ if (v->type != kObjectTypeString) {
+ api_set_error(err, kErrorTypeValidation,
+ "virt_text_pos is not a String");
+ goto error;
+ }
+ String str = v->data.string;
+ if (strequal("eol", str.data)) {
+ decor.virt_text_pos = kVTEndOfLine;
+ } else if (strequal("overlay", str.data)) {
+ decor.virt_text_pos = kVTOverlay;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "virt_text_pos: invalid value");
+ goto error;
+ }
+ } else if (strequal("virt_text_hide", k.data)) {
+ decor.virt_text_hide = api_object_to_bool(*v,
+ "virt_text_hide", false, err);
+ if (ERROR_SET(err)) {
+ goto error;
+ }
+ } else if (strequal("hl_eol", k.data)) {
+ decor.hl_eol = api_object_to_bool(*v, "hl_eol", false, err);
+ if (ERROR_SET(err)) {
+ goto error;
+ }
+ } else if (strequal("hl_mode", k.data)) {
+ if (v->type != kObjectTypeString) {
+ api_set_error(err, kErrorTypeValidation,
+ "hl_mode is not a String");
+ goto error;
+ }
+ String str = v->data.string;
+ if (strequal("replace", str.data)) {
+ decor.hl_mode = kHlModeReplace;
+ } else if (strequal("combine", str.data)) {
+ decor.hl_mode = kHlModeCombine;
+ } else if (strequal("blend", str.data)) {
+ decor.hl_mode = kHlModeBlend;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "virt_text_pos: invalid value");
+ goto error;
+ }
} else if (strequal("ephemeral", k.data)) {
ephemeral = api_object_to_bool(*v, "ephemeral", false, err);
if (ERROR_SET(err)) {
@@ -1532,7 +1625,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id,
"priority is not a valid value");
goto error;
}
- priority = (DecorPriority)v->data.integer;
+ decor.priority = (DecorPriority)v->data.integer;
} else if (strequal("right_gravity", k.data)) {
if (v->type != kObjectTypeBoolean) {
api_set_error(err, kErrorTypeValidation,
@@ -1556,7 +1649,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id,
// Only error out if they try to set end_right_gravity without
// setting end_col or end_line
- if (line2 == -1 && col2 == 0 && end_gravity_set) {
+ if (line2 == -1 && col2 == -1 && end_gravity_set) {
api_set_error(err, kErrorTypeValidation,
"cannot set end_right_gravity "
"without setting end_line or end_col");
@@ -1580,40 +1673,39 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id,
col2 = 0;
}
+ Decoration *d = NULL;
+
+ if (ephemeral) {
+ d = &decor;
+ } else if (kv_size(decor.virt_text)
+ || 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) {
- int attr_id = hl_id > 0 ? syn_id2attr(hl_id) : 0;
- VirtText *vt_allocated = NULL;
- if (kv_size(virt_text)) {
- vt_allocated = xmalloc(sizeof *vt_allocated);
- *vt_allocated = virt_text;
- }
- decor_add_ephemeral(attr_id, (int)line, (colnr_T)col,
- (int)line2, (colnr_T)col2, priority, vt_allocated);
+ decor_add_ephemeral((int)line, (int)col, line2, col2, &decor);
} else {
if (ephemeral) {
api_set_error(err, kErrorTypeException, "not yet implemented");
goto error;
}
- Decoration *decor = NULL;
- if (kv_size(virt_text)) {
- decor = xcalloc(1, sizeof(*decor));
- decor->hl_id = hl_id;
- decor->virt_text = virt_text;
- } else if (hl_id) {
- decor = decor_hl(hl_id);
- decor->priority = priority;
- }
id = extmark_set(buf, (uint64_t)ns_id, id, (int)line, (colnr_T)col,
- line2, col2, decor, right_gravity,
+ line2, col2, d, right_gravity,
end_right_gravity, kExtmarkNoUndo);
}
return (Integer)id;
error:
- clear_virttext(&virt_text);
+ clear_virttext(&decor.virt_text);
return 0;
}
@@ -1674,7 +1766,7 @@ Boolean nvim_buf_del_extmark(Buffer buffer,
/// @param[out] err Error details, if any
/// @return The ns_id that was used
Integer nvim_buf_add_highlight(Buffer buffer,
- Integer src_id,
+ Integer ns_id,
String hl_group,
Integer line,
Integer col_start,
@@ -1699,18 +1791,18 @@ Integer nvim_buf_add_highlight(Buffer buffer,
col_end = MAXCOL;
}
- uint64_t ns_id = src2ns(&src_id);
+ uint64_t ns = src2ns(&ns_id);
if (!(line < buf->b_ml.ml_line_count)) {
// safety check, we can't add marks outside the range
- return src_id;
+ return ns_id;
}
int hl_id = 0;
if (hl_group.size > 0) {
hl_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size);
} else {
- return src_id;
+ return ns_id;
}
int end_line = (int)line;
@@ -1719,11 +1811,11 @@ Integer nvim_buf_add_highlight(Buffer buffer,
end_line++;
}
- extmark_set(buf, ns_id, 0,
+ extmark_set(buf, ns, 0,
(int)line, (colnr_T)col_start,
end_line, (colnr_T)col_end,
decor_hl(hl_id), true, false, kExtmarkNoUndo);
- return src_id;
+ return ns_id;
}
/// Clears namespaced objects (highlights, extmarks, virtual text) from
@@ -1919,7 +2011,6 @@ static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra)
curwin->w_cursor.lnum += extra;
check_cursor_col();
} else if (extra < 0) {
- curwin->w_cursor.lnum = lo;
check_cursor();
} else {
check_cursor_col();
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 7cee569989..24ba6110c4 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1645,6 +1645,20 @@ bool api_object_to_bool(Object obj, const char *what,
}
}
+int object_to_hl_id(Object obj, const char *what, Error *err)
+{
+ if (obj.type == kObjectTypeString) {
+ String str = obj.data.string;
+ return str.size ? syn_check_group((char_u *)str.data, (int)str.size) : 0;
+ } else if (obj.type == kObjectTypeInteger) {
+ return (int)obj.data.integer;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "%s is not a valid highlight", what);
+ return 0;
+ }
+}
+
HlMessage parse_hl_msg(Array chunks, Error *err)
{
HlMessage hl_msg = KV_INITIAL_VALUE;
@@ -1694,29 +1708,362 @@ const char *describe_ns(NS ns_id)
return "(UNKNOWN PLUGIN)";
}
-DecorProvider *get_provider(NS ns_id, bool force)
+static bool parse_float_anchor(String anchor, FloatAnchor *out)
{
- ssize_t i;
- for (i = 0; i < (ssize_t)kv_size(decor_providers); i++) {
- DecorProvider *item = &kv_A(decor_providers, i);
- if (item->ns_id == ns_id) {
- return item;
- } else if (item->ns_id > ns_id) {
- break;
+ if (anchor.size == 0) {
+ *out = (FloatAnchor)0;
+ }
+ char *str = anchor.data;
+ if (striequal(str, "NW")) {
+ *out = 0; // NW is the default
+ } else if (striequal(str, "NE")) {
+ *out = kFloatAnchorEast;
+ } else if (striequal(str, "SW")) {
+ *out = kFloatAnchorSouth;
+ } else if (striequal(str, "SE")) {
+ *out = kFloatAnchorSouth | kFloatAnchorEast;
+ } else {
+ return false;
+ }
+ return true;
+}
+
+static bool parse_float_relative(String relative, FloatRelative *out)
+{
+ char *str = relative.data;
+ if (striequal(str, "editor")) {
+ *out = kFloatRelativeEditor;
+ } else if (striequal(str, "win")) {
+ *out = kFloatRelativeWindow;
+ } else if (striequal(str, "cursor")) {
+ *out = kFloatRelativeCursor;
+ } else {
+ return false;
+ }
+ return true;
+}
+
+static bool parse_float_bufpos(Array bufpos, lpos_T *out)
+{
+ if (bufpos.size != 2
+ || bufpos.items[0].type != kObjectTypeInteger
+ || bufpos.items[1].type != kObjectTypeInteger) {
+ return false;
+ }
+ out->lnum = bufpos.items[0].data.integer;
+ out->col = (colnr_T)bufpos.items[1].data.integer;
+ return true;
+}
+
+static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
+{
+ struct {
+ const char *name;
+ schar_T chars[8];
+ bool shadow_color;
+ } defaults[] = {
+ { "double", { "╔", "═", "╗", "║", "╝", "═", "╚", "║" }, false },
+ { "single", { "┌", "─", "┐", "│", "┘", "─", "└", "│" }, false },
+ { "shadow", { "", "", " ", " ", " ", " ", " ", "" }, true },
+ { "solid", { " ", " ", " ", " ", " ", " ", " ", " " }, false },
+ { NULL, { { NUL } } , false },
+ };
+
+ schar_T *chars = fconfig->border_chars;
+ int *hl_ids = fconfig->border_hl_ids;
+
+ fconfig->border = true;
+
+ if (style.type == kObjectTypeArray) {
+ Array arr = style.data.array;
+ size_t size = arr.size;
+ if (!size || size > 8 || (size & (size-1))) {
+ api_set_error(err, kErrorTypeValidation,
+ "invalid number of border chars");
+ return;
}
+ for (size_t i = 0; i < size; i++) {
+ Object iytem = arr.items[i];
+ String string = NULL_STRING;
+ int hl_id = 0;
+ if (iytem.type == kObjectTypeArray) {
+ Array iarr = iytem.data.array;
+ if (!iarr.size || iarr.size > 2) {
+ api_set_error(err, kErrorTypeValidation, "invalid border char");
+ return;
+ }
+ if (iarr.items[0].type != kObjectTypeString) {
+ api_set_error(err, kErrorTypeValidation, "invalid border char");
+ return;
+ }
+ string = iarr.items[0].data.string;
+ if (iarr.size == 2) {
+ hl_id = object_to_hl_id(iarr.items[1], "border char highlight", err);
+ if (ERROR_SET(err)) {
+ return;
+ }
+ }
+
+ } else if (iytem.type == kObjectTypeString) {
+ string = iytem.data.string;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "invalid border char");
+ return;
+ }
+ if (string.size
+ && mb_string2cells_len((char_u *)string.data, string.size) > 1) {
+ api_set_error(err, kErrorTypeValidation,
+ "border chars must be one cell");
+ return;
+ }
+ size_t len = MIN(string.size, sizeof(*chars)-1);
+ if (len) {
+ memcpy(chars[i], string.data, len);
+ }
+ chars[i][len] = NUL;
+ hl_ids[i] = hl_id;
+ }
+ while (size < 8) {
+ memcpy(chars+size, chars, sizeof(*chars) * size);
+ memcpy(hl_ids+size, hl_ids, sizeof(*hl_ids) * size);
+ size <<= 1;
+ }
+ if ((chars[7][0] && chars[1][0] && !chars[0][0])
+ || (chars[1][0] && chars[3][0] && !chars[2][0])
+ || (chars[3][0] && chars[5][0] && !chars[4][0])
+ || (chars[5][0] && chars[7][0] && !chars[6][0])) {
+ api_set_error(err, kErrorTypeValidation,
+ "corner between used edges must be specified");
+ }
+ } else if (style.type == kObjectTypeString) {
+ String str = style.data.string;
+ if (str.size == 0 || strequal(str.data, "none")) {
+ fconfig->border = false;
+ return;
+ }
+ for (size_t i = 0; defaults[i].name; i++) {
+ if (strequal(str.data, defaults[i].name)) {
+ memcpy(chars, defaults[i].chars, sizeof(defaults[i].chars));
+ memset(hl_ids, 0, 8 * sizeof(*hl_ids));
+ if (defaults[i].shadow_color) {
+ int hl_blend = SYN_GROUP_STATIC("FloatShadow");
+ int hl_through = SYN_GROUP_STATIC("FloatShadowThrough");
+ hl_ids[2] = hl_through;
+ hl_ids[3] = hl_blend;
+ hl_ids[4] = hl_blend;
+ hl_ids[5] = hl_blend;
+ hl_ids[6] = hl_through;
+ }
+ return;
+ }
+ }
+ api_set_error(err, kErrorTypeValidation,
+ "invalid border style \"%s\"", str.data);
}
+}
- if (!force) {
- return NULL;
+bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf,
+ Error *err)
+{
+ // TODO(bfredl): use a get/has_key interface instead and get rid of extra
+ // flags
+ bool has_row = false, has_col = false, has_relative = false;
+ bool has_external = false, has_window = false;
+ bool has_width = false, has_height = false;
+ bool has_bufpos = false;
+
+ for (size_t i = 0; i < config.size; i++) {
+ char *key = config.items[i].key.data;
+ Object val = config.items[i].value;
+ if (!strcmp(key, "row")) {
+ has_row = true;
+ if (val.type == kObjectTypeInteger) {
+ fconfig->row = (double)val.data.integer;
+ } else if (val.type == kObjectTypeFloat) {
+ fconfig->row = val.data.floating;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "'row' key must be Integer or Float");
+ return false;
+ }
+ } else if (!strcmp(key, "col")) {
+ has_col = true;
+ if (val.type == kObjectTypeInteger) {
+ fconfig->col = (double)val.data.integer;
+ } else if (val.type == kObjectTypeFloat) {
+ fconfig->col = val.data.floating;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "'col' key must be Integer or Float");
+ return false;
+ }
+ } else if (strequal(key, "width")) {
+ has_width = true;
+ if (val.type == kObjectTypeInteger && val.data.integer > 0) {
+ fconfig->width = (int)val.data.integer;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "'width' key must be a positive Integer");
+ return false;
+ }
+ } else if (strequal(key, "height")) {
+ has_height = true;
+ if (val.type == kObjectTypeInteger && val.data.integer > 0) {
+ fconfig->height= (int)val.data.integer;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "'height' key must be a positive Integer");
+ return false;
+ }
+ } else if (!strcmp(key, "anchor")) {
+ if (val.type != kObjectTypeString) {
+ api_set_error(err, kErrorTypeValidation,
+ "'anchor' key must be String");
+ return false;
+ }
+ if (!parse_float_anchor(val.data.string, &fconfig->anchor)) {
+ api_set_error(err, kErrorTypeValidation,
+ "Invalid value of 'anchor' key");
+ return false;
+ }
+ } else if (!strcmp(key, "relative")) {
+ if (val.type != kObjectTypeString) {
+ api_set_error(err, kErrorTypeValidation,
+ "'relative' key must be String");
+ return false;
+ }
+ // ignore empty string, to match nvim_win_get_config
+ if (val.data.string.size > 0) {
+ has_relative = true;
+ if (!parse_float_relative(val.data.string, &fconfig->relative)) {
+ api_set_error(err, kErrorTypeValidation,
+ "Invalid value of 'relative' key");
+ return false;
+ }
+ }
+ } else if (!strcmp(key, "win")) {
+ has_window = true;
+ if (val.type != kObjectTypeInteger
+ && val.type != kObjectTypeWindow) {
+ api_set_error(err, kErrorTypeValidation,
+ "'win' key must be Integer or Window");
+ return false;
+ }
+ fconfig->window = (Window)val.data.integer;
+ } else if (!strcmp(key, "bufpos")) {
+ if (val.type != kObjectTypeArray) {
+ api_set_error(err, kErrorTypeValidation,
+ "'bufpos' key must be Array");
+ return false;
+ }
+ if (!parse_float_bufpos(val.data.array, &fconfig->bufpos)) {
+ api_set_error(err, kErrorTypeValidation,
+ "Invalid value of 'bufpos' key");
+ return false;
+ }
+ has_bufpos = true;
+ } else if (!strcmp(key, "external")) {
+ if (val.type == kObjectTypeInteger) {
+ fconfig->external = val.data.integer;
+ } else if (val.type == kObjectTypeBoolean) {
+ fconfig->external = val.data.boolean;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "'external' key must be Boolean");
+ return false;
+ }
+ has_external = fconfig->external;
+ } else if (!strcmp(key, "focusable")) {
+ if (val.type == kObjectTypeInteger) {
+ fconfig->focusable = val.data.integer;
+ } else if (val.type == kObjectTypeBoolean) {
+ fconfig->focusable = val.data.boolean;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "'focusable' key must be Boolean");
+ return false;
+ }
+ } else if (!strcmp(key, "border")) {
+ parse_border_style(val, fconfig, err);
+ if (ERROR_SET(err)) {
+ return false;
+ }
+ } else if (!strcmp(key, "style")) {
+ if (val.type != kObjectTypeString) {
+ api_set_error(err, kErrorTypeValidation,
+ "'style' key must be String");
+ return false;
+ }
+ if (val.data.string.data[0] == NUL) {
+ fconfig->style = kWinStyleUnused;
+ } else if (striequal(val.data.string.data, "minimal")) {
+ fconfig->style = kWinStyleMinimal;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "Invalid value of 'style' key");
+ }
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "Invalid key '%s'", key);
+ return false;
+ }
+ }
+
+ if (has_window && !(has_relative
+ && fconfig->relative == kFloatRelativeWindow)) {
+ api_set_error(err, kErrorTypeValidation,
+ "'win' key is only valid with relative='win'");
+ return false;
+ }
+
+ if ((has_relative && fconfig->relative == kFloatRelativeWindow)
+ && (!has_window || fconfig->window == 0)) {
+ fconfig->window = curwin->handle;
}
- for (ssize_t j = (ssize_t)kv_size(decor_providers)-1; j >= i; j++) {
- // allocates if needed:
- (void)kv_a(decor_providers, (size_t)j+1);
- kv_A(decor_providers, (size_t)j+1) = kv_A(decor_providers, j);
+ if (has_window && !has_bufpos) {
+ fconfig->bufpos.lnum = -1;
}
- DecorProvider *item = &kv_a(decor_providers, (size_t)i);
- *item = DECORATION_PROVIDER_INIT(ns_id);
- return item;
+ if (has_bufpos) {
+ if (!has_row) {
+ fconfig->row = (fconfig->anchor & kFloatAnchorSouth) ? 0 : 1;
+ has_row = true;
+ }
+ if (!has_col) {
+ fconfig->col = 0;
+ has_col = true;
+ }
+ }
+
+ if (has_relative && has_external) {
+ api_set_error(err, kErrorTypeValidation,
+ "Only one of 'relative' and 'external' must be used");
+ return false;
+ } else if (!reconf && !has_relative && !has_external) {
+ api_set_error(err, kErrorTypeValidation,
+ "One of 'relative' and 'external' must be used");
+ return false;
+ } else if (has_relative) {
+ fconfig->external = false;
+ }
+
+ if (!reconf && !(has_height && has_width)) {
+ api_set_error(err, kErrorTypeValidation,
+ "Must specify 'width' and 'height'");
+ return false;
+ }
+
+ if (fconfig->external && !ui_has(kUIMultigrid)) {
+ api_set_error(err, kErrorTypeValidation,
+ "UI doesn't support external windows");
+ return false;
+ }
+
+ if (has_relative != has_row || has_row != has_col) {
+ api_set_error(err, kErrorTypeValidation,
+ "'relative' requires 'row'/'col' or 'bufpos'");
+ return false;
+ }
+ return true;
}
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 09895a2119..b5e53beabe 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -39,6 +39,7 @@
#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
#include "nvim/fileio.h"
+#include "nvim/move.h"
#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/state.h"
@@ -241,8 +242,7 @@ void nvim_set_hl(Integer ns_id, String name, Dictionary val, Error *err)
///
/// @param ns_id the namespace to activate
/// @param[out] err Error details, if any
-void nvim_set_hl_ns(Integer ns_id, Error *err)
- FUNC_API_SINCE(7)
+void nvim__set_hl_ns(Integer ns_id, Error *err)
FUNC_API_FAST
{
if (ns_id >= 0) {
@@ -545,6 +545,26 @@ Object nvim_exec_lua(String code, Array args, Error *err)
return nlua_exec(code, args, err);
}
+/// Notify the user with a message
+///
+/// Relays the call to vim.notify . By default forwards your message in the
+/// echo area but can be overriden to trigger desktop notifications.
+///
+/// @param msg Message to display to the user
+/// @param log_level The log level
+/// @param opts Reserved for future use.
+/// @param[out] err Error details, if any
+Object nvim_notify(String msg, Integer log_level, Dictionary opts, Error *err)
+ FUNC_API_SINCE(7)
+{
+ FIXED_TEMP_ARRAY(args, 3);
+ args.items[0] = STRING_OBJ(msg);
+ args.items[1] = INTEGER_OBJ(log_level);
+ args.items[2] = DICTIONARY_OBJ(opts);
+
+ return nlua_exec(STATIC_CSTR_AS_STRING("return vim.notify(...)"), args, err);
+}
+
/// Calls a VimL function.
///
/// @param fn Function name
@@ -1226,6 +1246,100 @@ fail:
return 0;
}
+/// Open a terminal instance in a buffer
+///
+/// By default (and currently the only option) the terminal will not be
+/// connected to an external process. Instead, input send on the channel
+/// will be echoed directly by the terminal. This is useful to disply
+/// ANSI terminal sequences returned as part of a rpc message, or similar.
+///
+/// Note: to directly initiate the terminal using the right size, display the
+/// buffer in a configured window before calling this. For instance, for a
+/// floating display, first create an empty buffer using |nvim_create_buf()|,
+/// then display it using |nvim_open_win()|, and then call this function.
+/// Then |nvim_chan_send()| cal be called immediately to process sequences
+/// in a virtual terminal having the intended size.
+///
+/// @param buffer the buffer to use (expected to be empty)
+/// @param opts Optional parameters. Reserved for future use.
+/// @param[out] err Error details, if any
+Integer nvim_open_term(Buffer buffer, Dictionary opts, Error *err)
+ FUNC_API_SINCE(7)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return 0;
+ }
+
+ if (opts.size > 0) {
+ api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ return 0;
+ }
+
+ TerminalOptions topts;
+ Channel *chan = channel_alloc(kChannelStreamInternal);
+ topts.data = chan;
+ // NB: overriden in terminal_check_size if a window is already
+ // displaying the buffer
+ topts.width = (uint16_t)MAX(curwin->w_width_inner - win_col_off(curwin), 0);
+ topts.height = (uint16_t)curwin->w_height_inner;
+ topts.write_cb = term_write;
+ topts.resize_cb = term_resize;
+ topts.close_cb = term_close;
+ Terminal *term = terminal_open(buf, topts);
+ terminal_check_size(term);
+ chan->term = term;
+ channel_incref(chan);
+ return (Integer)chan->id;
+}
+
+static void term_write(char *buf, size_t size, void *data)
+{
+ // TODO(bfredl): lua callback
+}
+
+static void term_resize(uint16_t width, uint16_t height, void *data)
+{
+ // TODO(bfredl): lua callback
+}
+
+static void term_close(void *data)
+{
+ Channel *chan = data;
+ terminal_destroy(chan->term);
+ chan->term = NULL;
+ channel_decref(chan);
+}
+
+
+/// 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.
+/// See |channel-bytes| for more information.
+///
+/// This function writes raw data, not RPC messages. If the channel
+/// was created with `rpc=true` then the channel expects RPC
+/// messages, use |vim.rpcnotify()| and |vim.rpcrequest()| instead.
+///
+/// @param chan id of the channel
+/// @param data data to write. 8-bit clean: can contain NUL bytes.
+/// @param[out] err Error details, if any
+void nvim_chan_send(Integer chan, String data, Error *err)
+ FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY FUNC_API_LUA_ONLY
+{
+ const char *error = NULL;
+ if (!data.size) {
+ return;
+ }
+
+ channel_send((uint64_t)chan, data.data, data.size,
+ false, &error);
+ if (error) {
+ api_set_error(err, kErrorTypeValidation, "%s", error);
+ }
+}
+
/// Open a new window.
///
/// Currently this is used to open floating and external windows.
@@ -1303,6 +1417,29 @@ fail:
/// end-of-buffer region is hidden by setting `eob` flag of
/// 'fillchars' to a space char, and clearing the
/// |EndOfBuffer| region in 'winhighlight'.
+/// - `border`: style of (optional) window border. This can either be a string
+/// or an array. the string values are:
+/// - "none" No border. This is the default
+/// - "single" a single line box
+/// - "double" a double line box
+/// - "shadow" a drop shadow effect by blending with the background.
+/// If it is an array it should be an array of eight items or any divisor of
+/// eight. The array will specifify the eight chars building up the border
+/// in a clockwise fashion starting with the top-left corner. As, an
+/// example, the double box style could be specified as:
+/// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ]
+/// if the number of chars are less than eight, they will be repeated. Thus
+/// an ASCII border could be specified as:
+/// [ "/", "-", "\\", "|" ]
+/// or all chars the same as:
+/// [ "x" ]
+/// An empty string can be used to turn off a specific border, for instance:
+/// [ "", "", "", ">", "", "", "", "<" ]
+/// will only make vertical borders but not horizontal ones.
+/// By default `FloatBorder` highlight is used which links to `VertSplit`
+/// when not defined. It could also be specified by character:
+/// [ {"+", "MyCorner"}, {"x", "MyBorder"} ]
+///
/// @param[out] err Error details, if any
///
/// @return Window handle, or 0 on error
@@ -1526,8 +1663,8 @@ theend:
/// - "c" |charwise| mode
/// - "l" |linewise| mode
/// - "" guess by contents, see |setreg()|
-/// @param after Insert after cursor (like |p|), or before (like |P|).
-/// @param follow Place cursor at end of inserted text.
+/// @param after If true insert after cursor (like |p|), or before (like |P|).
+/// @param follow If true place cursor at end of inserted text.
/// @param[out] err Error details, if any
void nvim_put(ArrayOf(String) lines, String type, Boolean after,
Boolean follow, Error *err)
@@ -2576,6 +2713,7 @@ Dictionary nvim__stats(void)
Dictionary rv = ARRAY_DICT_INIT;
PUT(rv, "fsync", INTEGER_OBJ(g_stats.fsync));
PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw));
+ PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_refcount));
return rv;
}
@@ -2718,8 +2856,8 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err)
g = &pum_grid;
} else if (grid > 1) {
win_T *wp = get_win_by_grid_handle((handle_T)grid);
- if (wp != NULL && wp->w_grid.chars != NULL) {
- g = &wp->w_grid;
+ if (wp != NULL && wp->w_grid_alloc.chars != NULL) {
+ g = &wp->w_grid_alloc;
} else {
api_set_error(err, kErrorTypeValidation,
"No grid with the given handle");
@@ -2748,19 +2886,6 @@ void nvim__screenshot(String path)
ui_call_screenshot(path);
}
-static void clear_provider(DecorProvider *p)
-{
- if (p == NULL) {
- return;
- }
- NLUA_CLEAR_REF(p->redraw_start);
- NLUA_CLEAR_REF(p->redraw_buf);
- NLUA_CLEAR_REF(p->redraw_win);
- NLUA_CLEAR_REF(p->redraw_line);
- NLUA_CLEAR_REF(p->redraw_end);
- p->active = false;
-}
-
/// Set or change decoration provider for a namespace
///
/// This is a very general purpose interface for having lua callbacks
@@ -2805,8 +2930,8 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts,
Error *err)
FUNC_API_SINCE(7) FUNC_API_LUA_ONLY
{
- DecorProvider *p = get_provider((NS)ns_id, true);
- clear_provider(p);
+ DecorProvider *p = get_decor_provider((NS)ns_id, true);
+ decor_provider_clear(p);
// regardless of what happens, it seems good idea to redraw
redraw_all_later(NOT_VALID); // TODO(bfredl): too soon?
@@ -2828,7 +2953,7 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts,
String k = opts.items[i].key;
Object *v = &opts.items[i].value;
size_t j;
- for (j = 0; cbs[j].name; j++) {
+ for (j = 0; cbs[j].name && cbs[j].dest; j++) {
if (strequal(cbs[j].name, k.data)) {
if (v->type != kObjectTypeLuaRef) {
api_set_error(err, kErrorTypeValidation,
@@ -2849,5 +2974,5 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts,
p->active = true;
return;
error:
- clear_provider(p);
+ decor_provider_clear(p);
}
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index f4af1632ec..89fa2f86fb 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -492,6 +492,35 @@ Dictionary nvim_win_get_config(Window window, Error *err)
return rv;
}
+/// Closes the window and hide the buffer it contains (like |:hide| with a
+/// |window-ID|).
+///
+/// Like |:hide| the buffer becomes hidden unless another window is editing it,
+/// or 'bufhidden' is `unload`, `delete` or `wipe` as opposed to |:close| or
+/// |nvim_win_close|, which will close the buffer.
+///
+/// @param window Window handle, or 0 for current window
+/// @param[out] err Error details, if any
+void nvim_win_hide(Window window, Error *err)
+ FUNC_API_SINCE(7)
+ FUNC_API_CHECK_TEXTLOCK
+{
+ win_T *win = find_window_by_handle(window, err);
+ if (!win) {
+ return;
+ }
+
+ tabpage_T *tabpage = win_find_tabpage(win);
+ TryState tstate;
+ try_enter(&tstate);
+ if (tabpage == curtab) {
+ win_close(win, false);
+ } else {
+ win_close_othertab(win, false, tabpage);
+ }
+ vim_ignored = try_leave(&tstate, err);
+}
+
/// Closes the window (like |:close| with a |window-ID|).
///
/// @param window Window handle, or 0 for current window
diff --git a/src/nvim/ascii.h b/src/nvim/ascii.h
index f41068ea70..7e4dee3d34 100644
--- a/src/nvim/ascii.h
+++ b/src/nvim/ascii.h
@@ -31,7 +31,9 @@
#define CSI 0x9b // Control Sequence Introducer
#define CSI_STR "\233"
#define DCS 0x90 // Device Control String
+#define DCS_STR "\033P"
#define STERM 0x9c // String Terminator
+#define STERM_STR "\033\\"
#define POUND 0xA3
diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c
index 42224d0a4f..145f6f5601 100644
--- a/src/nvim/autocmd.c
+++ b/src/nvim/autocmd.c
@@ -544,7 +544,7 @@ char_u *au_event_disable(char *what)
} else {
STRCAT(new_ei, what);
}
- set_string_option_direct((char_u *)"ei", -1, new_ei, OPT_FREE, SID_NONE);
+ set_string_option_direct("ei", -1, new_ei, OPT_FREE, SID_NONE);
xfree(new_ei);
return save_ei;
@@ -553,7 +553,7 @@ char_u *au_event_disable(char *what)
void au_event_restore(char_u *old_ei)
{
if (old_ei != NULL) {
- set_string_option_direct((char_u *)"ei", -1, old_ei, OPT_FREE, SID_NONE);
+ set_string_option_direct("ei", -1, old_ei, OPT_FREE, SID_NONE);
xfree(old_ei);
}
}
@@ -700,11 +700,15 @@ void do_autocmd(char_u *arg_in, int forceit)
last_event = (event_T)-1; // for listing the event name
last_group = AUGROUP_ERROR; // for listing the group name
if (*arg == '*' || *arg == NUL || *arg == '|') {
- for (event_T event = (event_T)0; event < (int)NUM_EVENTS;
- event = (event_T)(event + 1)) {
- if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group)
- == FAIL) {
- break;
+ if (!forceit && *cmd != NUL) {
+ EMSG(_(e_cannot_define_autocommands_for_all_events));
+ } else {
+ for (event_T event = (event_T)0; event < (int)NUM_EVENTS;
+ event = (event_T)(event + 1)) {
+ if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group)
+ == FAIL) {
+ break;
+ }
}
}
} else {
@@ -964,7 +968,7 @@ static int do_autocmd_event(event_T event,
// Implementation of ":doautocmd [group] event [fname]".
// Return OK for success, FAIL for failure;
int do_doautocmd(char_u *arg,
- int do_msg, // give message for no matching autocmds?
+ bool do_msg, // give message for no matching autocmds?
bool *did_something)
{
char_u *fname;
@@ -1013,11 +1017,12 @@ int do_doautocmd(char_u *arg,
// ":doautoall": execute autocommands for each loaded buffer.
void ex_doautoall(exarg_T *eap)
{
- int retval;
+ int retval = OK;
aco_save_T aco;
char_u *arg = eap->arg;
int call_do_modelines = check_nomodeline(&arg);
bufref_T bufref;
+ bool did_aucmd;
// This is a bit tricky: For some commands curwin->w_buffer needs to be
// equal to curbuf, but for some buffers there may not be a window.
@@ -1025,14 +1030,14 @@ void ex_doautoall(exarg_T *eap)
// gives problems when the autocommands make changes to the list of
// buffers or windows...
FOR_ALL_BUFFERS(buf) {
- if (buf->b_ml.ml_mfp == NULL) {
+ // Only do loaded buffers and skip the current buffer, it's done last.
+ if (buf->b_ml.ml_mfp == NULL || buf == curbuf) {
continue;
}
// Find a window for this buffer and save some values.
aucmd_prepbuf(&aco, buf);
set_bufref(&bufref, buf);
- bool did_aucmd;
// execute the autocommands for this buffer
retval = do_doautocmd(arg, false, &did_aucmd);
@@ -1048,10 +1053,19 @@ void ex_doautoall(exarg_T *eap)
// Stop if there is some error or buffer was deleted.
if (retval == FAIL || !bufref_valid(&bufref)) {
+ retval = FAIL;
break;
}
}
+ // Execute autocommands for the current buffer last.
+ if (retval == OK) {
+ (void)do_doautocmd(arg, false, &did_aucmd);
+ if (call_do_modelines && did_aucmd) {
+ do_modelines(0);
+ }
+ }
+
check_cursor(); // just in case lines got deleted
}
@@ -1178,10 +1192,10 @@ void aucmd_restbuf(aco_save_T *aco)
win_remove(curwin, NULL);
handle_unregister_window(curwin);
- if (curwin->w_grid.chars != NULL) {
- ui_comp_remove_grid(&curwin->w_grid);
- ui_call_win_hide(curwin->w_grid.handle);
- grid_free(&curwin->w_grid);
+ if (curwin->w_grid_alloc.chars != NULL) {
+ ui_comp_remove_grid(&curwin->w_grid_alloc);
+ ui_call_win_hide(curwin->w_grid_alloc.handle);
+ grid_free(&curwin->w_grid_alloc);
}
aucmd_win_used = false;
@@ -1607,13 +1621,21 @@ static bool apply_autocmds_group(event_T event,
ap->last = false;
}
ap->last = true;
- check_lnums(true); // make sure cursor and topline are valid
+
+ if (nesting == 1) {
+ // make sure cursor and topline are valid
+ check_lnums(true);
+ }
// Execute the autocmd. The `getnextac` callback handles iteration.
do_cmdline(NULL, getnextac, (void *)&patcmd,
DOCMD_NOWAIT | DOCMD_VERBOSE | DOCMD_REPEAT);
- reset_lnums(); // restore cursor and topline, unless they were changed
+ if (nesting == 1) {
+ // restore cursor and topline, unless they were changed
+ reset_lnums();
+ }
+
if (eap != NULL) {
(void)set_cmdarg(NULL, save_cmdarg);
@@ -1657,11 +1679,13 @@ static bool apply_autocmds_group(event_T event,
did_filetype = false;
while (au_pending_free_buf != NULL) {
buf_T *b = au_pending_free_buf->b_next;
+
xfree(au_pending_free_buf);
au_pending_free_buf = b;
}
while (au_pending_free_win != NULL) {
win_T *w = au_pending_free_win->w_next;
+
xfree(au_pending_free_win);
au_pending_free_win = w;
}
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 0134ee838d..ce4163fccf 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -282,7 +282,7 @@ int open_buffer(
// Set/reset the Changed flag first, autocmds may change the buffer.
// Apply the automatic commands, before processing the modelines.
- // So the modelines have priority over auto commands.
+ // So the modelines have priority over autocommands.
// When reading stdin, the buffer contents always needs writing, so set
// the changed flag. Unless in readonly mode: "ls | nvim -R -".
@@ -596,7 +596,7 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
// Disable buffer-updates for the current buffer.
// No need to check `unload_buf`: in that case the function returned above.
- buf_updates_unregister_all(buf);
+ buf_updates_unload(buf, false);
/*
* Remove the buffer from the list.
@@ -821,7 +821,7 @@ static void free_buffer_stuff(buf_T *buf, int free_flags)
map_clear_int(buf, MAP_ALL_MODES, true, true); // clear local abbrevs
XFREE_CLEAR(buf->b_start_fenc);
- buf_updates_unregister_all(buf);
+ buf_updates_unload(buf, false);
}
/*
@@ -1726,7 +1726,8 @@ buf_T *buflist_new(char_u *ffname_arg, char_u *sfname_arg, linenr_T lnum,
file_id_valid)) != NULL) {
xfree(ffname);
if (lnum != 0) {
- buflist_setfpos(buf, curwin, lnum, (colnr_T)0, false);
+ buflist_setfpos(buf, (flags & BLN_NOCURWIN) ? NULL : curwin,
+ lnum, (colnr_T)0, false);
}
if ((flags & BLN_NOOPT) == 0) {
// Copy the options now, if 'cpo' doesn't have 's' and not done already.
@@ -1843,7 +1844,7 @@ buf_T *buflist_new(char_u *ffname_arg, char_u *sfname_arg, linenr_T lnum,
EMSG(_("W14: Warning: List of file names overflow"));
if (emsg_silent == 0) {
ui_flush();
- os_delay(3000L, true); // make sure it is noticed
+ os_delay(3001L, true); // make sure it is noticed
}
top_file_num = 1;
}
@@ -1945,6 +1946,13 @@ void free_buf_options(buf_T *buf, int free_p_ff)
clear_string_option(&buf->b_p_fo);
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;
+ clear_string_option(&buf->b_p_vts);
+ XFREE_CLEAR(buf->b_p_vts_array);
clear_string_option(&buf->b_p_keymap);
keymap_ga_clear(&buf->b_kmap_ga);
ga_clear(&buf->b_kmap_ga);
@@ -2308,6 +2316,10 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options)
*num_file = 0; // return values in case of FAIL
*file = NULL;
+ if ((options & BUF_DIFF_FILTER) && !curwin->w_p_diff) {
+ return FAIL;
+ }
+
// Make a copy of "pat" and change "^" to "\(^\|[\/]\)".
if (*pat == '^') {
patc = xmalloc(STRLEN(pat) + 11);
@@ -2344,6 +2356,13 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options)
if (!buf->b_p_bl) { // skip unlisted buffers
continue;
}
+ if (options & BUF_DIFF_FILTER) {
+ // Skip buffers not suitable for
+ // :diffget or :diffput completion.
+ if (buf == curbuf || !diff_mode_buf(buf)) {
+ continue;
+ }
+ }
p = buflist_match(&regmatch, buf, p_wic);
if (p != NULL) {
if (round == 1) {
@@ -2486,6 +2505,7 @@ buflist_nr2name(
///
/// @param[in,out] buf Buffer for which line and column are set.
/// @param[in,out] win Window for which line and column are set.
+/// May be NULL when using :badd.
/// @param[in] lnum Line number to be set. If it is zero then only
/// options are touched.
/// @param[in] col Column number to be set.
@@ -2493,7 +2513,7 @@ buflist_nr2name(
void buflist_setfpos(buf_T *const buf, win_T *const win,
linenr_T lnum, colnr_T col,
bool copy_options)
- FUNC_ATTR_NONNULL_ALL
+ FUNC_ATTR_NONNULL_ARG(1)
{
wininfo_T *wip;
@@ -2528,7 +2548,7 @@ void buflist_setfpos(buf_T *const buf, win_T *const win,
wip->wi_fpos.lnum = lnum;
wip->wi_fpos.col = col;
}
- if (copy_options) {
+ if (copy_options && win != NULL) {
// Save the window-specific option values.
copy_winopt(&win->w_onebuf_opt, &wip->wi_opt);
wip->wi_fold_manual = win->w_fold_manual;
@@ -2566,29 +2586,39 @@ static bool wininfo_other_tab_diff(wininfo_T *wip)
return false;
}
-/*
- * Find info for the current window in buffer "buf".
- * If not found, return the info for the most recently used window.
- * When "skip_diff_buffer" is true avoid windows with 'diff' set that is in
- * another tab page.
- * Returns NULL when there isn't any info.
- */
-static wininfo_T *find_wininfo(buf_T *buf, int skip_diff_buffer)
+// Find info for the current window in buffer "buf".
+// If not found, return the info for the most recently used window.
+// When "need_options" is true skip entries where wi_optset is false.
+// When "skip_diff_buffer" is true avoid windows with 'diff' set that is in
+// another tab page.
+// Returns NULL when there isn't any info.
+static wininfo_T *find_wininfo(buf_T *buf, bool need_options,
+ bool skip_diff_buffer)
+ FUNC_ATTR_NONNULL_ALL
{
wininfo_T *wip;
- for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next)
+ for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) {
if (wip->wi_win == curwin
&& (!skip_diff_buffer || !wininfo_other_tab_diff(wip))
- )
+ && (!need_options || wip->wi_optset)) {
break;
+ }
+ }
- /* If no wininfo for curwin, use the first in the list (that doesn't have
- * 'diff' set and is in another tab page). */
+ // If no wininfo for curwin, use the first in the list (that doesn't have
+ // 'diff' set and is in another tab page).
+ // If "need_options" is true skip entries that don't have options set,
+ // unless the window is editing "buf", so we can copy from the window
+ // itself.
if (wip == NULL) {
if (skip_diff_buffer) {
for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) {
- if (!wininfo_other_tab_diff(wip)) {
+ if (!wininfo_other_tab_diff(wip)
+ && (!need_options
+ || wip->wi_optset
+ || (wip->wi_win != NULL
+ && wip->wi_win->w_buffer == buf))) {
break;
}
}
@@ -2607,12 +2637,10 @@ static wininfo_T *find_wininfo(buf_T *buf, int skip_diff_buffer)
*/
void get_winopts(buf_T *buf)
{
- wininfo_T *wip;
-
clear_winopt(&curwin->w_onebuf_opt);
clearFolding(curwin);
- wip = find_wininfo(buf, true);
+ wininfo_T *const wip = find_wininfo(buf, true, true);
if (wip != NULL && wip->wi_win != curwin && wip->wi_win != NULL
&& wip->wi_win->w_buffer == buf) {
win_T *wp = wip->wi_win;
@@ -2649,7 +2677,7 @@ pos_T *buflist_findfpos(buf_T *buf)
{
static pos_T no_position = { 1, 0, 0 };
- wininfo_T *wip = find_wininfo(buf, false);
+ wininfo_T *const wip = find_wininfo(buf, false, false);
return (wip == NULL) ? &no_position : &(wip->wi_fpos);
}
@@ -3236,7 +3264,7 @@ void maketitle(void)
0, maxlen, NULL, NULL);
title_str = (char_u *)buf;
if (called_emsg) {
- set_string_option_direct((char_u *)"titlestring", -1, (char_u *)"",
+ set_string_option_direct("titlestring", -1, (char_u *)"",
OPT_FREE, SID_ERROR);
}
called_emsg |= save_called_emsg;
@@ -3270,7 +3298,7 @@ void maketitle(void)
case 6: buf_p = strappend(buf_p, " -"); break;
case 5:
case 7: buf_p = strappend(buf_p, " -+"); break;
- default: assert(false);
+ default: abort();
}
if (curbuf->b_fname != NULL) {
@@ -3346,7 +3374,7 @@ void maketitle(void)
p_iconstring, use_sandbox,
0, 0, NULL, NULL);
if (called_emsg) {
- set_string_option_direct((char_u *)"iconstring", -1,
+ set_string_option_direct("iconstring", -1,
(char_u *)"", OPT_FREE, SID_ERROR);
}
called_emsg |= save_called_emsg;
@@ -3486,7 +3514,7 @@ int build_stl_str_hl(
stl_items = xmalloc(sizeof(stl_item_t) * stl_items_len);
stl_groupitems = xmalloc(sizeof(int) * stl_items_len);
stl_hltab = xmalloc(sizeof(stl_hlrec_t) * stl_items_len);
- stl_tabtab = xmalloc(sizeof(stl_hlrec_t) * stl_items_len);
+ stl_tabtab = xmalloc(sizeof(StlClickRecord) * stl_items_len);
stl_separator_locations = xmalloc(sizeof(int) * stl_items_len);
}
@@ -3940,9 +3968,9 @@ int build_stl_str_hl(
// Store the current buffer number as a string variable
vim_snprintf((char *)buf_tmp, sizeof(buf_tmp), "%d", curbuf->b_fnum);
- set_internal_string_var((char_u *)"g:actual_curbuf", buf_tmp);
+ set_internal_string_var("g:actual_curbuf", buf_tmp);
vim_snprintf((char *)win_tmp, sizeof(win_tmp), "%d", curwin->handle);
- set_internal_string_var((char_u *)"g:actual_curwin", win_tmp);
+ set_internal_string_var("g:actual_curwin", win_tmp);
buf_T *const save_curbuf = curbuf;
win_T *const save_curwin = curwin;
@@ -4482,22 +4510,15 @@ int build_stl_str_hl(
int num_separators = 0;
for (int i = 0; i < itemcnt; i++) {
if (stl_items[i].type == Separate) {
+ // Create an array of the start location for each
+ // separator mark.
+ stl_separator_locations[num_separators] = i;
num_separators++;
}
}
// If we have separated groups, then we deal with it now
if (num_separators) {
- // Create an array of the start location for each
- // separator mark.
- int index = 0;
- for (int i = 0; i < itemcnt; i++) {
- if (stl_items[i].type == Separate) {
- stl_separator_locations[index] = i;
- index++;
- }
- }
-
int standard_spaces = (maxwidth - width) / num_separators;
int final_spaces = (maxwidth - width) -
standard_spaces * (num_separators - 1);
@@ -5361,8 +5382,8 @@ bool bt_terminal(const buf_T *const buf)
return buf != NULL && buf->b_p_bt[0] == 't';
}
-// Return true if "buf" is a "nofile", "acwrite" or "terminal" buffer.
-// This means the buffer name is not a file name.
+// Return true if "buf" is a "nofile", "acwrite", "terminal" or "prompt"
+// buffer. This means the buffer name is not a file name.
bool bt_nofile(const buf_T *const buf)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
@@ -5372,7 +5393,8 @@ bool bt_nofile(const buf_T *const buf)
|| buf->b_p_bt[0] == 'p');
}
-// Return true if "buf" is a "nowrite", "nofile" or "terminal" buffer.
+// Return true if "buf" is a "nowrite", "nofile", "terminal" or "prompt"
+// buffer.
bool bt_dontwrite(const buf_T *const buf)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
@@ -5467,20 +5489,20 @@ bool find_win_for_buf(buf_T *buf, win_T **wp, tabpage_T **tp)
int buf_signcols(buf_T *buf)
{
if (buf->b_signcols_max == -1) {
- signlist_T *sign; // a sign in the signlist
+ sign_entry_T *sign; // a sign in the sign list
buf->b_signcols_max = 0;
int linesum = 0;
linenr_T curline = 0;
FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (sign->lnum > curline) {
+ if (sign->se_lnum > curline) {
if (linesum > buf->b_signcols_max) {
buf->b_signcols_max = linesum;
}
- curline = sign->lnum;
+ curline = sign->se_lnum;
linesum = 0;
}
- if (sign->has_text_or_icon) {
+ if (sign->se_has_text_or_icon) {
linesum++;
}
}
diff --git a/src/nvim/buffer.h b/src/nvim/buffer.h
index ee3fda5f6d..ac7ead5f92 100644
--- a/src/nvim/buffer.h
+++ b/src/nvim/buffer.h
@@ -33,6 +33,9 @@ enum bln_values {
BLN_DUMMY = 4, // Allocating dummy buffer
BLN_NEW = 8, // create a new buffer
BLN_NOOPT = 16, // Don't copy options to existing buffer
+ // BLN_DUMMY_OK = 32, // also find an existing dummy buffer
+ // BLN_REUSE = 64, // may re-use number from buf_reuse
+ BLN_NOCURWIN = 128, // buffer is not associated with curwin
};
// Values for action argument for do_buffer()
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index cc09b7e989..dd24db910e 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -110,7 +110,7 @@ typedef uint16_t disptick_T; // display tick type
#include "nvim/regexp_defs.h"
// for synstate_T (needs reg_extmatch_T, win_T, buf_T)
#include "nvim/syntax_defs.h"
-// for signlist_T
+// for sign_entry_T
#include "nvim/sign_defs.h"
#include "nvim/os/fs_defs.h" // for FileID
@@ -296,13 +296,11 @@ typedef struct arglist {
int id; ///< id of this arglist
} alist_T;
-/*
- * For each argument remember the file name as it was given, and the buffer
- * number that contains the expanded file name (required for when ":cd" is
- * used.
- *
- * TODO: move aentry_T to another header
- */
+// For each argument remember the file name as it was given, and the buffer
+// number that contains the expanded file name (required for when ":cd" is
+// used).
+//
+// TODO(Felipe): move aentry_T to another header
typedef struct argentry {
char_u *ae_fname; // file name as specified
int ae_fnum; // buffer number with expanded file name
@@ -490,11 +488,12 @@ typedef struct {
LuaRef on_bytes;
LuaRef on_changedtick;
LuaRef on_detach;
+ LuaRef on_reload;
bool utf_sizes;
bool preview;
} BufUpdateCallbacks;
#define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF, LUA_NOREF, \
- LUA_NOREF, false, false }
+ LUA_NOREF, LUA_NOREF, false, false }
EXTERN int curbuf_splice_pending INIT(= 0);
@@ -744,6 +743,11 @@ struct file_buffer {
long b_p_wm; ///< 'wrapmargin'
long b_p_wm_nobin; ///< b_p_wm saved for binary mode
long b_p_wm_nopaste; ///< b_p_wm saved for paste mode
+ char_u *b_p_vsts; ///< 'varsofttabstop'
+ long *b_p_vsts_array; ///< 'varsofttabstop' in internal format
+ char_u *b_p_vsts_nopaste; ///< b_p_vsts saved for paste mode
+ char_u *b_p_vts; ///< 'vartabstop'
+ long *b_p_vts_array; ///< 'vartabstop' in internal format
char_u *b_p_keymap; ///< 'keymap'
// local values for options which are normally global
@@ -844,7 +848,7 @@ struct file_buffer {
// normally points to this, but some windows
// may use a different synblock_T.
- signlist_T *b_signlist; // list of signs to draw
+ sign_entry_T *b_signlist; // list of placed signs
int b_signcols_max; // cached maximum number of sign columns
int b_signcols; // last calculated number of sign columns
@@ -1036,10 +1040,10 @@ struct matchitem {
int id; ///< match ID
int priority; ///< match priority
char_u *pattern; ///< pattern to highlight
- int hlg_id; ///< highlight group ID
regmmatch_T match; ///< regexp program for pattern
posmatch_T pos; ///< position matches
match_T hl; ///< struct for doing the actual highlighting
+ int hlg_id; ///< highlight group ID
int conceal_char; ///< cchar for Conceal highlighting
};
@@ -1080,6 +1084,11 @@ typedef struct {
bool external;
bool focusable;
WinStyle style;
+ bool border;
+ bool shadow;
+ schar_T border_chars[8];
+ int border_hl_ids[8];
+ int border_attr[8];
} FloatConfig;
#define FLOAT_CONFIG_INIT ((FloatConfig){ .height = 0, .width = 0, \
@@ -1193,6 +1202,7 @@ struct window_S {
int tab1; ///< first tab character
int tab2; ///< second tab character
int tab3; ///< third tab character
+ int lead;
int trail;
int conceal;
} w_p_lcs_chars;
@@ -1257,6 +1267,11 @@ struct window_S {
int w_height_request;
int w_width_request;
+ int w_border_adj[4]; // top, right, bottom, left
+ // outer size of window grid, including border
+ int w_height_outer;
+ int w_width_outer;
+
/*
* === start of cached values ====
*/
@@ -1332,7 +1347,8 @@ struct window_S {
// w_redr_type is REDRAW_TOP
linenr_T w_redraw_top; // when != 0: first line needing redraw
linenr_T w_redraw_bot; // when != 0: last line needing redraw
- int w_redr_status; // if TRUE status line must be redrawn
+ bool w_redr_status; // if true status line must be redrawn
+ bool w_redr_border; // if true border must be redrawn
// remember what is shown in the ruler for this window (if 'ruler' set)
pos_T w_ru_cursor; // cursor position shown in ruler
@@ -1410,6 +1426,7 @@ struct window_S {
int w_tagstacklen; // number of tags on stack
ScreenGrid w_grid; // the grid specific to the window
+ ScreenGrid w_grid_alloc; // the grid specific to the window
bool w_pos_changed; // true if window position changed
bool w_floating; ///< whether the window is floating
FloatConfig w_float_config;
diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c
index 68e123896b..5c573530d1 100644
--- a/src/nvim/buffer_updates.c
+++ b/src/nvim/buffer_updates.c
@@ -135,7 +135,7 @@ void buf_updates_unregister(buf_T *buf, uint64_t channelid)
}
}
-void buf_updates_unregister_all(buf_T *buf)
+void buf_updates_unload(buf_T *buf, bool can_reload)
{
size_t size = kv_size(buf->update_channels);
if (size) {
@@ -146,9 +146,20 @@ void buf_updates_unregister_all(buf_T *buf)
kv_init(buf->update_channels);
}
+ size_t j = 0;
for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) {
BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i);
- if (cb.on_detach != LUA_NOREF) {
+ LuaRef thecb = LUA_NOREF;
+
+ bool keep = false;
+ if (can_reload && cb.on_reload != LUA_NOREF) {
+ keep = true;
+ thecb = cb.on_reload;
+ } else if (cb.on_detach != LUA_NOREF) {
+ thecb = cb.on_detach;
+ }
+
+ if (thecb != LUA_NOREF) {
Array args = ARRAY_DICT_INIT;
Object items[1];
args.size = 1;
@@ -158,15 +169,24 @@ void buf_updates_unregister_all(buf_T *buf)
args.items[0] = BUFFER_OBJ(buf->handle);
textlock++;
- nlua_call_ref(cb.on_detach, "detach", args, false, NULL);
+ nlua_call_ref(thecb, keep ? "reload" : "detach", args, false, NULL);
textlock--;
}
- free_update_callbacks(cb);
+
+ if (keep) {
+ kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i);
+ } else {
+ buffer_update_callbacks_free(cb);
+ }
+ }
+ kv_size(buf->update_callbacks) = j;
+ if (kv_size(buf->update_callbacks) == 0) {
+ kv_destroy(buf->update_callbacks);
+ kv_init(buf->update_callbacks);
}
- kv_destroy(buf->update_callbacks);
- kv_init(buf->update_callbacks);
}
+
void buf_updates_send_changes(buf_T *buf,
linenr_T firstline,
int64_t num_added,
@@ -270,7 +290,7 @@ void buf_updates_send_changes(buf_T *buf,
textlock--;
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
- free_update_callbacks(cb);
+ buffer_update_callbacks_free(cb);
keep = false;
}
api_free_object(res);
@@ -322,7 +342,7 @@ void buf_updates_send_splice(
textlock--;
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
- free_update_callbacks(cb);
+ buffer_update_callbacks_free(cb);
keep = false;
}
}
@@ -358,7 +378,7 @@ void buf_updates_changedtick(buf_T *buf)
textlock--;
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
- free_update_callbacks(cb);
+ buffer_update_callbacks_free(cb);
keep = false;
}
api_free_object(res);
@@ -386,8 +406,11 @@ void buf_updates_changedtick_single(buf_T *buf, uint64_t channel_id)
rpc_send_event(channel_id, "nvim_buf_changedtick_event", args);
}
-static void free_update_callbacks(BufUpdateCallbacks cb)
+void buffer_update_callbacks_free(BufUpdateCallbacks cb)
{
api_free_luaref(cb.on_lines);
+ api_free_luaref(cb.on_bytes);
api_free_luaref(cb.on_changedtick);
+ api_free_luaref(cb.on_reload);
+ api_free_luaref(cb.on_detach);
}
diff --git a/src/nvim/change.c b/src/nvim/change.c
index 0f5081c94c..74e27ca880 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -68,7 +68,7 @@ void change_warning(int col)
(void)msg_end();
if (msg_silent == 0 && !silent_mode && ui_active()) {
ui_flush();
- os_delay(1000L, true); // give the user time to think about it
+ os_delay(1002L, true); // give the user time to think about it
}
curbuf->b_did_warn = true;
redraw_cmdline = false; // don't redraw and erase the message
@@ -109,7 +109,7 @@ void changed(void)
// and don't let the emsg() set msg_scroll.
if (need_wait_return && emsg_silent == 0) {
ui_flush();
- os_delay(2000L, true);
+ os_delay(2002L, true);
wait_return(true);
msg_scroll = save_msg_scroll;
} else {
@@ -828,6 +828,7 @@ int copy_indent(int size, char_u *src)
int tab_pad;
int ind_done;
int round;
+ int ind_col;
// Round 1: compute the number of characters needed for the indent
// Round 2: copy the characters.
@@ -835,13 +836,15 @@ int copy_indent(int size, char_u *src)
todo = size;
ind_len = 0;
ind_done = 0;
+ ind_col = 0;
s = src;
// Count/copy the usable portion of the source line.
while (todo > 0 && ascii_iswhite(*s)) {
if (*s == TAB) {
- tab_pad = (int)curbuf->b_p_ts
- - (ind_done % (int)curbuf->b_p_ts);
+ tab_pad = tabstop_padding(ind_done,
+ curbuf->b_p_ts,
+ curbuf->b_p_vts_array);
// Stop if this tab will overshoot the target.
if (todo < tab_pad) {
@@ -849,9 +852,11 @@ int copy_indent(int size, char_u *src)
}
todo -= tab_pad;
ind_done += tab_pad;
+ ind_col += tab_pad;
} else {
todo--;
ind_done++;
+ ind_col++;
}
ind_len++;
@@ -862,11 +867,12 @@ int copy_indent(int size, char_u *src)
}
// Fill to next tabstop with a tab, if possible.
- tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts);
+ tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts, curbuf->b_p_vts_array);
if ((todo >= tab_pad) && !curbuf->b_p_et) {
todo -= tab_pad;
ind_len++;
+ ind_col += tab_pad;
if (p != NULL) {
*p++ = TAB;
@@ -874,12 +880,20 @@ int copy_indent(int size, char_u *src)
}
// Add tabs required for indent.
- while (todo >= (int)curbuf->b_p_ts && !curbuf->b_p_et) {
- todo -= (int)curbuf->b_p_ts;
- ind_len++;
-
- if (p != NULL) {
- *p++ = TAB;
+ if (!curbuf->b_p_et) {
+ for (;;) {
+ tab_pad = tabstop_padding(ind_col,
+ curbuf->b_p_ts,
+ curbuf->b_p_vts_array);
+ if (todo < tab_pad) {
+ break;
+ }
+ todo -= tab_pad;
+ ind_len++;
+ ind_col += tab_pad;
+ if (p != NULL) {
+ *p++ = TAB;
+ }
}
}
@@ -1029,7 +1043,9 @@ int open_line(
|| do_si
) {
// count white space on current line
- newindent = get_indent_str(saved_line, (int)curbuf->b_p_ts, false);
+ newindent = get_indent_str_vtab(saved_line,
+ curbuf->b_p_ts,
+ curbuf->b_p_vts_array, false);
if (newindent == 0 && !(flags & OPENLINE_COM_LIST)) {
newindent = second_line_indent; // for ^^D command in insert mode
}
@@ -1453,7 +1469,9 @@ int open_line(
if (curbuf->b_p_ai
|| do_si
) {
- newindent = get_indent_str(leader, (int)curbuf->b_p_ts, false);
+ newindent = get_indent_str_vtab(leader,
+ curbuf->b_p_ts,
+ curbuf->b_p_vts_array, false);
}
// Add the indent offset
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
index 37cbfb968b..22eb31513d 100644
--- a/src/nvim/channel.c
+++ b/src/nvim/channel.c
@@ -161,7 +161,7 @@ void channel_init(void)
///
/// Channel is allocated with refcount 1, which should be decreased
/// when the underlying stream closes.
-static Channel *channel_alloc(ChannelStreamType type)
+Channel *channel_alloc(ChannelStreamType type)
{
Channel *chan = xcalloc(1, sizeof(*chan));
if (type == kChannelStreamStdio) {
@@ -292,7 +292,6 @@ static void close_cb(Stream *stream, void *data)
/// directory if `cwd` is NULL
/// @param[in] pty_width Width of the pty, ignored if `pty` is false
/// @param[in] pty_height Height of the pty, ignored if `pty` is false
-/// @param[in] term_name `$TERM` for the pty
/// @param[in] env Nvim's configured environment is used if this is NULL,
/// otherwise defines all environment variables
/// @param[out] status_out 0 for invalid arguments, > 0 for the channel id,
@@ -304,7 +303,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
bool pty, bool rpc, bool overlapped, bool detach,
const char *cwd,
uint16_t pty_width, uint16_t pty_height,
- char *term_name, char **env, varnumber_T *status_out)
+ dict_T *env, varnumber_T *status_out)
{
assert(cwd == NULL || os_isdir_executable(cwd));
@@ -317,7 +316,9 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
if (detach) {
EMSG2(_(e_invarg2), "terminal/pty job cannot be detached");
shell_free_argv(argv);
- xfree(term_name);
+ if (env) {
+ tv_dict_free(env);
+ }
channel_destroy_early(chan);
*status_out = 0;
return NULL;
@@ -329,9 +330,6 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
if (pty_height > 0) {
chan->stream.pty.height = pty_height;
}
- if (term_name) {
- chan->stream.pty.term_name = term_name;
- }
} else {
chan->stream.uv = libuv_process_init(&main_loop, chan);
}
@@ -358,17 +356,17 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
if (status) {
EMSG3(_(e_jobspawn), os_strerror(status), cmd);
xfree(cmd);
- os_free_fullenv(proc->env);
- if (proc->type == kProcessTypePty) {
- xfree(chan->stream.pty.term_name);
+ if (proc->env) {
+ tv_dict_free(proc->env);
}
channel_destroy_early(chan);
*status_out = proc->status;
return NULL;
}
xfree(cmd);
- os_free_fullenv(proc->env);
-
+ if (proc->env) {
+ tv_dict_free(proc->env);
+ }
wstream_init(&proc->in, 0);
if (has_out) {
@@ -501,43 +499,54 @@ uint64_t channel_from_stdio(bool rpc, CallbackReader on_output,
}
/// @param data will be consumed
-size_t channel_send(uint64_t id, char *data, size_t len, const char **error)
+size_t channel_send(uint64_t id, char *data, size_t len,
+ bool data_owned, const char **error)
{
Channel *chan = find_channel(id);
+ size_t written = 0;
if (!chan) {
- EMSG(_(e_invchan));
- goto err;
+ *error = _(e_invchan);
+ goto retfree;
}
if (chan->streamtype == kChannelStreamStderr) {
if (chan->stream.err.closed) {
*error = _("Can't send data to closed stream");
- goto err;
+ goto retfree;
}
// unbuffered write
- size_t written = fwrite(data, len, 1, stderr);
- xfree(data);
- return len * written;
+ written = len * fwrite(data, len, 1, stderr);
+ goto retfree;
+ }
+
+ if (chan->streamtype == kChannelStreamInternal && chan->term) {
+ terminal_receive(chan->term, data, len);
+ written = len;
+ goto retfree;
}
Stream *in = channel_instream(chan);
if (in->closed) {
*error = _("Can't send data to closed stream");
- goto err;
+ goto retfree;
}
if (chan->is_rpc) {
*error = _("Can't send raw data to rpc channel");
- goto err;
+ goto retfree;
}
- WBuffer *buf = wstream_new_buffer(data, len, 1, xfree);
+ // write can be delayed indefinitely, so always use an allocated buffer
+ WBuffer *buf = wstream_new_buffer(data_owned ? data : xmemdup(data, len),
+ len, 1, xfree);
return wstream_write(in, buf) ? len : 0;
-err:
- xfree(data);
- return 0;
+retfree:
+ if (data_owned) {
+ xfree(data);
+ }
+ return written;
}
/// Convert binary byte array to a readfile()-style list
@@ -726,8 +735,8 @@ static void channel_callback_call(Channel *chan, CallbackReader *reader)
/// Open terminal for channel
///
/// Channel `chan` is assumed to be an open pty channel,
-/// and curbuf is assumed to be a new, unmodified buffer.
-void channel_terminal_open(Channel *chan)
+/// and `buf` is assumed to be a new, unmodified buffer.
+void channel_terminal_open(buf_T *buf, Channel *chan)
{
TerminalOptions topts;
topts.data = chan;
@@ -736,8 +745,8 @@ void channel_terminal_open(Channel *chan)
topts.write_cb = term_write;
topts.resize_cb = term_resize;
topts.close_cb = term_close;
- curbuf->b_p_channel = (long)chan->id; // 'channel' option
- Terminal *term = terminal_open(topts);
+ buf->b_p_channel = (long)chan->id; // 'channel' option
+ Terminal *term = terminal_open(buf, topts);
chan->term = term;
channel_incref(chan);
}
diff --git a/src/nvim/charset.c b/src/nvim/charset.c
index 3e52b3e3ce..e2d844a351 100644
--- a/src/nvim/charset.c
+++ b/src/nvim/charset.c
@@ -708,7 +708,7 @@ int ptr2cells(const char_u *p)
/// @return number of character cells.
int vim_strsize(char_u *s)
{
- return vim_strnsize(s, (int)MAXCOL);
+ return vim_strnsize(s, MAXCOL);
}
/// Return the number of character cells string "s[len]" will take on the
@@ -744,8 +744,7 @@ int vim_strnsize(char_u *s, int len)
/// @return Number of characters.
#define RET_WIN_BUF_CHARTABSIZE(wp, buf, p, col) \
if (*(p) == TAB && (!(wp)->w_p_list || wp->w_p_lcs_chars.tab1)) { \
- const int ts = (int)(buf)->b_p_ts; \
- return (ts - (int)(col % ts)); \
+ return tabstop_padding(col, (buf)->b_p_ts, (buf)->b_p_vts_array); \
} else { \
return ptr2cells(p); \
}
@@ -1143,8 +1142,9 @@ static int win_nolbr_chartabsize(win_T *wp, char_u *s, colnr_T col, int *headp)
int n;
if ((*s == TAB) && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) {
- n = (int)wp->w_buffer->b_p_ts;
- return n - (col % n);
+ return tabstop_padding(col,
+ wp->w_buffer->b_p_ts,
+ wp->w_buffer->b_p_vts_array);
}
n = ptr2cells(s);
@@ -1211,6 +1211,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor,
char_u *line; // start of the line
int incr;
int head;
+ long *vts = wp->w_buffer->b_p_vts_array;
int ts = (int)wp->w_buffer->b_p_ts;
int c;
@@ -1251,7 +1252,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor,
// A tab gets expanded, depending on the current column
if (c == TAB) {
- incr = ts - (vcol % ts);
+ incr = tabstop_padding(vcol, ts, vts);
} else {
// For utf-8, if the byte is >= 0x80, need to look at
// further bytes to find the cell width.
@@ -1747,7 +1748,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len,
goto vim_str2nr_dec;
}
default: {
- assert(false);
+ abort();
}
}
} else if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN))
@@ -1788,7 +1789,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len,
}
// Do the string-to-numeric conversion "manually" to avoid sscanf quirks.
- assert(false); // Should’ve used goto earlier.
+ abort(); // Should’ve used goto earlier.
#define PARSE_NUMBER(base, cond, conv) \
do { \
while (!STRING_ENDED(ptr) && (cond)) { \
diff --git a/src/nvim/context.c b/src/nvim/context.c
index 1ae0510762..4162daa6ca 100644
--- a/src/nvim/context.c
+++ b/src/nvim/context.c
@@ -126,7 +126,7 @@ bool ctx_restore(Context *ctx, const int flags)
}
char_u *op_shada;
- get_option_value((char_u *)"shada", NULL, &op_shada, OPT_GLOBAL);
+ get_option_value("shada", NULL, &op_shada, OPT_GLOBAL);
set_option_value("shada", 0L, "!,'100,%", OPT_GLOBAL);
if (flags & kCtxRegs) {
diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c
index f3ee42fab1..e39d2328f5 100644
--- a/src/nvim/decoration.c
+++ b/src/nvim/decoration.c
@@ -2,6 +2,7 @@
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
#include "nvim/vim.h"
+#include "nvim/lua/executor.h"
#include "nvim/extmark.h"
#include "nvim/decoration.h"
#include "nvim/screen.h"
@@ -143,10 +144,9 @@ bool decor_redraw_reset(buf_T *buf, DecorState *state)
state->row = -1;
state->buf = buf;
for (size_t i = 0; i < kv_size(state->active); i++) {
- HlRange item = kv_A(state->active, i);
+ DecorRange item = kv_A(state->active, i);
if (item.virt_text_owned) {
- clear_virttext(item.virt_text);
- xfree(item.virt_text);
+ clear_virttext(&item.decor.virt_text);
}
}
kv_size(state->active) = 0;
@@ -188,20 +188,21 @@ bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state)
goto next_mark;
}
- int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0;
- VirtText *vt = kv_size(decor->virt_text) ? &decor->virt_text : NULL;
- HlRange range;
if (mark.id&MARKTREE_END_FLAG) {
- range = (HlRange){ altpos.row, altpos.col, mark.row, mark.col,
- attr_id, decor->priority, vt, false };
+ decor_add(state, altpos.row, altpos.col, mark.row, mark.col,
+ decor, false);
} else {
- range = (HlRange){ mark.row, mark.col, altpos.row,
- altpos.col, attr_id, decor->priority, vt, false };
+ if (altpos.row == -1) {
+ altpos.row = mark.row;
+ altpos.col = mark.col;
+ }
+ decor_add(state, mark.row, mark.col, altpos.row, altpos.col,
+ decor, false);
}
- hlrange_activate(range, state);
next_mark:
if (marktree_itr_node_done(state->itr)) {
+ marktree_itr_next(buf->b_marktree, state->itr);
break;
}
marktree_itr_next(buf->b_marktree, state->itr);
@@ -220,41 +221,37 @@ bool decor_redraw_line(buf_T *buf, int row, DecorState *state)
return true; // TODO(bfredl): be more precise
}
-static void hlrange_activate(HlRange range, DecorState *state)
+static void decor_add(DecorState *state, int start_row, int start_col,
+ int end_row, int end_col, Decoration *decor, bool owned)
{
- // Get size before preparing the push, to have the number of elements
- size_t s = kv_size(state->active);
-
- kv_pushp(state->active);
+ int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0;
- size_t dest_index = 0;
+ DecorRange range = { start_row, start_col, end_row, end_col,
+ *decor, attr_id,
+ kv_size(decor->virt_text) && owned, -1 };
- // Determine insertion dest_index
- while (dest_index < s) {
- HlRange item = kv_A(state->active, dest_index);
- if (item.priority > range.priority) {
+ kv_pushp(state->active);
+ size_t index;
+ for (index = kv_size(state->active)-1; index > 0; index--) {
+ DecorRange item = kv_A(state->active, index-1);
+ if (item.decor.priority <= range.decor.priority) {
break;
}
-
- dest_index++;
- }
-
- // Splice
- for (size_t index = s; index > dest_index; index--) {
kv_A(state->active, index) = kv_A(state->active, index-1);
}
-
- // Insert
- kv_A(state->active, dest_index) = range;
+ kv_A(state->active, index) = range;
}
-int decor_redraw_col(buf_T *buf, int col, DecorState *state)
+int decor_redraw_col(buf_T *buf, int col, int virt_col, bool hidden,
+ DecorState *state)
{
if (col <= state->col_until) {
return state->current;
}
state->col_until = MAXCOL;
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) {
break;
@@ -278,6 +275,11 @@ int decor_redraw_col(buf_T *buf, int col, DecorState *state)
}
Decoration *decor = item->decor;
+ if (endpos.row == -1) {
+ endpos.row = mark.row;
+ endpos.col = mark.col;
+ }
+
if (endpos.row < mark.row
|| (endpos.row == mark.row && endpos.col <= mark.col)) {
if (!kv_size(decor->virt_text)) {
@@ -285,12 +287,8 @@ int decor_redraw_col(buf_T *buf, int col, DecorState *state)
}
}
- int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0;
- VirtText *vt = kv_size(decor->virt_text) ? &decor->virt_text : NULL;
- hlrange_activate((HlRange){ mark.row, mark.col,
- endpos.row, endpos.col,
- attr_id, decor->priority,
- vt, false }, state);
+ decor_add(state, mark.row, mark.col, endpos.row, endpos.col,
+ decor, false);
next_mark:
marktree_itr_next(buf->b_marktree, state->itr);
@@ -299,18 +297,18 @@ next_mark:
int attr = 0;
size_t j = 0;
for (size_t i = 0; i < kv_size(state->active); i++) {
- HlRange item = kv_A(state->active, i);
+ DecorRange item = kv_A(state->active, i);
bool active = false, keep = true;
if (item.end_row < state->row
|| (item.end_row == state->row && item.end_col <= col)) {
- if (!(item.start_row >= state->row && item.virt_text)) {
+ if (!(item.start_row >= state->row && kv_size(item.decor.virt_text))) {
keep = false;
}
} else {
if (item.start_row < state->row
|| (item.start_row == state->row && item.start_col <= col)) {
active = true;
- if (item.end_row == state->row) {
+ if (item.end_row == state->row && item.end_col > col) {
state->col_until = MIN(state->col_until, item.end_col-1);
}
} else {
@@ -322,11 +320,14 @@ next_mark:
if (active && item.attr_id > 0) {
attr = hl_combine_attr(attr, item.attr_id);
}
+ if ((item.start_row == state->row && item.start_col <= col)
+ && kv_size(item.decor.virt_text) && item.virt_col == -1) {
+ item.virt_col = (item.decor.virt_text_hide && hidden) ? -2 : virt_col;
+ }
if (keep) {
- kv_A(state->active, j++) = kv_A(state->active, i);
+ kv_A(state->active, j++) = item;
} else if (item.virt_text_owned) {
- clear_virttext(item.virt_text);
- xfree(item.virt_text);
+ clear_virttext(&item.decor.virt_text);
}
}
kv_size(state->active) = j;
@@ -339,23 +340,81 @@ void decor_redraw_end(DecorState *state)
state->buf = NULL;
}
-VirtText *decor_redraw_virt_text(buf_T *buf, DecorState *state)
+VirtText decor_redraw_eol(buf_T *buf, DecorState *state, int *eol_attr)
{
- decor_redraw_col(buf, MAXCOL, state);
+ decor_redraw_col(buf, MAXCOL, MAXCOL, false, state);
+ VirtText text = VIRTTEXT_EMPTY;
for (size_t i = 0; i < kv_size(state->active); i++) {
- HlRange item = kv_A(state->active, i);
- if (item.start_row == state->row && item.virt_text) {
- return item.virt_text;
+ DecorRange item = kv_A(state->active, i);
+ if (!kv_size(text)
+ && item.start_row == state->row && kv_size(item.decor.virt_text)
+ && item.decor.virt_text_pos == kVTEndOfLine) {
+ text = item.decor.virt_text;
+ }
+
+ if (item.decor.hl_eol && item.start_row <= state->row) {
+ *eol_attr = hl_combine_attr(*eol_attr, item.attr_id);
}
}
- return NULL;
+
+ return text;
+}
+
+void decor_add_ephemeral(int start_row, int start_col, int end_row, int end_col,
+ Decoration *decor)
+{
+ if (end_row == -1) {
+ end_row = start_row;
+ end_col = start_col;
+ }
+ decor_add(&decor_state, start_row, start_col, end_row, end_col, decor, true);
+}
+
+
+DecorProvider *get_decor_provider(NS ns_id, bool force)
+{
+ ssize_t i;
+ for (i = 0; i < (ssize_t)kv_size(decor_providers); i++) {
+ DecorProvider *item = &kv_A(decor_providers, i);
+ if (item->ns_id == ns_id) {
+ return item;
+ } else if (item->ns_id > ns_id) {
+ break;
+ }
+ }
+
+ if (!force) {
+ return NULL;
+ }
+
+ for (ssize_t j = (ssize_t)kv_size(decor_providers)-1; j >= i; j++) {
+ // allocates if needed:
+ (void)kv_a(decor_providers, (size_t)j+1);
+ kv_A(decor_providers, (size_t)j+1) = kv_A(decor_providers, j);
+ }
+ DecorProvider *item = &kv_a(decor_providers, (size_t)i);
+ *item = DECORATION_PROVIDER_INIT(ns_id);
+
+ return item;
}
-void decor_add_ephemeral(int attr_id, int start_row, int start_col,
- int end_row, int end_col, DecorPriority priority,
- VirtText *virt_text)
+void decor_provider_clear(DecorProvider *p)
{
-hlrange_activate(((HlRange){ start_row, start_col, end_row, end_col, attr_id,
- priority, virt_text, virt_text != NULL }),
- &decor_state);
+ if (p == NULL) {
+ return;
+ }
+ NLUA_CLEAR_REF(p->redraw_start);
+ NLUA_CLEAR_REF(p->redraw_buf);
+ NLUA_CLEAR_REF(p->redraw_win);
+ NLUA_CLEAR_REF(p->redraw_line);
+ NLUA_CLEAR_REF(p->redraw_end);
+ p->active = false;
+}
+
+void decor_free_all_mem(void)
+{
+ for (size_t i = 0; i < kv_size(decor_providers); i++) {
+ decor_provider_clear(&kv_A(decor_providers, i));
+ }
+ kv_destroy(decor_providers);
}
diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h
index 2533a641dd..08d69060f0 100644
--- a/src/nvim/decoration.h
+++ b/src/nvim/decoration.h
@@ -18,29 +18,47 @@ typedef kvec_t(VirtTextChunk) VirtText;
typedef uint16_t DecorPriority;
#define DECOR_PRIORITY_BASE 0x1000
+typedef enum {
+ kVTEndOfLine,
+ kVTOverlay,
+} VirtTextPos;
+
+typedef enum {
+ kHlModeUnknown,
+ kHlModeReplace,
+ kHlModeCombine,
+ kHlModeBlend,
+} HlMode;
+
struct Decoration
{
int hl_id; // highlight group
VirtText virt_text;
+ VirtTextPos virt_text_pos;
+ bool virt_text_hide;
+ HlMode hl_mode;
+ bool hl_eol;
// TODO(bfredl): style, signs, etc
DecorPriority priority;
bool shared; // shared decoration, don't free
};
+#define DECORATION_INIT { 0, KV_INITIAL_VALUE, kVTEndOfLine, false, \
+ kHlModeUnknown, false, DECOR_PRIORITY_BASE, false }
typedef struct {
int start_row;
int start_col;
int end_row;
int end_col;
- int attr_id;
- DecorPriority priority;
- VirtText *virt_text;
+ Decoration decor;
+ int attr_id; // cached lookup of decor.hl_id
bool virt_text_owned;
-} HlRange;
+ int virt_col;
+} DecorRange;
typedef struct {
MarkTreeIter itr[1];
- kvec_t(HlRange) active;
+ kvec_t(DecorRange) active;
buf_T *buf;
int top_row;
int row;
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index 93bc34fa4b..31b7b1bd8f 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -58,6 +58,7 @@ static bool diff_need_update = false; // ex_diffupdate needs to be called
#define DIFF_HIDDEN_OFF 0x100 // diffoff when hidden
#define DIFF_INTERNAL 0x200 // use internal xdiff algorithm
#define DIFF_CLOSE_OFF 0x400 // diffoff when closing window
+#define DIFF_FOLLOWWRAP 0x800 // follow the wrap option
#define ALL_WHITE_DIFF (DIFF_IWHITE | DIFF_IWHITEALL | DIFF_IWHITEEOL)
static int diff_flags = DIFF_INTERNAL | DIFF_FILLER | DIFF_CLOSE_OFF;
@@ -1361,11 +1362,12 @@ void diff_win_options(win_T *wp, int addbuf)
wp->w_p_crb_save = wp->w_p_crb;
}
wp->w_p_crb = true;
-
- if (!wp->w_p_diff) {
- wp->w_p_wrap_save = wp->w_p_wrap;
+ if (!(diff_flags & DIFF_FOLLOWWRAP)) {
+ if (!wp->w_p_diff) {
+ wp->w_p_wrap_save = wp->w_p_wrap;
+ }
+ wp->w_p_wrap = false;
}
- wp->w_p_wrap = false;
curwin = wp; // -V519
curbuf = curwin->w_buffer;
@@ -1375,7 +1377,7 @@ void diff_win_options(win_T *wp, int addbuf)
}
wp->w_p_fdm_save = vim_strsave(wp->w_p_fdm);
}
- set_string_option_direct((char_u *)"fdm", -1, (char_u *)"diff",
+ set_string_option_direct("fdm", -1, (char_u *)"diff",
OPT_LOCAL | OPT_FREE, 0);
curwin = old_curwin;
curbuf = curwin->w_buffer;
@@ -1437,11 +1439,11 @@ void ex_diffoff(exarg_T *eap)
if (wp->w_p_crb) {
wp->w_p_crb = wp->w_p_crb_save;
}
-
- if (!wp->w_p_wrap) {
- wp->w_p_wrap = wp->w_p_wrap_save;
+ if (!(diff_flags & DIFF_FOLLOWWRAP)) {
+ if (!wp->w_p_wrap) {
+ wp->w_p_wrap = wp->w_p_wrap_save;
+ }
}
-
free_string_option(wp->w_p_fdm);
wp->w_p_fdm = vim_strsave(*wp->w_p_fdm_save
? wp->w_p_fdm_save
@@ -2158,6 +2160,9 @@ int diffopt_changed(void)
} else if (STRNCMP(p, "closeoff", 8) == 0) {
p += 8;
diff_flags_new |= DIFF_CLOSE_OFF;
+ } else if (STRNCMP(p, "followwrap", 10) == 0) {
+ p += 10;
+ diff_flags_new |= DIFF_FOLLOWWRAP;
} else if (STRNCMP(p, "indent-heuristic", 16) == 0) {
p += 16;
diff_indent_heuristic = XDF_INDENT_HEURISTIC;
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index b2abb06075..f2fddc89fe 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -313,6 +313,11 @@ static void insert_enter(InsertState *s)
set_vim_var_string(VV_CHAR, NULL, -1);
ins_apply_autocmds(EVENT_INSERTENTER);
+ // Check for changed highlighting, e.g. for ModeMsg.
+ if (need_highlight_changed) {
+ highlight_changed();
+ }
+
// Make sure the cursor didn't move. Do call check_cursor_col() in
// case the text was modified. Since Insert mode was not started yet
// a call to check_cursor_col() may move the cursor, especially with
@@ -592,7 +597,10 @@ static int insert_check(VimState *state)
s->mincol = curwin->w_wcol;
validate_cursor_col();
- if (curwin->w_wcol < s->mincol - curbuf->b_p_ts
+ if (
+ curwin->w_wcol < s->mincol - tabstop_at(get_nolist_virtcol(),
+ curbuf->b_p_ts,
+ curbuf->b_p_vts_array)
&& curwin->w_wrow == curwin->w_winrow
+ curwin->w_height_inner - 1 - get_scrolloff_value(curwin)
&& (curwin->w_cursor.lnum != curwin->w_topline
@@ -1019,7 +1027,7 @@ static int insert_handle_key(InsertState *s)
break;
case K_EVENT: // some event
- multiqueue_process_events(main_loop.events);
+ state_handle_k_event();
goto check_pum;
case K_COMMAND: // some command
@@ -1560,7 +1568,7 @@ void edit_putchar(int c, bool highlight)
{
int attr;
- if (curwin->w_grid.chars != NULL || default_grid.chars != NULL) {
+ if (curwin->w_grid_alloc.chars != NULL || default_grid.chars != NULL) {
update_topline(curwin); // just in case w_topline isn't valid
validate_cursor();
if (highlight) {
@@ -1622,11 +1630,11 @@ static void init_prompt(int cmdchar_todo)
ml_append(curbuf->b_ml.ml_line_count, prompt, 0, false);
}
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
- coladvance((colnr_T)MAXCOL);
+ coladvance(MAXCOL);
changed_bytes(curbuf->b_ml.ml_line_count, 0);
}
if (cmdchar_todo == 'A') {
- coladvance((colnr_T)MAXCOL);
+ coladvance(MAXCOL);
}
if (cmdchar_todo == 'I' || curwin->w_cursor.col <= (int)STRLEN(prompt)) {
curwin->w_cursor.col = STRLEN(prompt);
@@ -2050,7 +2058,7 @@ static bool check_compl_option(bool dict_opt)
vim_beep(BO_COMPL);
setcursor();
ui_flush();
- os_delay(2000L, false);
+ os_delay(2004L, false);
}
return false;
}
@@ -2311,7 +2319,11 @@ static int ins_compl_add(char_u *const str, int len,
const Direction dir = (cdir == kDirectionNotSet ? compl_direction : cdir);
int flags = flags_arg;
- os_breakcheck();
+ if (flags & CP_FAST) {
+ fast_breakcheck();
+ } else {
+ os_breakcheck();
+ }
#define FREE_CPTEXT(cptext, cptext_allocated) \
do { \
if (cptext != NULL && cptext_allocated) { \
@@ -2515,7 +2527,8 @@ static void ins_compl_add_matches(int num_matches, char_u **matches, int icase)
for (int i = 0; i < num_matches && add_r != FAIL; i++) {
if ((add_r = ins_compl_add(matches[i], -1, NULL, NULL, false, NULL, dir,
- icase ? CP_ICASE : 0, false)) == OK) {
+ CP_FAST | (icase ? CP_ICASE : 0),
+ false)) == OK) {
// If dir was BACKWARD then honor it just once.
dir = FORWARD;
}
@@ -2590,7 +2603,7 @@ void set_completion(colnr_T startcol, list_T *list)
flags |= CP_ICASE;
}
if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, NULL, 0,
- flags, false) != OK) {
+ flags | CP_FAST, false) != OK) {
return;
}
@@ -3310,8 +3323,8 @@ static int ins_compl_bs(void)
// allow the word to be deleted, we won't match everything.
// Respect the 'backspace' option.
if ((int)(p - line) - (int)compl_col < 0
- || ((int)(p - line) - (int)compl_col == 0
- && ctrl_x_mode != CTRL_X_OMNI) || ctrl_x_mode == CTRL_X_EVAL
+ || ((int)(p - line) - (int)compl_col == 0 && ctrl_x_mode != CTRL_X_OMNI)
+ || ctrl_x_mode == CTRL_X_EVAL
|| (!can_bs(BS_START) && (int)(p - line) - (int)compl_col
- compl_length < 0)) {
return K_BS;
@@ -3926,7 +3939,7 @@ static void ins_compl_add_list(list_T *const list)
// Go through the List with matches and add each of them.
TV_LIST_ITER(list, li, {
- if (ins_compl_add_tv(TV_LIST_ITEM_TV(li), dir) == OK) {
+ if (ins_compl_add_tv(TV_LIST_ITEM_TV(li), dir, true) == OK) {
// If dir was BACKWARD then honor it just once.
dir = FORWARD;
} else if (did_emsg) {
@@ -3965,17 +3978,18 @@ static void ins_compl_add_dict(dict_T *dict)
///
/// @param[in] tv Object to get matches from.
/// @param[in] dir Completion direction.
+/// @param[in] fast use fast_breakcheck() instead of os_breakcheck().
///
/// @return NOTDONE if the given string is already in the list of completions,
/// otherwise it is added to the list and OK is returned. FAIL will be
/// returned in case of error.
-int ins_compl_add_tv(typval_T *const tv, const Direction dir)
+int ins_compl_add_tv(typval_T *const tv, const Direction dir, bool fast)
FUNC_ATTR_NONNULL_ALL
{
const char *word;
bool dup = false;
bool empty = false;
- int flags = 0;
+ int flags = fast ? CP_FAST : 0;
char *(cptext[CPT_COUNT]);
typval_T user_data;
@@ -6241,9 +6255,10 @@ void auto_format(
if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
// "cannot happen"
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
- coladvance((colnr_T)MAXCOL);
- } else
+ coladvance(MAXCOL);
+ } else {
check_cursor_col();
+ }
// Insert mode: If the cursor is now after the end of the line while it
// previously wasn't, the line was broken. Because of the rule above we
@@ -7260,7 +7275,6 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty)
char_u *p;
char_u *line;
int icase;
- int i;
if (keytyped == NUL) {
// Can happen with CTRL-Y and CTRL-E on a short line.
@@ -7345,8 +7359,9 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty)
&& p[curwin->w_cursor.col - 1] == ':'
&& p[curwin->w_cursor.col - 2] == ':') {
p[curwin->w_cursor.col - 1] = ' ';
- i = (cin_iscase(p, FALSE) || cin_isscopedecl(p)
- || cin_islabel());
+ const bool i = cin_iscase(p, false)
+ || cin_isscopedecl(p)
+ || cin_islabel();
p = get_cursor_line_ptr();
p[curwin->w_cursor.col - 1] = ':';
if (i) {
@@ -8172,24 +8187,20 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
/*
* Handle deleting one 'shiftwidth' or 'softtabstop'.
*/
- if ( mode == BACKSPACE_CHAR
- && ((p_sta && in_indent)
- || (get_sts_value() != 0
- && curwin->w_cursor.col > 0
- && (*(get_cursor_pos_ptr() - 1) == TAB
- || (*(get_cursor_pos_ptr() - 1) == ' '
- && (!*inserted_space_p
- || arrow_used)))))) {
+ if (mode == BACKSPACE_CHAR
+ && ((p_sta && in_indent)
+ || ((get_sts_value() != 0
+ || tabstop_count(curbuf->b_p_vsts_array))
+ && curwin->w_cursor.col > 0
+ && (*(get_cursor_pos_ptr() - 1) == TAB
+ || (*(get_cursor_pos_ptr() - 1) == ' '
+ && (!*inserted_space_p || arrow_used)))))) {
int ts;
colnr_T vcol;
colnr_T want_vcol;
colnr_T start_vcol;
- *inserted_space_p = FALSE;
- if (p_sta && in_indent)
- ts = get_sw_value(curbuf);
- else
- ts = get_sts_value();
+ *inserted_space_p = false;
// Compute the virtual column where we want to be. Since
// 'showbreak' may get in the way, need to get the last column of
// the previous character.
@@ -8198,7 +8209,14 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
dec_cursor();
getvcol(curwin, &curwin->w_cursor, NULL, NULL, &want_vcol);
inc_cursor();
- want_vcol = (want_vcol / ts) * ts;
+ if (p_sta && in_indent) {
+ ts = (int)get_sw_value(curbuf);
+ want_vcol = (want_vcol / ts) * ts;
+ } else {
+ want_vcol = tabstop_start(want_vcol,
+ get_sts_value(),
+ curbuf->b_p_vsts_array);
+ }
// delete characters until we are at or before want_vcol
while (vcol > want_vcol
@@ -8427,8 +8445,8 @@ static void ins_left(void)
// if 'whichwrap' set for cursor in insert mode may go to previous line.
// always break undo when moving upwards/downwards, else undo may break
start_arrow(&tpos);
- --(curwin->w_cursor.lnum);
- coladvance((colnr_T)MAXCOL);
+ curwin->w_cursor.lnum--;
+ coladvance(MAXCOL);
curwin->w_set_curswant = true; // so we stay at the end
} else {
vim_beep(BO_CRSR);
@@ -8462,7 +8480,7 @@ static void ins_end(int c)
tpos = curwin->w_cursor;
if (c == K_C_END)
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
- coladvance((colnr_T)MAXCOL);
+ coladvance(MAXCOL);
curwin->w_curswant = MAXCOL;
start_arrow(&tpos);
@@ -8663,10 +8681,19 @@ static bool ins_tab(void)
can_cindent = false;
}
- // When nothing special, insert TAB like a normal character
+ // When nothing special, insert TAB like a normal character.
if (!curbuf->b_p_et
- && !(p_sta && ind && curbuf->b_p_ts != get_sw_value(curbuf))
- && get_sts_value() == 0) {
+ && !(
+ p_sta
+ && ind
+ // These five lines mean 'tabstop' != 'shiftwidth'
+ && ((tabstop_count(curbuf->b_p_vts_array) > 1)
+ || (tabstop_count(curbuf->b_p_vts_array) == 1
+ && tabstop_first(curbuf->b_p_vts_array)
+ != get_sw_value(curbuf))
+ || (tabstop_count(curbuf->b_p_vts_array) == 0
+ && curbuf->b_p_ts != get_sw_value(curbuf))))
+ && tabstop_count(curbuf->b_p_vsts_array) == 0 && get_sts_value() == 0) {
return true;
}
@@ -8680,16 +8707,22 @@ static bool ins_tab(void)
can_si_back = false;
AppendToRedobuff("\t");
- if (p_sta && ind) { // insert tab in indent, use "shiftwidth"
- temp = get_sw_value(curbuf);
- } else if (curbuf->b_p_sts != 0) { // use "softtabstop" when set
- temp = get_sts_value();
- } else { // otherwise use "tabstop"
- temp = (int)curbuf->b_p_ts;
+ if (p_sta && ind) { // insert tab in indent, use 'shiftwidth'
+ temp = (int)get_sw_value(curbuf);
+ temp -= get_nolist_virtcol() % temp;
+ } else if (tabstop_count(curbuf->b_p_vsts_array) > 0
+ || curbuf->b_p_sts != 0) {
+ // use 'softtabstop' when set
+ temp = tabstop_padding(get_nolist_virtcol(),
+ get_sts_value(),
+ curbuf->b_p_vsts_array);
+ } else {
+ // otherwise use 'tabstop'
+ temp = tabstop_padding(get_nolist_virtcol(),
+ curbuf->b_p_ts,
+ curbuf->b_p_vts_array);
}
- temp -= get_nolist_virtcol() % temp;
-
/*
* Insert the first space with ins_char(). It will delete one char in
* replace mode. Insert the rest with ins_str(); it will not delete any
@@ -8710,7 +8743,9 @@ static bool ins_tab(void)
/*
* When 'expandtab' not set: Replace spaces by TABs where possible.
*/
- if (!curbuf->b_p_et && (get_sts_value() || (p_sta && ind))) {
+ if (!curbuf->b_p_et && (tabstop_count(curbuf->b_p_vsts_array) > 0
+ || get_sts_value() > 0
+ || (p_sta && ind))) {
char_u *ptr;
char_u *saved_line = NULL; // init for GCC
pos_T pos;
@@ -8807,6 +8842,11 @@ static bool ins_tab(void)
replace_join(repl_off);
}
}
+ if (!(State & VREPLACE_FLAG)) {
+ extmark_splice_cols(curbuf, fpos.lnum - 1, change_col,
+ cursor->col - change_col, fpos.col - change_col,
+ kExtmarkUndo);
+ }
}
cursor->col -= i;
@@ -9118,10 +9158,16 @@ static void ins_try_si(int c)
* Get the value that w_virtcol would have when 'list' is off.
* Unless 'cpo' contains the 'L' flag.
*/
-static colnr_T get_nolist_virtcol(void)
+colnr_T get_nolist_virtcol(void)
{
- if (curwin->w_p_list && vim_strchr(p_cpo, CPO_LISTWM) == NULL)
+ // check validity of cursor in current buffer
+ if (curwin->w_buffer == NULL || curwin->w_buffer->b_ml.ml_mfp == NULL
+ || curwin->w_cursor.lnum > curwin->w_buffer->b_ml.ml_line_count) {
+ return 0;
+ }
+ if (curwin->w_p_list && vim_strchr(p_cpo, CPO_LISTWM) == NULL) {
return getvcol_nolist(&curwin->w_cursor);
+ }
validate_virtcol();
return curwin->w_virtcol;
}
diff --git a/src/nvim/edit.h b/src/nvim/edit.h
index 09f401ee82..ef5dce738a 100644
--- a/src/nvim/edit.h
+++ b/src/nvim/edit.h
@@ -19,6 +19,7 @@ typedef enum {
CP_CONT_S_IPOS = 4, // use CONT_S_IPOS for compl_cont_status
CP_EQUAL = 8, // ins_compl_equal() always returns true
CP_ICASE = 16, // ins_compl_equal ignores case
+ CP_FAST = 32, // use fast_breakcheck instead of os_breakcheck
} cp_flags_T;
typedef int (*IndentGetter)(void);
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 8a1556320c..05d429c7d5 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -176,7 +176,6 @@ static struct vimvar {
VV(VV_DYING, "dying", VAR_NUMBER, VV_RO),
VV(VV_EXCEPTION, "exception", VAR_STRING, VV_RO),
VV(VV_THROWPOINT, "throwpoint", VAR_STRING, VV_RO),
- VV(VV_STDERR, "stderr", VAR_NUMBER, VV_RO),
VV(VV_REG, "register", VAR_STRING, VV_RO),
VV(VV_CMDBANG, "cmdbang", VAR_NUMBER, VV_RO),
VV(VV_INSERTMODE, "insertmode", VAR_STRING, VV_RO),
@@ -211,13 +210,9 @@ static struct vimvar {
VV(VV_OPTION_OLD, "option_old", VAR_STRING, VV_RO),
VV(VV_OPTION_TYPE, "option_type", VAR_STRING, VV_RO),
VV(VV_ERRORS, "errors", VAR_LIST, 0),
- VV(VV_MSGPACK_TYPES, "msgpack_types", VAR_DICT, VV_RO),
- VV(VV_EVENT, "event", VAR_DICT, VV_RO),
VV(VV_FALSE, "false", VAR_BOOL, VV_RO),
VV(VV_TRUE, "true", VAR_BOOL, VV_RO),
VV(VV_NULL, "null", VAR_SPECIAL, VV_RO),
- VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO),
- VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO),
VV(VV_VIM_DID_ENTER, "vim_did_enter", VAR_NUMBER, VV_RO),
VV(VV_TESTING, "testing", VAR_NUMBER, 0),
VV(VV_TYPE_NUMBER, "t_number", VAR_NUMBER, VV_RO),
@@ -227,10 +222,17 @@ static struct vimvar {
VV(VV_TYPE_DICT, "t_dict", VAR_NUMBER, VV_RO),
VV(VV_TYPE_FLOAT, "t_float", VAR_NUMBER, VV_RO),
VV(VV_TYPE_BOOL, "t_bool", VAR_NUMBER, VV_RO),
+ VV(VV_EVENT, "event", VAR_DICT, VV_RO),
VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO),
+ VV(VV_ARGV, "argv", VAR_LIST, VV_RO),
VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO),
+ // Neovim
+ VV(VV_STDERR, "stderr", VAR_NUMBER, VV_RO),
+ VV(VV_MSGPACK_TYPES, "msgpack_types", VAR_DICT, VV_RO),
+ VV(VV__NULL_STRING, "_null_string", VAR_STRING, VV_RO),
+ VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO),
+ VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO),
VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO),
- VV(VV_ARGV, "argv", VAR_LIST, VV_RO),
};
#undef VV
@@ -365,7 +367,7 @@ void eval_init(void)
eval_msgpack_type_lists[i] = type_list;
if (tv_dict_add(msgpack_types_dict, di) == FAIL) {
// There must not be duplicate items in this dictionary by definition.
- assert(false);
+ abort();
}
}
msgpack_types_dict->dv_lock = VAR_FIXED;
@@ -455,14 +457,15 @@ void eval_clear(void)
* Set an internal variable to a string value. Creates the variable if it does
* not already exist.
*/
-void set_internal_string_var(char_u *name, char_u *value)
+void set_internal_string_var(const char *name, char_u *value)
+ FUNC_ATTR_NONNULL_ARG(1)
{
- const typval_T tv = {
+ typval_T tv = {
.v_type = VAR_STRING,
.vval.v_string = value,
};
- set_var((const char *)name, STRLEN(name), (typval_T *)&tv, true);
+ set_var(name, strlen(name), &tv, true);
}
static lval_T *redir_lval = NULL;
@@ -522,9 +525,9 @@ var_redir_start(
tv.v_type = VAR_STRING;
tv.vval.v_string = (char_u *)"";
if (append) {
- set_var_lval(redir_lval, redir_endp, &tv, true, false, (char_u *)".");
+ set_var_lval(redir_lval, redir_endp, &tv, true, false, ".");
} else {
- set_var_lval(redir_lval, redir_endp, &tv, true, false, (char_u *)"=");
+ set_var_lval(redir_lval, redir_endp, &tv, true, false, "=");
}
clear_lval(redir_lval);
err = did_emsg;
@@ -584,7 +587,7 @@ void var_redir_stop(void)
redir_endp = (char_u *)get_lval(redir_varname, NULL, redir_lval,
false, false, 0, FNE_CHECK_START);
if (redir_endp != NULL && redir_lval->ll_name != NULL) {
- set_var_lval(redir_lval, redir_endp, &tv, false, false, (char_u *)".");
+ set_var_lval(redir_lval, redir_endp, &tv, false, false, ".");
}
clear_lval(redir_lval);
}
@@ -914,6 +917,17 @@ varnumber_T eval_to_number(char_u *expr)
return retval;
}
+// Top level evaluation function.
+// Returns an allocated typval_T with the result.
+// Returns NULL when there is an error.
+typval_T *eval_expr(char_u *arg)
+{
+ typval_T *tv = xmalloc(sizeof(*tv));
+ if (eval0(arg, tv, NULL, true) == FAIL) {
+ XFREE_CLEAR(tv);
+ }
+ return tv;
+}
/*
* Prepare v: variable "idx" to be used.
@@ -1847,7 +1861,7 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,
s = tv_get_string_chk(tv); // != NULL if number or string.
}
if (s != NULL && op != NULL && *op != '=') {
- opt_type = get_option_value(arg, &numval, (char_u **)&stringval,
+ opt_type = get_option_value((char *)arg, &numval, (char_u **)&stringval,
opt_flags);
if ((opt_type == 1 && *op == '.')
|| (opt_type == 0 && *op != '.')) {
@@ -1924,7 +1938,7 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,
if (endchars != NULL && vim_strchr(endchars, *skipwhite(p)) == NULL) {
EMSG(_(e_letunexp));
} else {
- set_var_lval(&lv, p, tv, copy, is_const, op);
+ set_var_lval(&lv, p, tv, copy, is_const, (const char *)op);
arg_end = p;
}
}
@@ -2121,9 +2135,10 @@ char_u *get_lval(char_u *const name, typval_T *const rettv,
return NULL;
}
}
- lp->ll_range = TRUE;
- } else
- lp->ll_range = FALSE;
+ lp->ll_range = true;
+ } else {
+ lp->ll_range = false;
+ }
if (*p != ']') {
if (!quiet) {
@@ -2240,12 +2255,10 @@ char_u *get_lval(char_u *const name, typval_T *const rettv,
return NULL;
}
- /*
- * May need to find the item or absolute index for the second
- * index of a range.
- * When no index given: "lp->ll_empty2" is TRUE.
- * Otherwise "lp->ll_n2" is set to the second index.
- */
+ // May need to find the item or absolute index for the second
+ // index of a range.
+ // When no index given: "lp->ll_empty2" is true.
+ // Otherwise "lp->ll_n2" is set to the second index.
if (lp->ll_range && !lp->ll_empty2) {
lp->ll_n2 = (long)tv_get_number(&var2); // Is number or string.
tv_clear(&var2);
@@ -2299,7 +2312,7 @@ void clear_lval(lval_T *lp)
* "%" for "%=", "." for ".=" or "=" for "=".
*/
static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,
- int copy, const bool is_const, const char_u *op)
+ int copy, const bool is_const, const char *op)
{
int cc;
listitem_T *ri;
@@ -2326,7 +2339,7 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,
TV_CSTRING)
&& !tv_check_lock(di->di_tv.v_lock, (const char *)lp->ll_name,
TV_CSTRING)))
- && eexe_mod_op(&tv, rettv, (const char *)op) == OK) {
+ && eexe_mod_op(&tv, rettv, op) == OK) {
set_var(lp->ll_name, lp->ll_name_len, &tv, false);
}
tv_clear(&tv);
@@ -2369,8 +2382,7 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,
*/
for (ri = tv_list_first(rettv->vval.v_list); ri != NULL; ) {
if (op != NULL && *op != '=') {
- eexe_mod_op(TV_LIST_ITEM_TV(lp->ll_li), TV_LIST_ITEM_TV(ri),
- (const char *)op);
+ eexe_mod_op(TV_LIST_ITEM_TV(lp->ll_li), TV_LIST_ITEM_TV(ri), op);
} else {
tv_clear(TV_LIST_ITEM_TV(lp->ll_li));
tv_copy(TV_LIST_ITEM_TV(ri), TV_LIST_ITEM_TV(lp->ll_li));
@@ -2428,7 +2440,7 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,
}
if (op != NULL && *op != '=') {
- eexe_mod_op(lp->ll_tv, rettv, (const char *)op);
+ eexe_mod_op(lp->ll_tv, rettv, op);
goto notify;
} else {
tv_clear(lp->ll_tv);
@@ -3007,7 +3019,8 @@ static size_t varnamebuflen = 0;
/*
* Function to concatenate a prefix and a variable name.
*/
-static char_u *cat_prefix_varname(int prefix, char_u *name)
+char_u *cat_prefix_varname(int prefix, const char_u *name)
+ FUNC_ATTR_NONNULL_ALL
{
size_t len = STRLEN(name) + 3;
@@ -3128,21 +3141,6 @@ static int pattern_match(char_u *pat, char_u *text, bool ic)
return matches;
}
-/*
- * types for expressions.
- */
-typedef enum {
- TYPE_UNKNOWN = 0,
- TYPE_EQUAL, // ==
- TYPE_NEQUAL, // !=
- TYPE_GREATER, // >
- TYPE_GEQUAL, // >=
- TYPE_SMALLER, // <
- TYPE_SEQUAL, // <=
- TYPE_MATCH, // =~
- TYPE_NOMATCH, // !~
-} exptype_T;
-
// TODO(ZyX-I): move to eval/expressions
/*
@@ -3419,11 +3417,8 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate)
{
typval_T var2;
char_u *p;
- int i;
- exptype_T type = TYPE_UNKNOWN;
- bool type_is = false; // true for "is" and "isnot"
+ exprtype_T type = EXPR_UNKNOWN;
int len = 2;
- varnumber_T n1, n2;
bool ic;
/*
@@ -3434,35 +3429,42 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate)
p = *arg;
switch (p[0]) {
- case '=': if (p[1] == '=')
- type = TYPE_EQUAL;
- else if (p[1] == '~')
- type = TYPE_MATCH;
+ case '=':
+ if (p[1] == '=') {
+ type = EXPR_EQUAL;
+ } else if (p[1] == '~') {
+ type = EXPR_MATCH;
+ }
break;
- case '!': if (p[1] == '=')
- type = TYPE_NEQUAL;
- else if (p[1] == '~')
- type = TYPE_NOMATCH;
+ case '!':
+ if (p[1] == '=') {
+ type = EXPR_NEQUAL;
+ } else if (p[1] == '~') {
+ type = EXPR_NOMATCH;
+ }
break;
- case '>': if (p[1] != '=') {
- type = TYPE_GREATER;
+ case '>':
+ if (p[1] != '=') {
+ type = EXPR_GREATER;
len = 1;
- } else
- type = TYPE_GEQUAL;
+ } else {
+ type = EXPR_GEQUAL;
+ }
break;
- case '<': if (p[1] != '=') {
- type = TYPE_SMALLER;
+ case '<':
+ if (p[1] != '=') {
+ type = EXPR_SMALLER;
len = 1;
- } else
- type = TYPE_SEQUAL;
+ } else {
+ type = EXPR_SEQUAL;
+ }
break;
case 'i': if (p[1] == 's') {
if (p[2] == 'n' && p[3] == 'o' && p[4] == 't') {
len = 5;
}
if (!isalnum(p[len]) && p[len] != '_') {
- type = len == 2 ? TYPE_EQUAL : TYPE_NEQUAL;
- type_is = true;
+ type = len == 2 ? EXPR_IS : EXPR_ISNOT;
}
}
break;
@@ -3471,7 +3473,7 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate)
/*
* If there is a comparative operator, use it.
*/
- if (type != TYPE_UNKNOWN) {
+ if (type != EXPR_UNKNOWN) {
// extra question mark appended: ignore case
if (p[len] == '?') {
ic = true;
@@ -3489,173 +3491,11 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate)
tv_clear(rettv);
return FAIL;
}
-
if (evaluate) {
- if (type_is && rettv->v_type != var2.v_type) {
- /* For "is" a different type always means FALSE, for "notis"
- * it means TRUE. */
- n1 = (type == TYPE_NEQUAL);
- } else if (rettv->v_type == VAR_LIST || var2.v_type == VAR_LIST) {
- if (type_is) {
- n1 = (rettv->v_type == var2.v_type
- && rettv->vval.v_list == var2.vval.v_list);
- if (type == TYPE_NEQUAL)
- n1 = !n1;
- } else if (rettv->v_type != var2.v_type
- || (type != TYPE_EQUAL && type != TYPE_NEQUAL)) {
- if (rettv->v_type != var2.v_type) {
- EMSG(_("E691: Can only compare List with List"));
- } else {
- EMSG(_("E692: Invalid operation for List"));
- }
- tv_clear(rettv);
- tv_clear(&var2);
- return FAIL;
- } else {
- // Compare two Lists for being equal or unequal.
- n1 = tv_list_equal(rettv->vval.v_list, var2.vval.v_list, ic, false);
- if (type == TYPE_NEQUAL) {
- n1 = !n1;
- }
- }
- } else if (rettv->v_type == VAR_DICT || var2.v_type == VAR_DICT) {
- if (type_is) {
- n1 = (rettv->v_type == var2.v_type
- && rettv->vval.v_dict == var2.vval.v_dict);
- if (type == TYPE_NEQUAL)
- n1 = !n1;
- } else if (rettv->v_type != var2.v_type
- || (type != TYPE_EQUAL && type != TYPE_NEQUAL)) {
- if (rettv->v_type != var2.v_type)
- EMSG(_("E735: Can only compare Dictionary with Dictionary"));
- else
- EMSG(_("E736: Invalid operation for Dictionary"));
- tv_clear(rettv);
- tv_clear(&var2);
- return FAIL;
- } else {
- // Compare two Dictionaries for being equal or unequal.
- n1 = tv_dict_equal(rettv->vval.v_dict, var2.vval.v_dict,
- ic, false);
- if (type == TYPE_NEQUAL) {
- n1 = !n1;
- }
- }
- } else if (tv_is_func(*rettv) || tv_is_func(var2)) {
- if (type != TYPE_EQUAL && type != TYPE_NEQUAL) {
- EMSG(_("E694: Invalid operation for Funcrefs"));
- tv_clear(rettv);
- tv_clear(&var2);
- return FAIL;
- }
- if ((rettv->v_type == VAR_PARTIAL
- && rettv->vval.v_partial == NULL)
- || (var2.v_type == VAR_PARTIAL
- && var2.vval.v_partial == NULL)) {
- // when a partial is NULL assume not equal
- n1 = false;
- } else if (type_is) {
- if (rettv->v_type == VAR_FUNC && var2.v_type == VAR_FUNC) {
- // strings are considered the same if their value is
- // the same
- n1 = tv_equal(rettv, &var2, ic, false);
- } else if (rettv->v_type == VAR_PARTIAL
- && var2.v_type == VAR_PARTIAL) {
- n1 = (rettv->vval.v_partial == var2.vval.v_partial);
- } else {
- n1 = false;
- }
- } else {
- n1 = tv_equal(rettv, &var2, ic, false);
- }
- if (type == TYPE_NEQUAL) {
- n1 = !n1;
- }
- }
- /*
- * If one of the two variables is a float, compare as a float.
- * When using "=~" or "!~", always compare as string.
- */
- else if ((rettv->v_type == VAR_FLOAT || var2.v_type == VAR_FLOAT)
- && type != TYPE_MATCH && type != TYPE_NOMATCH) {
- float_T f1, f2;
+ const int ret = typval_compare(rettv, &var2, type, ic);
- if (rettv->v_type == VAR_FLOAT) {
- f1 = rettv->vval.v_float;
- } else {
- f1 = tv_get_number(rettv);
- }
- if (var2.v_type == VAR_FLOAT) {
- f2 = var2.vval.v_float;
- } else {
- f2 = tv_get_number(&var2);
- }
- n1 = false;
- switch (type) {
- case TYPE_EQUAL: n1 = (f1 == f2); break;
- case TYPE_NEQUAL: n1 = (f1 != f2); break;
- case TYPE_GREATER: n1 = (f1 > f2); break;
- case TYPE_GEQUAL: n1 = (f1 >= f2); break;
- case TYPE_SMALLER: n1 = (f1 < f2); break;
- case TYPE_SEQUAL: n1 = (f1 <= f2); break;
- case TYPE_UNKNOWN:
- case TYPE_MATCH:
- case TYPE_NOMATCH: break;
- }
- }
- /*
- * If one of the two variables is a number, compare as a number.
- * When using "=~" or "!~", always compare as string.
- */
- else if ((rettv->v_type == VAR_NUMBER || var2.v_type == VAR_NUMBER)
- && type != TYPE_MATCH && type != TYPE_NOMATCH) {
- n1 = tv_get_number(rettv);
- n2 = tv_get_number(&var2);
- switch (type) {
- case TYPE_EQUAL: n1 = (n1 == n2); break;
- case TYPE_NEQUAL: n1 = (n1 != n2); break;
- case TYPE_GREATER: n1 = (n1 > n2); break;
- case TYPE_GEQUAL: n1 = (n1 >= n2); break;
- case TYPE_SMALLER: n1 = (n1 < n2); break;
- case TYPE_SEQUAL: n1 = (n1 <= n2); break;
- case TYPE_UNKNOWN:
- case TYPE_MATCH:
- case TYPE_NOMATCH: break;
- }
- } else {
- char buf1[NUMBUFLEN];
- char buf2[NUMBUFLEN];
- const char *const s1 = tv_get_string_buf(rettv, buf1);
- const char *const s2 = tv_get_string_buf(&var2, buf2);
- if (type != TYPE_MATCH && type != TYPE_NOMATCH) {
- i = mb_strcmp_ic(ic, s1, s2);
- } else {
- i = 0;
- }
- n1 = false;
- switch (type) {
- case TYPE_EQUAL: n1 = (i == 0); break;
- case TYPE_NEQUAL: n1 = (i != 0); break;
- case TYPE_GREATER: n1 = (i > 0); break;
- case TYPE_GEQUAL: n1 = (i >= 0); break;
- case TYPE_SMALLER: n1 = (i < 0); break;
- case TYPE_SEQUAL: n1 = (i <= 0); break;
-
- case TYPE_MATCH:
- case TYPE_NOMATCH: {
- n1 = pattern_match((char_u *)s2, (char_u *)s1, ic);
- if (type == TYPE_NOMATCH) {
- n1 = !n1;
- }
- break;
- }
- case TYPE_UNKNOWN: break; // Avoid gcc warning.
- }
- }
- tv_clear(rettv);
tv_clear(&var2);
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = n1;
+ return ret;
}
}
@@ -4538,7 +4378,7 @@ int get_option_tv(const char **const arg, typval_T *const rettv,
c = *option_end;
*option_end = NUL;
- opt_type = get_option_value((char_u *)(*arg), &numval,
+ opt_type = get_option_value(*arg, &numval,
rettv == NULL ? NULL : &stringval, opt_flags);
if (opt_type == -3) { // invalid name
@@ -5280,22 +5120,18 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack,
if (ht_stack == NULL) {
abort = set_ref_in_ht(&dd->dv_hashtab, copyID, list_stack);
} else {
- ht_stack_T *newitem = try_malloc(sizeof(ht_stack_T));
- if (newitem == NULL) {
- abort = true;
- } else {
- newitem->ht = &dd->dv_hashtab;
- newitem->prev = *ht_stack;
- *ht_stack = newitem;
- }
+ ht_stack_T *const newitem = xmalloc(sizeof(ht_stack_T));
+ newitem->ht = &dd->dv_hashtab;
+ newitem->prev = *ht_stack;
+ *ht_stack = newitem;
}
QUEUE *w = NULL;
DictWatcher *watcher = NULL;
- QUEUE_FOREACH(w, &dd->watchers) {
+ QUEUE_FOREACH(w, &dd->watchers, {
watcher = tv_dict_watcher_node_data(w);
set_ref_in_callback(&watcher->callback, copyID, ht_stack, list_stack);
- }
+ })
}
break;
}
@@ -5308,14 +5144,10 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack,
if (list_stack == NULL) {
abort = set_ref_in_list(ll, copyID, ht_stack);
} else {
- list_stack_T *newitem = try_malloc(sizeof(list_stack_T));
- if (newitem == NULL) {
- abort = true;
- } else {
- newitem->list = ll;
- newitem->prev = *list_stack;
- *list_stack = newitem;
- }
+ list_stack_T *const newitem = xmalloc(sizeof(list_stack_T));
+ newitem->list = ll;
+ newitem->prev = *list_stack;
+ *list_stack = newitem;
}
}
break;
@@ -5874,26 +5706,53 @@ int assert_inrange(typval_T *argvars)
FUNC_ATTR_NONNULL_ALL
{
bool error = false;
- const varnumber_T lower = tv_get_number_chk(&argvars[0], &error);
- const varnumber_T upper = tv_get_number_chk(&argvars[1], &error);
- const varnumber_T actual = tv_get_number_chk(&argvars[2], &error);
- if (error) {
- return 0;
- }
- if (actual < lower || actual > upper) {
- garray_T ga;
- prepare_assert_error(&ga);
+ if (argvars[0].v_type == VAR_FLOAT
+ || argvars[1].v_type == VAR_FLOAT
+ || argvars[2].v_type == VAR_FLOAT) {
+ const float_T flower = tv_get_float(&argvars[0]);
+ const float_T fupper = tv_get_float(&argvars[1]);
+ const float_T factual = tv_get_float(&argvars[2]);
- char msg[55];
- vim_snprintf(msg, sizeof(msg),
- "range %" PRIdVARNUMBER " - %" PRIdVARNUMBER ",",
- lower, upper);
- fill_assert_error(&ga, &argvars[3], (char_u *)msg, NULL, &argvars[2],
- ASSERT_INRANGE);
- assert_error(&ga);
- ga_clear(&ga);
- return 1;
+ if (factual < flower || factual > fupper) {
+ garray_T ga;
+ prepare_assert_error(&ga);
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ char_u *const tofree = (char_u *)encode_tv2string(&argvars[3], NULL);
+ ga_concat(&ga, tofree);
+ xfree(tofree);
+ } else {
+ char msg[80];
+ vim_snprintf(msg, sizeof(msg), "Expected range %g - %g, but got %g",
+ flower, fupper, factual);
+ ga_concat(&ga, (char_u *)msg);
+ }
+ assert_error(&ga);
+ ga_clear(&ga);
+ return 1;
+ }
+ } else {
+ const varnumber_T lower = tv_get_number_chk(&argvars[0], &error);
+ const varnumber_T upper = tv_get_number_chk(&argvars[1], &error);
+ const varnumber_T actual = tv_get_number_chk(&argvars[2], &error);
+
+ if (error) {
+ return 0;
+ }
+ if (actual < lower || actual > upper) {
+ garray_T ga;
+ prepare_assert_error(&ga);
+
+ char msg[55];
+ vim_snprintf(msg, sizeof(msg),
+ "range %" PRIdVARNUMBER " - %" PRIdVARNUMBER ",",
+ lower, upper);
+ fill_assert_error(&ga, &argvars[3], (char_u *)msg, NULL, &argvars[2],
+ ASSERT_INRANGE);
+ assert_error(&ga);
+ ga_clear(&ga);
+ return 1;
+ }
}
return 0;
}
@@ -5961,6 +5820,35 @@ static void assert_append_cmd_or_arg(garray_T *gap, typval_T *argvars,
}
}
+int assert_beeps(typval_T *argvars, bool no_beep)
+ FUNC_ATTR_NONNULL_ALL
+{
+ const char *const cmd = tv_get_string_chk(&argvars[0]);
+ int ret = 0;
+
+ called_vim_beep = false;
+ suppress_errthrow = true;
+ emsg_silent = false;
+ do_cmdline_cmd(cmd);
+ if (no_beep ? called_vim_beep : !called_vim_beep) {
+ garray_T ga;
+ prepare_assert_error(&ga);
+ if (no_beep) {
+ ga_concat(&ga, (const char_u *)"command did beep: ");
+ } else {
+ ga_concat(&ga, (const char_u *)"command did not beep: ");
+ }
+ ga_concat(&ga, (const char_u *)cmd);
+ assert_error(&ga);
+ ga_clear(&ga);
+ ret = 1;
+ }
+
+ suppress_errthrow = false;
+ emsg_on_display = false;
+ return ret;
+}
+
int assert_fails(typval_T *argvars)
FUNC_ATTR_NONNULL_ALL
{
@@ -6214,6 +6102,7 @@ void common_function(typval_T *argvars, typval_T *rettv,
// function(dict.MyFunc, [arg])
arg_pt = argvars[0].vval.v_partial;
s = partial_name(arg_pt);
+ // TODO(bfredl): do the entire nlua_is_table_from_lua dance
} else {
// function('MyFunc', [arg], dict)
s = (char_u *)tv_get_string(&argvars[0]);
@@ -7313,7 +7202,6 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg)
char_u *name = nlua_register_table_as_callable(arg);
if (name != NULL) {
- func_ref(name);
callback->data.funcref = vim_strsave(name);
callback->type = kCallbackFuncref;
} else {
@@ -7951,8 +7839,8 @@ int get_id_len(const char **const arg)
*/
int get_name_len(const char **const arg,
char **alias,
- int evaluate,
- int verbose)
+ bool evaluate,
+ bool verbose)
{
int len;
@@ -8437,10 +8325,8 @@ char_u *set_cmdarg(exarg_T *eap, char_u *oldarg)
return oldval;
}
-/*
- * Get the value of internal variable "name".
- * Return OK or FAIL.
- */
+// Get the value of internal variable "name".
+// Return OK or FAIL. If OK is returned "rettv" must be cleared.
int get_var_tv(
const char *name,
int len, // length of "name"
@@ -8508,7 +8394,7 @@ static bool tv_is_luafunc(typval_T *tv)
int check_luafunc_name(const char *str, bool paren)
{
const char *p = str;
- while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.') {
+ while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.' || *p == '\'') {
p++;
}
if (*p != (paren ? '(' : NUL)) {
@@ -10697,3 +10583,209 @@ bool invoke_prompt_interrupt(void)
tv_clear(&rettv);
return true;
}
+
+// Compare "typ1" and "typ2". Put the result in "typ1".
+int typval_compare(
+ typval_T *typ1, // first operand
+ typval_T *typ2, // second operand
+ exprtype_T type, // operator
+ bool ic // ignore case
+)
+ FUNC_ATTR_NONNULL_ALL
+{
+ varnumber_T n1, n2;
+ const bool type_is = type == EXPR_IS || type == EXPR_ISNOT;
+
+ if (type_is && typ1->v_type != typ2->v_type) {
+ // For "is" a different type always means false, for "notis"
+ // it means true.
+ n1 = type == EXPR_ISNOT;
+ } else if (typ1->v_type == VAR_LIST || typ2->v_type == VAR_LIST) {
+ if (type_is) {
+ n1 = typ1->v_type == typ2->v_type
+ && typ1->vval.v_list == typ2->vval.v_list;
+ if (type == EXPR_ISNOT) {
+ n1 = !n1;
+ }
+ } else if (typ1->v_type != typ2->v_type
+ || (type != EXPR_EQUAL && type != EXPR_NEQUAL)) {
+ if (typ1->v_type != typ2->v_type) {
+ EMSG(_("E691: Can only compare List with List"));
+ } else {
+ EMSG(_("E692: Invalid operation for List"));
+ }
+ tv_clear(typ1);
+ return FAIL;
+ } else {
+ // Compare two Lists for being equal or unequal.
+ n1 = tv_list_equal(typ1->vval.v_list, typ2->vval.v_list, ic, false);
+ if (type == EXPR_NEQUAL) {
+ n1 = !n1;
+ }
+ }
+ } else if (typ1->v_type == VAR_DICT || typ2->v_type == VAR_DICT) {
+ if (type_is) {
+ n1 = typ1->v_type == typ2->v_type
+ && typ1->vval.v_dict == typ2->vval.v_dict;
+ if (type == EXPR_ISNOT) {
+ n1 = !n1;
+ }
+ } else if (typ1->v_type != typ2->v_type
+ || (type != EXPR_EQUAL && type != EXPR_NEQUAL)) {
+ if (typ1->v_type != typ2->v_type) {
+ EMSG(_("E735: Can only compare Dictionary with Dictionary"));
+ } else {
+ EMSG(_("E736: Invalid operation for Dictionary"));
+ }
+ tv_clear(typ1);
+ return FAIL;
+ } else {
+ // Compare two Dictionaries for being equal or unequal.
+ n1 = tv_dict_equal(typ1->vval.v_dict, typ2->vval.v_dict, ic, false);
+ if (type == EXPR_NEQUAL) {
+ n1 = !n1;
+ }
+ }
+ } else if (tv_is_func(*typ1) || tv_is_func(*typ2)) {
+ if (type != EXPR_EQUAL && type != EXPR_NEQUAL
+ && type != EXPR_IS && type != EXPR_ISNOT) {
+ EMSG(_("E694: Invalid operation for Funcrefs"));
+ tv_clear(typ1);
+ return FAIL;
+ }
+ if ((typ1->v_type == VAR_PARTIAL && typ1->vval.v_partial == NULL)
+ || (typ2->v_type == VAR_PARTIAL && typ2->vval.v_partial == NULL)) {
+ // when a partial is NULL assume not equal
+ n1 = false;
+ } else if (type_is) {
+ if (typ1->v_type == VAR_FUNC && typ2->v_type == VAR_FUNC) {
+ // strings are considered the same if their value is
+ // the same
+ n1 = tv_equal(typ1, typ2, ic, false);
+ } else if (typ1->v_type == VAR_PARTIAL && typ2->v_type == VAR_PARTIAL) {
+ n1 = typ1->vval.v_partial == typ2->vval.v_partial;
+ } else {
+ n1 = false;
+ }
+ } else {
+ n1 = tv_equal(typ1, typ2, ic, false);
+ }
+ if (type == EXPR_NEQUAL || type == EXPR_ISNOT) {
+ n1 = !n1;
+ }
+ } else if ((typ1->v_type == VAR_FLOAT || typ2->v_type == VAR_FLOAT)
+ && type != EXPR_MATCH && type != EXPR_NOMATCH) {
+ // If one of the two variables is a float, compare as a float.
+ // When using "=~" or "!~", always compare as string.
+ const float_T f1 = tv_get_float(typ1);
+ const float_T f2 = tv_get_float(typ2);
+ n1 = false;
+ switch (type) {
+ case EXPR_IS:
+ case EXPR_EQUAL: n1 = f1 == f2; break;
+ case EXPR_ISNOT:
+ case EXPR_NEQUAL: n1 = f1 != f2; break;
+ case EXPR_GREATER: n1 = f1 > f2; break;
+ case EXPR_GEQUAL: n1 = f1 >= f2; break;
+ case EXPR_SMALLER: n1 = f1 < f2; break;
+ case EXPR_SEQUAL: n1 = f1 <= f2; break;
+ case EXPR_UNKNOWN:
+ case EXPR_MATCH:
+ case EXPR_NOMATCH: break; // avoid gcc warning
+ }
+ } else if ((typ1->v_type == VAR_NUMBER || typ2->v_type == VAR_NUMBER)
+ && type != EXPR_MATCH && type != EXPR_NOMATCH) {
+ // If one of the two variables is a number, compare as a number.
+ // When using "=~" or "!~", always compare as string.
+ n1 = tv_get_number(typ1);
+ n2 = tv_get_number(typ2);
+ switch (type) {
+ case EXPR_IS:
+ case EXPR_EQUAL: n1 = n1 == n2; break;
+ case EXPR_ISNOT:
+ case EXPR_NEQUAL: n1 = n1 != n2; break;
+ case EXPR_GREATER: n1 = n1 > n2; break;
+ case EXPR_GEQUAL: n1 = n1 >= n2; break;
+ case EXPR_SMALLER: n1 = n1 < n2; break;
+ case EXPR_SEQUAL: n1 = n1 <= n2; break;
+ case EXPR_UNKNOWN:
+ case EXPR_MATCH:
+ case EXPR_NOMATCH: break; // avoid gcc warning
+ }
+ } else {
+ char buf1[NUMBUFLEN];
+ char buf2[NUMBUFLEN];
+ const char *const s1 = tv_get_string_buf(typ1, buf1);
+ const char *const s2 = tv_get_string_buf(typ2, buf2);
+ int i;
+ if (type != EXPR_MATCH && type != EXPR_NOMATCH) {
+ i = mb_strcmp_ic(ic, s1, s2);
+ } else {
+ i = 0;
+ }
+ n1 = false;
+ switch (type) {
+ case EXPR_IS:
+ case EXPR_EQUAL: n1 = i == 0; break;
+ case EXPR_ISNOT:
+ case EXPR_NEQUAL: n1 = i != 0; break;
+ case EXPR_GREATER: n1 = i > 0; break;
+ case EXPR_GEQUAL: n1 = i >= 0; break;
+ case EXPR_SMALLER: n1 = i < 0; break;
+ case EXPR_SEQUAL: n1 = i <= 0; break;
+
+ case EXPR_MATCH:
+ case EXPR_NOMATCH:
+ n1 = pattern_match((char_u *)s2, (char_u *)s1, ic);
+ if (type == EXPR_NOMATCH) {
+ n1 = !n1;
+ }
+ break;
+ case EXPR_UNKNOWN: break; // avoid gcc warning
+ }
+ }
+ tv_clear(typ1);
+ typ1->v_type = VAR_NUMBER;
+ typ1->vval.v_number = n1;
+ return OK;
+}
+
+char *typval_tostring(typval_T *arg)
+{
+ if (arg == NULL) {
+ return xstrdup("(does not exist)");
+ }
+ return encode_tv2string(arg, NULL);
+}
+
+bool var_exists(const char *var)
+ FUNC_ATTR_NONNULL_ALL
+{
+ char *tofree;
+ bool n = false;
+
+ // get_name_len() takes care of expanding curly braces
+ const char *name = var;
+ const int len = get_name_len((const char **)&var, &tofree, true, false);
+ if (len > 0) {
+ typval_T tv;
+
+ if (tofree != NULL) {
+ name = tofree;
+ }
+ n = get_var_tv(name, len, &tv, NULL, false, true) == OK;
+ if (n) {
+ // Handle d.key, l[idx], f(expr).
+ n = handle_subscript(&var, &tv, true, false) == OK;
+ if (n) {
+ tv_clear(&tv);
+ }
+ }
+ }
+ if (*var != NUL) {
+ n = false;
+ }
+
+ xfree(tofree);
+ return n;
+}
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index 06b7f9e21d..3da4bb8655 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -56,10 +56,10 @@ typedef struct lval_S {
///< isn't NULL it's the Dict to which to add the item.
listitem_T *ll_li; ///< The list item or NULL.
list_T *ll_list; ///< The list or NULL.
- int ll_range; ///< TRUE when a [i:j] range was used.
+ bool ll_range; ///< true when a [i:j] range was used.
+ bool ll_empty2; ///< Second index is empty: [i:].
long ll_n1; ///< First index for list.
long ll_n2; ///< Second index for list range.
- int ll_empty2; ///< Second index is empty: [i:].
dict_T *ll_dict; ///< The Dictionary or NULL.
dictitem_T *ll_di; ///< The dictitem or NULL.
char_u *ll_newkey; ///< New key for Dict in allocated memory or NULL.
@@ -105,7 +105,6 @@ typedef enum {
VV_DYING,
VV_EXCEPTION,
VV_THROWPOINT,
- VV_STDERR,
VV_REG,
VV_CMDBANG,
VV_INSERTMODE,
@@ -140,13 +139,9 @@ typedef enum {
VV_OPTION_OLD,
VV_OPTION_TYPE,
VV_ERRORS,
- VV_MSGPACK_TYPES,
- VV_EVENT,
VV_FALSE,
VV_TRUE,
VV_NULL,
- VV__NULL_LIST, // List with NULL value. For test purposes only.
- VV__NULL_DICT, // Dictionary with NULL value. For test purposes only.
VV_VIM_DID_ENTER,
VV_TESTING,
VV_TYPE_NUMBER,
@@ -156,10 +151,17 @@ typedef enum {
VV_TYPE_DICT,
VV_TYPE_FLOAT,
VV_TYPE_BOOL,
+ VV_EVENT,
VV_ECHOSPACE,
+ VV_ARGV,
VV_EXITING,
+ // Neovim
+ VV_STDERR,
+ VV_MSGPACK_TYPES,
+ VV__NULL_STRING, // String with NULL value. For test purposes only.
+ VV__NULL_LIST, // List with NULL value. For test purposes only.
+ VV__NULL_DICT, // Dictionary with NULL value. For test purposes only.
VV_LUA,
- VV_ARGV,
} VimVarIndex;
/// All recognized msgpack types
@@ -226,6 +228,21 @@ typedef enum
ASSERT_OTHER,
} assert_type_T;
+/// types for expressions.
+typedef enum {
+ EXPR_UNKNOWN = 0,
+ EXPR_EQUAL, ///< ==
+ EXPR_NEQUAL, ///< !=
+ EXPR_GREATER, ///< >
+ EXPR_GEQUAL, ///< >=
+ EXPR_SMALLER, ///< <
+ EXPR_SEQUAL, ///< <=
+ EXPR_MATCH, ///< =~
+ EXPR_NOMATCH, ///< !~
+ EXPR_IS, ///< is
+ EXPR_ISNOT, ///< isnot
+} exprtype_T;
+
/// Type for dict_list function
typedef enum {
kDictListKeys, ///< List dictionary keys.
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 952fa35b83..b10e99fc08 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -26,7 +26,7 @@ return {
arglistid={args={0, 2}},
argv={args={0, 2}},
asin={args=1, func="float_op_wrapper", data="&asin"}, -- WJMc
- assert_beeps={args={1, 2}},
+ assert_beeps={args={1}},
assert_equal={args={2, 3}},
assert_equalfile={args={2, 3}},
assert_exception={args={1, 2}},
@@ -34,6 +34,7 @@ return {
assert_false={args={1, 2}},
assert_inrange={args={3, 4}},
assert_match={args={2, 3}},
+ assert_nobeep={args={1}},
assert_notequal={args={2, 3}},
assert_notmatch={args={2, 3}},
assert_report={args=1},
@@ -63,6 +64,7 @@ return {
chanclose={args={1, 2}},
chansend={args=2},
char2nr={args={1, 2}},
+ charidx={args={2, 3}},
cindent={args=1},
clearmatches={args={0, 1}},
col={args=1},
@@ -308,14 +310,16 @@ return {
setwinvar={args=3},
sha256={args=1},
shellescape={args={1, 2}},
- shiftwidth={},
+ shiftwidth={args={0, 1}},
sign_define={args={1, 2}},
sign_getdefined={args={0, 1}},
sign_getplaced={args={0, 2}},
sign_jump={args={3, 3}},
sign_place={args={4, 5}},
+ sign_placelist={args={1}},
sign_undefine={args={0, 1}},
sign_unplace={args={1, 2}},
+ sign_unplacelist={args={1}},
simplify={args=1},
sin={args=1, func="float_op_wrapper", data="&sin"},
sinh={args=1, func="float_op_wrapper", data="&sinh"},
@@ -340,6 +344,7 @@ return {
string={args=1},
strlen={args=1},
strpart={args={2, 4}},
+ strptime={args=2},
strridx={args={2, 3}},
strtrans={args=1},
strwidth={args=1},
@@ -391,6 +396,7 @@ return {
win_id2tabwin={args=1},
win_id2win={args=1},
win_screenpos={args=1},
+ win_splitmove={args={2, 3}},
winbufnr={args=1},
wincol={},
windowsversion={},
diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c
index 638fef331a..bd4dc87d31 100644
--- a/src/nvim/eval/decode.c
+++ b/src/nvim/eval/decode.c
@@ -147,7 +147,7 @@ static inline int json_decoder_pop(ValuesStackItem obj,
tv_clear(&key.val);
if (tv_dict_add(last_container.container.vval.v_dict, obj_di)
== FAIL) {
- assert(false);
+ abort();
}
obj_di->di_tv = obj.val;
} else {
@@ -480,7 +480,7 @@ static inline int parse_json_string(const char *const buf, const size_t buf_len,
break;
}
default: {
- assert(false);
+ abort();
}
}
} else {
diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c
index 9a9f2e4287..a4d7af7971 100644
--- a/src/nvim/eval/encode.c
+++ b/src/nvim/eval/encode.c
@@ -174,7 +174,7 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack,
case kMPConvPartial: {
switch (v.data.p.stage) {
case kMPConvPartialArgs: {
- assert(false);
+ abort();
break;
}
case kMPConvPartialSelf: {
@@ -237,7 +237,7 @@ bool encode_vim_list_to_buf(const list_T *const list, size_t *const ret_len,
char *const buf = xmalloc(len);
size_t read_bytes;
if (encode_read_from_list(&lrstate, buf, len, &read_bytes) != OK) {
- assert(false);
+ abort();
}
assert(len == read_bytes);
*ret_buf = buf;
diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c
index da05ecda43..bbba9d12f2 100644
--- a/src/nvim/eval/executor.c
+++ b/src/nvim/eval/executor.c
@@ -118,7 +118,7 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2,
return OK;
}
case VAR_UNKNOWN: {
- assert(false);
+ abort();
}
}
}
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 8235d74cbb..0d288e2cc2 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -117,8 +117,12 @@ char_u *get_function_name(expand_T *xp, int idx)
intidx = -1;
if (intidx < 0) {
name = get_user_func_name(xp, idx);
- if (name != NULL)
+ if (name != NULL) {
+ if (*name != '<' && STRNCMP("g:", xp->xp_pattern, 2) == 0) {
+ return cat_prefix_varname('g', name);
+ }
return name;
+ }
}
while ((size_t)++intidx < ARRAY_SIZE(functions)
&& functions[intidx].name[0] == '\0') {
@@ -387,28 +391,16 @@ static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+// "assert_beeps(cmd [, error])" function
static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- const char *const cmd = tv_get_string_chk(&argvars[0]);
- garray_T ga;
- int ret = 0;
-
- called_vim_beep = false;
- suppress_errthrow = true;
- emsg_silent = false;
- do_cmdline_cmd(cmd);
- if (!called_vim_beep) {
- prepare_assert_error(&ga);
- ga_concat(&ga, (const char_u *)"command did not beep: ");
- ga_concat(&ga, (const char_u *)cmd);
- assert_error(&ga);
- ga_clear(&ga);
- ret = 1;
- }
+ rettv->vval.v_number = assert_beeps(argvars, false);
+}
- suppress_errthrow = false;
- emsg_on_display = false;
- rettv->vval.v_number = ret;
+// "assert_nobeep(cmd [, error])" function
+static void f_assert_nobeep(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = assert_beeps(argvars, true);
}
// "assert_equal(expected, actual[, msg])" function
@@ -818,6 +810,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
+ bool owned = false;
char_u *func;
partial_T *partial = NULL;
dict_T *selfdict = NULL;
@@ -828,6 +821,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr)
func = partial_name(partial);
} else if (nlua_is_table_from_lua(&argvars[0])) {
func = nlua_register_table_as_callable(&argvars[0]);
+ owned = true;
} else {
func = (char_u *)tv_get_string(&argvars[0]);
}
@@ -845,6 +839,9 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
func_call(func, &argvars[1], partial, selfdict, rettv);
+ if (owned) {
+ func_unref(func);
+ }
}
/*
@@ -919,7 +916,7 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
uint64_t id = argvars[0].vval.v_number;
const char *error = NULL;
- rettv->vval.v_number = channel_send(id, input, input_len, &error);
+ rettv->vval.v_number = channel_send(id, input, input_len, true, &error);
if (error) {
EMSG(error);
}
@@ -940,6 +937,52 @@ static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
(const char_u *)tv_get_string(&argvars[0]));
}
+// "charidx()" function
+static void f_charidx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = -1;
+
+ if (argvars[0].v_type != VAR_STRING
+ || argvars[1].v_type != VAR_NUMBER
+ || (argvars[2].v_type != VAR_UNKNOWN
+ && argvars[2].v_type != VAR_NUMBER)) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ const char *str = tv_get_string_chk(&argvars[0]);
+ varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
+ if (str == NULL || idx < 0) {
+ return;
+ }
+ int countcc = 0;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ countcc = (int)tv_get_number(&argvars[2]);
+ }
+ if (countcc < 0 || countcc > 1) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ int (*ptr2len)(const char_u *);
+ if (countcc) {
+ ptr2len = utf_ptr2len;
+ } else {
+ ptr2len = utfc_ptr2len;
+ }
+
+ const char *p;
+ int len;
+ for (p = str, len = 0; p <= str + idx; len++) {
+ if (*p == NUL) {
+ return;
+ }
+ p += ptr2len((const char_u *)p);
+ }
+
+ rettv->vval.v_number = len > 0 ? len - 1 : 0;
+}
+
/*
* "cindent(lnum)" function
*/
@@ -1055,7 +1098,7 @@ static void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr)
*/
static void f_complete_add(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- rettv->vval.v_number = ins_compl_add_tv(&argvars[0], 0);
+ rettv->vval.v_number = ins_compl_add_tv(&argvars[0], 0, false);
}
/*
@@ -1629,27 +1672,26 @@ static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (u_save(first - 1, last + 1) == FAIL) {
rettv->vval.v_number = 1; // FAIL
- return;
- }
-
- for (linenr_T lnum = first; lnum <= last; lnum++) {
- ml_delete(first, true);
- }
+ } else {
+ for (linenr_T lnum = first; lnum <= last; lnum++) {
+ ml_delete(first, true);
+ }
- FOR_ALL_TAB_WINDOWS(tp, wp) {
- if (wp->w_buffer == buf) {
- if (wp->w_cursor.lnum > last) {
- wp->w_cursor.lnum -= count;
- } else if (wp->w_cursor.lnum> first) {
- wp->w_cursor.lnum = first;
- }
- if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) {
- wp->w_cursor.lnum = wp->w_buffer->b_ml.ml_line_count;
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ if (wp->w_buffer == buf) {
+ if (wp->w_cursor.lnum > last) {
+ wp->w_cursor.lnum -= count;
+ } else if (wp->w_cursor.lnum> first) {
+ wp->w_cursor.lnum = first;
+ }
+ if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) {
+ wp->w_cursor.lnum = wp->w_buffer->b_ml.ml_line_count;
+ }
}
}
+ check_cursor_col();
+ deleted_lines_mark(first, count);
}
- check_cursor_col();
- deleted_lines_mark(first, count);
if (!is_curbuf) {
curbuf = curbuf_save;
@@ -1798,7 +1840,7 @@ static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr)
os_copy_fullenv(env, env_size);
- for (size_t i = 0; i < env_size; i++) {
+ for (ssize_t i = env_size - 1; i >= 0; i--) {
const char * str = env[i];
const char * const end = strchr(str + (str[0] == '=' ? 1 : 0),
'=');
@@ -1806,6 +1848,12 @@ static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr)
ptrdiff_t len = end - str;
assert(len > 0);
const char * value = str + len + 1;
+ if (tv_dict_find(rettv->vval.v_dict, str, len) != NULL) {
+ // Since we're traversing from the end of the env block to the front, any
+ // duplicate names encountered should be ignored. This preserves the
+ // semantics of env vars defined later in the env block taking precedence.
+ continue;
+ }
tv_dict_add_str(rettv->vval.v_dict,
str, len,
value);
@@ -2003,7 +2051,6 @@ static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
int n = false;
- int len = 0;
const char *p = tv_get_string(&argvars[0]);
if (*p == '$') { // Environment variable.
@@ -2034,29 +2081,7 @@ static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr)
n = au_exists(p + 1);
}
} else { // Internal variable.
- typval_T tv;
-
- // get_name_len() takes care of expanding curly braces
- const char *name = p;
- char *tofree;
- len = get_name_len((const char **)&p, &tofree, true, false);
- if (len > 0) {
- if (tofree != NULL) {
- name = tofree;
- }
- n = (get_var_tv(name, len, &tv, NULL, false, true) == OK);
- if (n) {
- // Handle d.key, l[idx], f(expr).
- n = (handle_subscript(&p, &tv, true, false) == OK);
- if (n) {
- tv_clear(&tv);
- }
- }
- }
- if (*p != NUL)
- n = FALSE;
-
- xfree(tofree);
+ n = var_exists(p);
}
rettv->vval.v_number = n;
@@ -2143,7 +2168,7 @@ static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
tv_list_alloc_ret(rettv, kListLenMayKnow);
int modes = MENU_ALL_MODES;
if (argvars[1].v_type == VAR_STRING) {
- const char_u *const strmodes = (char_u *)tv_get_string(&argvars[1]);
+ const char *const strmodes = tv_get_string(&argvars[1]);
modes = get_menu_cmd_modes(strmodes, false, NULL, NULL);
}
menu_get((char_u *)tv_get_string(&argvars[0]), modes, rettv->vval.v_list);
@@ -2978,10 +3003,11 @@ static void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
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())) {
(void)os_inchar(NULL, 0, -1, 0, main_loop.events);
if (!multiqueue_empty(main_loop.events)) {
- multiqueue_process_events(main_loop.events);
+ state_handle_k_event();
continue;
}
}
@@ -3129,6 +3155,12 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr)
int options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH
| WILD_NO_BEEP;
+ if (argvars[1].v_type != VAR_STRING) {
+ EMSG2(_(e_invarg2), "type must be a string");
+ return;
+ }
+ const char *const type = tv_get_string(&argvars[1]);
+
if (argvars[2].v_type != VAR_UNKNOWN) {
filtered = (bool)tv_get_number_chk(&argvars[2], NULL);
}
@@ -3142,12 +3174,12 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr)
options |= WILD_KEEP_ALL;
}
- if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) {
+ if (argvars[0].v_type != VAR_STRING) {
EMSG(_(e_invarg));
return;
}
- if (strcmp(tv_get_string(&argvars[1]), "cmdline") == 0) {
+ if (strcmp(type, "cmdline") == 0) {
set_one_cmd_context(&xpc, tv_get_string(&argvars[0]));
xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);
goto theend;
@@ -3156,15 +3188,14 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr)
ExpandInit(&xpc);
xpc.xp_pattern = (char_u *)tv_get_string(&argvars[0]);
xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);
- xpc.xp_context = cmdcomplete_str_to_type(
- (char_u *)tv_get_string(&argvars[1]));
+ xpc.xp_context = cmdcomplete_str_to_type(type);
if (xpc.xp_context == EXPAND_NOTHING) {
- EMSG2(_(e_invarg2), argvars[1].vval.v_string);
+ EMSG2(_(e_invarg2), type);
return;
}
if (xpc.xp_context == EXPAND_MENUS) {
- set_context_in_menu_cmd(&xpc, (char_u *)"menu", xpc.xp_pattern, false);
+ set_context_in_menu_cmd(&xpc, "menu", xpc.xp_pattern, false);
xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);
}
@@ -3301,7 +3332,7 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
break;
case kCdScopeInvalid: // We should never get here
- assert(false);
+ abort();
}
if (from) {
@@ -3919,6 +3950,87 @@ static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1);
}
+//
+// Move the window wp into a new split of targetwin in a given direction
+//
+static void win_move_into_split(win_T *wp, win_T *targetwin,
+ int size, int flags)
+{
+ int dir;
+ int height = wp->w_height;
+ win_T *oldwin = curwin;
+
+ if (wp == targetwin) {
+ return;
+ }
+
+ // Jump to the target window
+ if (curwin != targetwin) {
+ win_goto(targetwin);
+ }
+
+ // Remove the old window and frame from the tree of frames
+ (void)winframe_remove(wp, &dir, NULL);
+ win_remove(wp, NULL);
+ last_status(false); // may need to remove last status line
+ (void)win_comp_pos(); // recompute window positions
+
+ // Split a window on the desired side and put the old window there
+ (void)win_split_ins(size, flags, wp, dir);
+
+ // If splitting horizontally, try to preserve height
+ if (size == 0 && !(flags & WSP_VERT)) {
+ win_setheight_win(height, wp);
+ if (p_ea) {
+ win_equal(wp, true, 'v');
+ }
+ }
+
+ if (oldwin != curwin) {
+ win_goto(oldwin);
+ }
+}
+
+// "win_splitmove()" function
+static void f_win_splitmove(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *wp;
+ win_T *targetwin;
+ int flags = 0, size = 0;
+
+ wp = find_win_by_nr_or_id(&argvars[0]);
+ targetwin = find_win_by_nr_or_id(&argvars[1]);
+
+ if (wp == NULL || targetwin == NULL || wp == targetwin
+ || !win_valid(wp) || !win_valid(targetwin)
+ || win_valid_floating(wp) || win_valid_floating(targetwin)) {
+ EMSG(_(e_invalwindow));
+ rettv->vval.v_number = -1;
+ return;
+ }
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ dict_T *d;
+ dictitem_T *di;
+
+ if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ d = argvars[2].vval.v_dict;
+ if (tv_dict_get_number(d, "vertical")) {
+ flags |= WSP_VERT;
+ }
+ if ((di = tv_dict_find(d, "rightbelow", -1)) != NULL) {
+ flags |= tv_get_number(&di->di_tv) ? WSP_BELOW : WSP_ABOVE;
+ }
+ size = tv_dict_get_number(d, "size");
+ }
+
+ win_move_into_split(wp, targetwin, size, flags);
+}
+
// "getwinpos({timeout})" function
static void f_getwinpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
@@ -4075,7 +4187,9 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)
#ifdef _WIN64
"win64",
#endif
+#ifndef CASE_INSENSITIVE_FILENAME
"fname_case",
+#endif
#ifdef HAVE_ACL
"acl",
#endif
@@ -4168,6 +4282,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)
"title",
"user-commands", // was accidentally included in 5.4
"user_commands",
+ "vartabs",
"vertsplit",
"virtualedit",
"visual",
@@ -4354,7 +4469,7 @@ static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
break;
case kCdScopeInvalid:
// We should never get here
- assert(false);
+ abort();
}
}
@@ -4875,6 +4990,108 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = 1;
}
+static const char *ignored_env_vars[] = {
+#ifndef WIN32
+ "COLUMNS",
+ "LINES",
+ "TERMCAP",
+ "COLORFGBG",
+#endif
+ NULL
+};
+
+/// According to comments in src/win/process.c of libuv, Windows has a few
+/// "essential" environment variables.
+static const char *required_env_vars[] = {
+#ifdef WIN32
+ "HOMEDRIVE",
+ "HOMEPATH",
+ "LOGONSERVER",
+ "PATH",
+ "SYSTEMDRIVE",
+ "SYSTEMROOT",
+ "TEMP",
+ "USERDOMAIN",
+ "USERNAME",
+ "USERPROFILE",
+ "WINDIR",
+#endif
+ NULL
+};
+
+static dict_T *create_environment(const dictitem_T *job_env,
+ const bool clear_env,
+ const bool pty,
+ const char * const pty_term_name)
+{
+ dict_T * env = tv_dict_alloc();
+
+ if (!clear_env) {
+ typval_T temp_env = TV_INITIAL_VALUE;
+ f_environ(NULL, &temp_env, NULL);
+ tv_dict_extend(env, temp_env.vval.v_dict, "force");
+ tv_dict_free(temp_env.vval.v_dict);
+
+ if (pty) {
+ // These environment variables generally shouldn't be propagated to the
+ // child process. We're removing them here so the user can still decide
+ // they want to explicitly set them.
+ for (size_t i = 0;
+ i < ARRAY_SIZE(ignored_env_vars) && ignored_env_vars[i];
+ i++) {
+ dictitem_T *dv = tv_dict_find(env, ignored_env_vars[i], -1);
+ if (dv) {
+ tv_dict_item_remove(env, dv);
+ }
+ }
+#ifndef WIN32
+ // Set COLORTERM to "truecolor" if termguicolors is set and 256
+ // otherwise, but only if it was set in the parent terminal at all
+ dictitem_T *dv = tv_dict_find(env, S_LEN("COLORTERM"));
+ if (dv) {
+ tv_dict_item_remove(env, dv);
+ tv_dict_add_str(env, S_LEN("COLORTERM"), p_tgc ? "truecolor" : "256");
+ }
+#endif
+ }
+ }
+
+ // For a pty, we need a sane $TERM set. We can't rely on nvim's environment,
+ // because the child process is going to be communicating with nvim, not the
+ // parent terminal. Set a sane default, but let the user override it in the
+ // job's environment if they want.
+ if (pty) {
+ dictitem_T *dv = tv_dict_find(env, S_LEN("TERM"));
+ if (dv) {
+ tv_dict_item_remove(env, dv);
+ }
+ tv_dict_add_str(env, S_LEN("TERM"), pty_term_name);
+ }
+
+ if (job_env) {
+ tv_dict_extend(env, job_env->di_tv.vval.v_dict, "force");
+ }
+
+ if (pty) {
+ // Now that the custom environment is configured, we need to ensure certain
+ // environment variables are present.
+ for (size_t i = 0;
+ i < ARRAY_SIZE(required_env_vars) && required_env_vars[i];
+ i++) {
+ size_t len = strlen(required_env_vars[i]);
+ dictitem_T *dv = tv_dict_find(env, required_env_vars[i], len);
+ if (!dv) {
+ const char *env_var = os_getenv(required_env_vars[i]);
+ if (env_var) {
+ tv_dict_add_str(env, required_env_vars[i], len, env_var);
+ }
+ }
+ }
+ }
+
+ return env;
+}
+
// "jobstart()" function
static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
@@ -4887,7 +5104,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
bool executable = true;
char **argv = tv_to_argv(&argvars[0], NULL, &executable);
- char **env = NULL;
+ dict_T *env = NULL;
if (!argv) {
rettv->vval.v_number = executable ? 0 : -1;
return; // Did error message in tv_to_argv.
@@ -4911,6 +5128,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
on_stderr = CALLBACK_READER_INIT;
Callback on_exit = CALLBACK_NONE;
char *cwd = NULL;
+ dictitem_T *job_env = NULL;
if (argvars[1].v_type == VAR_DICT) {
job_opts = argvars[1].vval.v_dict;
@@ -4936,7 +5154,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
#endif
char *new_cwd = tv_dict_get_string(job_opts, "cwd", false);
- if (new_cwd && strlen(new_cwd) > 0) {
+ if (new_cwd && *new_cwd != NUL) {
cwd = new_cwd;
// The new cwd must be a directory.
if (!os_isdir_executable((const char *)cwd)) {
@@ -4945,46 +5163,14 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
}
- dictitem_T *job_env = tv_dict_find(job_opts, S_LEN("env"));
- if (job_env) {
- if (job_env->di_tv.v_type != VAR_DICT) {
- EMSG2(_(e_invarg2), "env");
- shell_free_argv(argv);
- return;
- }
-
- size_t custom_env_size = (size_t)tv_dict_len(job_env->di_tv.vval.v_dict);
- size_t i = 0;
- size_t env_size = 0;
-
- if (clear_env) {
- // + 1 for last null entry
- env = xmalloc((custom_env_size + 1) * sizeof(*env));
- env_size = 0;
- } else {
- env_size = os_get_fullenv_size();
-
- env = xmalloc((custom_env_size + env_size + 1) * sizeof(*env));
- os_copy_fullenv(env, env_size);
- i = env_size;
- }
- assert(env); // env must be allocated at this point
-
- TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, {
- const char *str = tv_get_string(&var->di_tv);
- assert(str);
- size_t len = STRLEN(var->di_key) + strlen(str) + strlen("=") + 1;
- env[i] = xmalloc(len);
- snprintf(env[i], len, "%s=%s", (char *)var->di_key, str);
- i++;
- });
-
- // must be null terminated
- env[env_size + custom_env_size] = NULL;
+ job_env = tv_dict_find(job_opts, S_LEN("env"));
+ if (job_env && job_env->di_tv.v_type != VAR_DICT) {
+ EMSG2(_(e_invarg2), "env");
+ shell_free_argv(argv);
+ return;
}
-
if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) {
shell_free_argv(argv);
return;
@@ -4997,12 +5183,19 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (pty) {
width = (uint16_t)tv_dict_get_number(job_opts, "width");
height = (uint16_t)tv_dict_get_number(job_opts, "height");
- term_name = tv_dict_get_string(job_opts, "TERM", true);
+ // Legacy method, before env option existed, to specify $TERM. No longer
+ // documented, but still usable to avoid breaking scripts.
+ term_name = tv_dict_get_string(job_opts, "TERM", false);
+ if (!term_name) {
+ term_name = "ansi";
+ }
}
+ env = create_environment(job_env, clear_env, pty, term_name);
+
Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty,
rpc, overlapped, detach, cwd, width, height,
- term_name, env, &rettv->vval.v_number);
+ env, &rettv->vval.v_number);
if (chan) {
channel_create_event(chan, NULL);
}
@@ -7445,7 +7638,7 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT,
CALLBACK_READER_INIT, CALLBACK_NONE,
false, true, false, false, NULL, 0, 0,
- NULL, NULL, &rettv->vval.v_number);
+ NULL, &rettv->vval.v_number);
if (chan) {
channel_create_event(chan, NULL);
}
@@ -8587,8 +8780,6 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (set_tagstack(wp, d, action) == OK) {
rettv->vval.v_number = 0;
- } else {
- EMSG(_(e_listreq));
}
}
@@ -8628,6 +8819,18 @@ static void f_shellescape(typval_T *argvars, typval_T *rettv, FunPtr fptr)
*/
static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
+ rettv->vval.v_number = 0;
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ long col;
+
+ col = (long)tv_get_number_chk(argvars, NULL);
+ if (col < 0) {
+ return; // type error; errmsg already given
+ }
+ rettv->vval.v_number = get_sw_value_col(curbuf, col);
+ return;
+ }
rettv->vval.v_number = get_sw_value(curbuf);
}
@@ -8635,56 +8838,30 @@ static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static void f_sign_define(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
const char *name;
- dict_T *dict;
- char *icon = NULL;
- char *linehl = NULL;
- char *text = NULL;
- char *texthl = NULL;
- char *numhl = NULL;
- rettv->vval.v_number = -1;
+ if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN) {
+ // Define multiple signs
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
- name = tv_get_string_chk(&argvars[0]);
- if (name == NULL) {
+ sign_define_multiple(argvars[0].vval.v_list, rettv->vval.v_list);
return;
}
- if (argvars[1].v_type != VAR_UNKNOWN) {
- if (argvars[1].v_type != VAR_DICT) {
- EMSG(_(e_dictreq));
- return;
- }
+ // Define a single sign
+ rettv->vval.v_number = -1;
- // sign attributes
- dict = argvars[1].vval.v_dict;
- if (tv_dict_find(dict, "icon", -1) != NULL) {
- icon = tv_dict_get_string(dict, "icon", true);
- }
- if (tv_dict_find(dict, "linehl", -1) != NULL) {
- linehl = tv_dict_get_string(dict, "linehl", true);
- }
- if (tv_dict_find(dict, "text", -1) != NULL) {
- text = tv_dict_get_string(dict, "text", true);
- }
- if (tv_dict_find(dict, "texthl", -1) != NULL) {
- texthl = tv_dict_get_string(dict, "texthl", true);
- }
- if (tv_dict_find(dict, "numhl", -1) != NULL) {
- numhl = tv_dict_get_string(dict, "numhl", true);
- }
+ name = tv_get_string_chk(&argvars[0]);
+ if (name == NULL) {
+ return;
}
- if (sign_define_by_name((char_u *)name, (char_u *)icon, (char_u *)linehl,
- (char_u *)text, (char_u *)texthl, (char_u *)numhl)
- == OK) {
- rettv->vval.v_number = 0;
+ if (argvars[1].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_DICT) {
+ EMSG(_(e_dictreq));
+ return;
}
- xfree(icon);
- xfree(linehl);
- xfree(text);
- xfree(texthl);
- xfree(numhl);
+ rettv->vval.v_number = sign_define_from_dict(
+ name, argvars[1].v_type == VAR_DICT ? argvars[1].vval.v_dict : NULL);
}
/// "sign_getdefined()" function
@@ -8769,7 +8946,7 @@ static void f_sign_jump(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = -1;
- // Sign identifer
+ // Sign identifier
sign_id = (int)tv_get_number_chk(&argvars[0], &notanum);
if (notanum) {
return;
@@ -8805,83 +8982,44 @@ cleanup:
/// "sign_place()" function
static void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- int sign_id;
- char_u *group = NULL;
- const char *sign_name;
- buf_T *buf;
- dict_T *dict;
- dictitem_T *di;
- linenr_T lnum = 0;
- int prio = SIGN_DEF_PRIO;
- bool notanum = false;
+ dict_T *dict = NULL;
rettv->vval.v_number = -1;
- // Sign identifer
- sign_id = (int)tv_get_number_chk(&argvars[0], &notanum);
- if (notanum) {
- return;
- }
- if (sign_id < 0) {
- EMSG(_(e_invarg));
+ if (argvars[4].v_type != VAR_UNKNOWN
+ && (argvars[4].v_type != VAR_DICT
+ || ((dict = argvars[4].vval.v_dict) == NULL))) {
+ EMSG(_(e_dictreq));
return;
}
- // Sign group
- const char *group_chk = tv_get_string_chk(&argvars[1]);
- if (group_chk == NULL) {
- return;
- }
- if (group_chk[0] == '\0') {
- group = NULL; // global sign group
- } else {
- group = vim_strsave((const char_u *)group_chk);
- }
+ rettv->vval.v_number = sign_place_from_dict(
+ &argvars[0], &argvars[1], &argvars[2], &argvars[3], dict);
+}
- // Sign name
- sign_name = tv_get_string_chk(&argvars[2]);
- if (sign_name == NULL) {
- goto cleanup;
- }
+/// "sign_placelist()" function. Place multiple signs.
+static void f_sign_placelist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int sign_id;
- // Buffer to place the sign
- buf = get_buf_arg(&argvars[3]);
- if (buf == NULL) {
- goto cleanup;
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG(_(e_listreq));
+ return;
}
- if (argvars[4].v_type != VAR_UNKNOWN) {
- if (argvars[4].v_type != VAR_DICT
- || ((dict = argvars[4].vval.v_dict) == NULL)) {
+ // Process the List of sign attributes
+ TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, {
+ sign_id = -1;
+ if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) {
+ sign_id = sign_place_from_dict(
+ NULL, NULL, NULL, NULL, TV_LIST_ITEM_TV(li)->vval.v_dict);
+ } else {
EMSG(_(e_dictreq));
- goto cleanup;
- }
-
- // Line number where the sign is to be placed
- if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) {
- lnum = (linenr_T)tv_get_number_chk(&di->di_tv, &notanum);
- if (notanum) {
- goto cleanup;
- }
- (void)lnum;
- lnum = tv_get_lnum(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, "priority", -1)) != NULL) {
- // Sign priority
- prio = (int)tv_get_number_chk(&di->di_tv, &notanum);
- if (notanum) {
- goto cleanup;
- }
}
- }
-
- if (sign_place(&sign_id, group, (const char_u *)sign_name, buf, lnum, prio)
- == OK) {
- rettv->vval.v_number = sign_id;
- }
-
-cleanup:
- xfree(group);
+ tv_list_append_number(rettv->vval.v_list, sign_id);
+ });
}
/// "sign_undefine()" function
@@ -8889,6 +9027,14 @@ static void f_sign_undefine(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
const char *name;
+ if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN) {
+ // Undefine multiple signs
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+
+ sign_undefine_multiple(argvars[0].vval.v_list, rettv->vval.v_list);
+ return;
+ }
+
rettv->vval.v_number = -1;
if (argvars[0].v_type == VAR_UNKNOWN) {
@@ -8911,11 +9057,7 @@ static void f_sign_undefine(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "sign_unplace()" function
static void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- dict_T *dict;
- dictitem_T *di;
- int sign_id = 0;
- buf_T *buf = NULL;
- char_u *group = NULL;
+ dict_T *dict = NULL;
rettv->vval.v_number = -1;
@@ -8924,46 +9066,38 @@ static void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
- const char *group_chk = tv_get_string(&argvars[0]);
- if (group_chk[0] == '\0') {
- group = NULL; // global sign group
- } else {
- group = vim_strsave((const char_u *)group_chk);
- }
-
if (argvars[1].v_type != VAR_UNKNOWN) {
if (argvars[1].v_type != VAR_DICT) {
EMSG(_(e_dictreq));
- goto cleanup;
+ return;
}
dict = argvars[1].vval.v_dict;
-
- if ((di = tv_dict_find(dict, "buffer", -1)) != NULL) {
- buf = get_buf_arg(&di->di_tv);
- if (buf == NULL) {
- goto cleanup;
- }
- }
- if (tv_dict_find(dict, "id", -1) != NULL) {
- sign_id = tv_dict_get_number(dict, "id");
- }
}
- if (buf == NULL) {
- // Delete the sign in all the buffers
- FOR_ALL_BUFFERS(cbuf) {
- if (sign_unplace(sign_id, group, cbuf, 0) == OK) {
- rettv->vval.v_number = 0;
- }
- }
- } else {
- if (sign_unplace(sign_id, group, buf, 0) == OK) {
- rettv->vval.v_number = 0;
- }
+ rettv->vval.v_number = sign_unplace_from_dict(&argvars[0], dict);
+}
+
+/// "sign_unplacelist()" function
+static void f_sign_unplacelist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int retval;
+
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG(_(e_listreq));
+ return;
}
-cleanup:
- xfree(group);
+ TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, {
+ retval = -1;
+ if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) {
+ retval = sign_unplace_from_dict(NULL, TV_LIST_ITEM_TV(li)->vval.v_dict);
+ } else {
+ EMSG(_(e_dictreq));
+ }
+ tv_list_append_number(rettv->vval.v_list, retval);
+ });
}
/*
@@ -9969,6 +10103,38 @@ static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_string = (char_u *)xmemdupz(p + n, (size_t)len);
}
+// "strptime({format}, {timestring})" function
+static void f_strptime(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char fmt_buf[NUMBUFLEN];
+ char str_buf[NUMBUFLEN];
+
+ struct tm tmval = {
+ .tm_isdst = -1,
+ };
+ char *fmt = (char *)tv_get_string_buf(&argvars[0], fmt_buf);
+ char *str = (char *)tv_get_string_buf(&argvars[1], str_buf);
+
+ vimconv_T conv = {
+ .vc_type = CONV_NONE,
+ };
+ char_u *enc = enc_locale();
+ convert_setup(&conv, p_enc, enc);
+ if (conv.vc_type != CONV_NONE) {
+ fmt = (char *)string_convert(&conv, (char_u *)fmt, NULL);
+ }
+ if (fmt == NULL
+ || os_strptime(str, fmt, &tmval) == NULL
+ || (rettv->vval.v_number = mktime(&tmval)) == -1) {
+ rettv->vval.v_number = 0;
+ }
+ if (conv.vc_type != CONV_NONE) {
+ xfree(fmt);
+ }
+ convert_setup(&conv, NULL, NULL);
+ xfree(enc);
+}
+
/*
* "strridx()" function
*/
@@ -10518,6 +10684,11 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
Callback on_exit = CALLBACK_NONE;
dict_T *job_opts = NULL;
const char *cwd = ".";
+ dict_T *env = NULL;
+ const bool pty = true;
+ bool clear_env = false;
+ dictitem_T *job_env = NULL;
+
if (argvars[1].v_type == VAR_DICT) {
job_opts = argvars[1].vval.v_dict;
@@ -10532,18 +10703,31 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+ job_env = tv_dict_find(job_opts, S_LEN("env"));
+ if (job_env && job_env->di_tv.v_type != VAR_DICT) {
+ EMSG2(_(e_invarg2), "env");
+ shell_free_argv(argv);
+ return;
+ }
+
+ clear_env = tv_dict_get_number(job_opts, "clear_env") != 0;
+
if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) {
shell_free_argv(argv);
return;
}
}
+ env = create_environment(job_env, clear_env, pty, "xterm-256color");
+
+ const bool rpc = false;
+ const bool overlapped = false;
+ const bool detach = false;
uint16_t term_width = MAX(0, curwin->w_width_inner - win_col_off(curwin));
Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit,
- true, false, false, false, cwd,
+ pty, rpc, overlapped, detach, cwd,
term_width, curwin->w_height_inner,
- xstrdup("xterm-256color"), NULL,
- &rettv->vval.v_number);
+ env, &rettv->vval.v_number);
if (rettv->vval.v_number <= 0) {
return;
}
@@ -10576,7 +10760,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
INTEGER_OBJ(pid), false, false, &err);
api_clear_error(&err);
- channel_terminal_open(chan);
+ channel_terminal_open(curbuf, chan);
channel_create_event(chan, NULL);
}
@@ -11138,17 +11322,23 @@ static void f_winnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
*/
static void f_winrestcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- int winnr = 1;
garray_T ga;
char_u buf[50];
ga_init(&ga, (int)sizeof(char), 70);
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- sprintf((char *)buf, "%dresize %d|", winnr, wp->w_height);
- ga_concat(&ga, buf);
- sprintf((char *)buf, "vert %dresize %d|", winnr, wp->w_width);
- ga_concat(&ga, buf);
- ++winnr;
+
+ // Do this twice to handle some window layouts properly.
+ for (int i = 0; i < 2; i++) {
+ int winnr = 1;
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ snprintf((char *)buf, sizeof(buf), "%dresize %d|", winnr,
+ wp->w_height);
+ ga_concat(&ga, buf);
+ snprintf((char *)buf, sizeof(buf), "vert %dresize %d|", winnr,
+ wp->w_width);
+ ga_concat(&ga, buf);
+ winnr++;
+ }
}
ga_append(&ga, NUL);
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index 02d32a4f86..71e4edc667 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -219,6 +219,7 @@ list_T *tv_list_alloc(const ptrdiff_t len)
list->lv_used_next = gc_first_list;
gc_first_list = list;
list_log(list, NULL, (void *)(uintptr_t)len, "alloc");
+ list->lua_table_ref = LUA_NOREF;
return list;
}
@@ -302,7 +303,7 @@ void tv_list_free_list(list_T *const l)
}
list_log(l, NULL, NULL, "freelist");
- nlua_free_typval_list(l);
+ NLUA_CLEAR_REF(l->lua_table_ref);
xfree(l);
}
@@ -1109,6 +1110,7 @@ void tv_dict_watcher_add(dict_T *const dict, const char *const key_pattern,
watcher->key_pattern_len = key_pattern_len;
watcher->callback = callback;
watcher->busy = false;
+ watcher->needs_free = false;
QUEUE_INSERT_TAIL(&dict->watchers, &watcher->node);
}
@@ -1182,22 +1184,30 @@ bool tv_dict_watcher_remove(dict_T *const dict, const char *const key_pattern,
QUEUE *w = NULL;
DictWatcher *watcher = NULL;
bool matched = false;
- QUEUE_FOREACH(w, &dict->watchers) {
+ bool queue_is_busy = false;
+ QUEUE_FOREACH(w, &dict->watchers, {
watcher = tv_dict_watcher_node_data(w);
+ if (watcher->busy) {
+ queue_is_busy = true;
+ }
if (tv_callback_equal(&watcher->callback, &callback)
&& watcher->key_pattern_len == key_pattern_len
&& memcmp(watcher->key_pattern, key_pattern, key_pattern_len) == 0) {
matched = true;
break;
}
- }
+ })
if (!matched) {
return false;
}
- QUEUE_REMOVE(w);
- tv_dict_watcher_free(watcher);
+ if (queue_is_busy) {
+ watcher->needs_free = true;
+ } else {
+ QUEUE_REMOVE(w);
+ tv_dict_watcher_free(watcher);
+ }
return true;
}
@@ -1258,9 +1268,10 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key,
typval_T rettv;
+ bool any_needs_free = false;
dict->dv_refcount++;
QUEUE *w;
- QUEUE_FOREACH(w, &dict->watchers) {
+ QUEUE_FOREACH(w, &dict->watchers, {
DictWatcher *watcher = tv_dict_watcher_node_data(w);
if (!watcher->busy && tv_dict_watcher_matches(watcher, key)) {
rettv = TV_INITIAL_VALUE;
@@ -1268,7 +1279,19 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key,
callback_call(&watcher->callback, 3, argv, &rettv);
watcher->busy = false;
tv_clear(&rettv);
+ if (watcher->needs_free) {
+ any_needs_free = true;
+ }
}
+ })
+ if (any_needs_free) {
+ QUEUE_FOREACH(w, &dict->watchers, {
+ DictWatcher *watcher = tv_dict_watcher_node_data(w);
+ if (watcher->needs_free) {
+ QUEUE_REMOVE(w);
+ tv_dict_watcher_free(watcher);
+ }
+ })
}
tv_dict_unref(dict);
@@ -1382,6 +1405,8 @@ dict_T *tv_dict_alloc(void)
d->dv_copyID = 0;
QUEUE_INIT(&d->watchers);
+ d->lua_table_ref = LUA_NOREF;
+
return d;
}
@@ -1432,7 +1457,7 @@ void tv_dict_free_dict(dict_T *const d)
d->dv_used_next->dv_used_prev = d->dv_used_prev;
}
- nlua_free_typval_dict(d);
+ NLUA_CLEAR_REF(d->lua_table_ref);
xfree(d);
}
@@ -1523,6 +1548,33 @@ varnumber_T tv_dict_get_number(const dict_T *const d, const char *const key)
return tv_get_number(&di->di_tv);
}
+/// Converts a dict to an environment
+///
+///
+char **tv_dict_to_env(dict_T *denv)
+{
+ size_t env_size = (size_t)tv_dict_len(denv);
+
+ size_t i = 0;
+ char **env = NULL;
+
+ // + 1 for NULL
+ env = xmalloc((env_size + 1) * sizeof(*env));
+
+ TV_DICT_ITER(denv, var, {
+ const char *str = tv_get_string(&var->di_tv);
+ assert(str);
+ size_t len = STRLEN(var->di_key) + strlen(str) + strlen("=") + 1;
+ env[i] = xmalloc(len);
+ snprintf(env[i], len, "%s=%s", (char *)var->di_key, str);
+ i++;
+ });
+
+ // must be null terminated
+ env[env_size] = NULL;
+ return env;
+}
+
/// Get a string item from a dictionary
///
/// @param[in] d Dictionary to get item from.
@@ -2494,7 +2546,7 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock)
break;
}
case VAR_UNKNOWN: {
- assert(false);
+ abort();
}
}
#undef CHANGE_LOCK
@@ -2666,7 +2718,7 @@ bool tv_equal(typval_T *const tv1, typval_T *const tv2, const bool ic,
}
}
- assert(false);
+ abort();
return false;
}
@@ -2719,7 +2771,7 @@ bool tv_check_str_or_nr(const typval_T *const tv)
return false;
}
}
- assert(false);
+ abort();
return false;
}
@@ -2764,7 +2816,7 @@ bool tv_check_num(const typval_T *const tv)
return false;
}
}
- assert(false);
+ abort();
return false;
}
@@ -2809,7 +2861,7 @@ bool tv_check_str(const typval_T *const tv)
return false;
}
}
- assert(false);
+ abort();
return false;
}
diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h
index d8ede1e3ba..2b4612016b 100644
--- a/src/nvim/eval/typval.h
+++ b/src/nvim/eval/typval.h
@@ -89,6 +89,7 @@ typedef struct dict_watcher {
size_t key_pattern_len;
QUEUE node;
bool busy; // prevent recursion if the dict is changed in the callback
+ bool needs_free;
} DictWatcher;
/// Bool variable values
@@ -315,6 +316,7 @@ struct ufunc {
int uf_calls; ///< nr of active calls
bool uf_cleared; ///< func_clear() was already called
garray_T uf_args; ///< arguments
+ garray_T uf_def_args; ///< default argument expressions
garray_T uf_lines; ///< function lines
int uf_profiling; ///< true when func is being profiled
int uf_prof_initialized;
@@ -340,8 +342,9 @@ struct ufunc {
///< used for s: variables
int uf_refcount; ///< reference count, see func_name_refcount()
funccall_T *uf_scoped; ///< l: local variables for closure
- char_u uf_name[]; ///< Name of function; can start with <SNR>123_
- ///< (<SNR> is K_SPECIAL KS_EXTRA KE_SNR)
+ char_u uf_name[]; ///< Name of function (actual size equals name);
+ ///< can start with <SNR>123_
+ ///< (<SNR> is K_SPECIAL KS_EXTRA KE_SNR)
};
struct partial_S {
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index 70c998ef39..00260bc3f7 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -67,7 +67,7 @@ void func_init(void)
/// Get function arguments.
static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs,
- int *varargs, bool skip)
+ int *varargs, garray_T *default_args, bool skip)
{
bool mustend = false;
char_u *arg = *argp;
@@ -78,12 +78,16 @@ static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs,
if (newargs != NULL) {
ga_init(newargs, (int)sizeof(char_u *), 3);
}
+ if (default_args != NULL) {
+ ga_init(default_args, (int)sizeof(char_u *), 3);
+ }
if (varargs != NULL) {
*varargs = false;
}
// Isolate the arguments: "arg1, arg2, ...)"
+ bool any_default = false;
while (*p != endchar) {
if (p[0] == '.' && p[1] == '.' && p[2] == '.') {
if (varargs != NULL) {
@@ -123,6 +127,38 @@ static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs,
*p = c;
}
+ if (*skipwhite(p) == '=' && default_args != NULL) {
+ typval_T rettv;
+
+ any_default = true;
+ p = skipwhite(p) + 1;
+ p = skipwhite(p);
+ char_u *expr = p;
+ if (eval1(&p, &rettv, false) != FAIL) {
+ ga_grow(default_args, 1);
+
+ // trim trailing whitespace
+ while (p > expr && ascii_iswhite(p[-1])) {
+ p--;
+ }
+ c = *p;
+ *p = NUL;
+ expr = vim_strsave(expr);
+ if (expr == NULL) {
+ *p = c;
+ goto err_ret;
+ }
+ ((char_u **)(default_args->ga_data))
+ [default_args->ga_len] = expr;
+ default_args->ga_len++;
+ *p = c;
+ } else {
+ mustend = true;
+ }
+ } else if (any_default) {
+ EMSG(_("E989: Non-default argument follows default argument"));
+ mustend = true;
+ }
if (*p == ',') {
p++;
} else {
@@ -149,6 +185,9 @@ err_ret:
if (newargs != NULL) {
ga_clear_strings(newargs);
}
+ if (default_args != NULL) {
+ ga_clear_strings(default_args);
+ }
return FAIL;
}
@@ -195,7 +234,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
bool eval_lavars = false;
// First, check if this is a lambda expression. "->" must exists.
- ret = get_function_args(&start, '-', NULL, NULL, true);
+ ret = get_function_args(&start, '-', NULL, NULL, NULL, true);
if (ret == FAIL || *start != '>') {
return NOTDONE;
}
@@ -207,7 +246,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
pnewargs = NULL;
}
*arg = skipwhite(*arg + 1);
- ret = get_function_args(arg, '-', pnewargs, &varargs, false);
+ ret = get_function_args(arg, '-', pnewargs, &varargs, NULL, false);
if (ret == FAIL || **arg != '>') {
goto errret;
}
@@ -259,6 +298,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
STRCPY(fp->uf_name, name);
hash_add(&func_hashtab, UF2HIKEY(fp));
fp->uf_args = newargs;
+ ga_init(&fp->uf_def_args, (int)sizeof(char_u *), 1);
fp->uf_lines = newlines;
if (current_funccal != NULL && eval_lavars) {
flags |= FC_CLOSURE;
@@ -715,6 +755,7 @@ static bool func_remove(ufunc_T *fp)
static void func_clear_items(ufunc_T *fp)
{
ga_clear_strings(&(fp->uf_args));
+ ga_clear_strings(&(fp->uf_def_args));
ga_clear_strings(&(fp->uf_lines));
if (fp->uf_cb_free != NULL) {
@@ -792,6 +833,8 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
bool islambda = false;
char_u numbuf[NUMBUFLEN];
char_u *name;
+ typval_T *tv_to_free[MAX_FUNC_ARGS];
+ int tv_to_free_len = 0;
proftime_T wait_start;
proftime_T call_start;
int started_profiling = false;
@@ -857,12 +900,13 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
}
// Init a: variables, unless none found (in lambda).
- // Set a:0 to "argcount".
+ // Set a:0 to "argcount" less number of named arguments, if >= 0.
// Set a:000 to a list with room for the "..." arguments.
init_var_dict(&fc->l_avars, &fc->l_avars_var, VAR_SCOPE);
if ((fp->uf_flags & FC_NOARGS) == 0) {
add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], "0",
- (varnumber_T)(argcount - fp->uf_args.ga_len));
+ (varnumber_T)(argcount >= fp->uf_args.ga_len
+ ? argcount - fp->uf_args.ga_len : 0));
}
fc->l_avars.dv_lock = VAR_FIXED;
if ((fp->uf_flags & FC_NOARGS) == 0) {
@@ -892,8 +936,11 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++],
"lastline", (varnumber_T)lastline);
}
- for (int i = 0; i < argcount; i++) {
+ bool default_arg_err = false;
+ for (int i = 0; i < argcount || i < fp->uf_args.ga_len; i++) {
bool addlocal = false;
+ bool isdefault = false;
+ typval_T def_rettv;
ai = i - fp->uf_args.ga_len;
if (ai < 0) {
@@ -902,6 +949,21 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
if (islambda) {
addlocal = true;
}
+
+ // evaluate named argument default expression
+ isdefault = ai + fp->uf_def_args.ga_len >= 0 && i >= argcount;
+ if (isdefault) {
+ char_u *default_expr = NULL;
+ def_rettv.v_type = VAR_NUMBER;
+ def_rettv.vval.v_number = -1;
+
+ default_expr = ((char_u **)(fp->uf_def_args.ga_data))
+ [ai + fp->uf_def_args.ga_len];
+ if (eval1(&default_expr, &def_rettv, true) == FAIL) {
+ default_arg_err = true;
+ break;
+ }
+ }
} else {
if ((fp->uf_flags & FC_NOARGS) != 0) {
// Bail out if no a: arguments used (in lambda).
@@ -922,9 +984,14 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
// Note: the values are copied directly to avoid alloc/free.
// "argvars" must have VAR_FIXED for v_lock.
- v->di_tv = argvars[i];
+ v->di_tv = isdefault ? def_rettv : argvars[i];
v->di_tv.v_lock = VAR_FIXED;
+ if (isdefault) {
+ // Need to free this later, no matter where it's stored.
+ tv_to_free[tv_to_free_len++] = &v->di_tv;
+ }
+
if (addlocal) {
// Named arguments can be accessed without the "a:" prefix in lambda
// expressions. Add to the l: dict.
@@ -1046,7 +1113,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
save_did_emsg = did_emsg;
did_emsg = FALSE;
- if (islambda) {
+ if (default_arg_err && (fp->uf_flags & FC_ABORT)) {
+ did_emsg = true;
+ } else if (islambda) {
char_u *p = *(char_u **)fp->uf_lines.ga_data + 7;
// A Lambda always has the command "return {expr}". It is much faster
@@ -1147,7 +1216,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
did_emsg |= save_did_emsg;
depth--;
-
+ for (int i = 0; i < tv_to_free_len; i++) {
+ tv_clear(tv_to_free[i]);
+ }
cleanup_function_call(fc);
if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) {
@@ -1490,7 +1561,7 @@ call_func(
if (fp->uf_flags & FC_RANGE) {
*doesrange = true;
}
- if (argcount < fp->uf_args.ga_len) {
+ if (argcount < fp->uf_args.ga_len - fp->uf_def_args.ga_len) {
error = ERROR_TOOFEW;
} else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) {
error = ERROR_TOOMANY;
@@ -1573,6 +1644,11 @@ static void list_func_head(ufunc_T *fp, int indent, bool force)
msg_puts(", ");
}
msg_puts((const char *)FUNCARG(fp, j));
+ if (j >= fp->uf_args.ga_len - fp->uf_def_args.ga_len) {
+ msg_puts(" = ");
+ msg_puts(((char **)(fp->uf_def_args.ga_data))
+ [j - fp->uf_args.ga_len + fp->uf_def_args.ga_len]);
+ }
}
if (fp->uf_varargs) {
if (j) {
@@ -1836,6 +1912,7 @@ void ex_function(exarg_T *eap)
char_u *arg;
char_u *line_arg = NULL;
garray_T newargs;
+ garray_T default_args;
garray_T newlines;
int varargs = false;
int flags = 0;
@@ -2039,7 +2116,8 @@ void ex_function(exarg_T *eap)
}
}
- if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL) {
+ if (get_function_args(&p, ')', &newargs, &varargs,
+ &default_args, eap->skip) == FAIL) {
goto errret_2;
}
@@ -2458,6 +2536,7 @@ void ex_function(exarg_T *eap)
fp->uf_refcount = 1;
}
fp->uf_args = newargs;
+ fp->uf_def_args = default_args;
fp->uf_lines = newlines;
if ((flags & FC_CLOSURE) != 0) {
register_closure(fp);
@@ -2480,6 +2559,7 @@ void ex_function(exarg_T *eap)
erret:
ga_clear_strings(&newargs);
+ ga_clear_strings(&default_args);
errret_2:
ga_clear_strings(&newlines);
ret_free:
diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c
index 13517d3df1..c02f730431 100644
--- a/src/nvim/event/libuv_process.c
+++ b/src/nvim/event/libuv_process.c
@@ -41,7 +41,6 @@ int libuv_process_spawn(LibuvProcess *uvproc)
#endif
uvproc->uvopts.exit_cb = exit_cb;
uvproc->uvopts.cwd = proc->cwd;
- uvproc->uvopts.env = proc->env;
uvproc->uvopts.stdio = uvproc->uvstdio;
uvproc->uvopts.stdio_count = 3;
uvproc->uvstdio[0].flags = UV_IGNORE;
@@ -49,6 +48,12 @@ int libuv_process_spawn(LibuvProcess *uvproc)
uvproc->uvstdio[2].flags = UV_IGNORE;
uvproc->uv.data = proc;
+ if (proc->env) {
+ uvproc->uvopts.env = tv_dict_to_env(proc->env);
+ } else {
+ uvproc->uvopts.env = NULL;
+ }
+
if (!proc->in.closed) {
uvproc->uvstdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE;
#ifdef WIN32
@@ -77,7 +82,10 @@ int libuv_process_spawn(LibuvProcess *uvproc)
int status;
if ((status = uv_spawn(&proc->loop->uv, &uvproc->uv, &uvproc->uvopts))) {
- ELOG("uv_spawn failed: %s", uv_strerror(status));
+ ELOG("uv_spawn(%s) failed: %s", uvproc->uvopts.file, uv_strerror(status));
+ if (uvproc->uvopts.env) {
+ os_free_fullenv(uvproc->uvopts.env);
+ }
return status;
}
@@ -97,6 +105,10 @@ static void close_cb(uv_handle_t *handle)
if (proc->internal_close_cb) {
proc->internal_close_cb(proc);
}
+ LibuvProcess *uvproc = (LibuvProcess *)proc;
+ if (uvproc->uvopts.env) {
+ os_free_fullenv(uvproc->uvopts.env);
+ }
}
static void exit_cb(uv_process_t *handle, int64_t status, int term_signal)
diff --git a/src/nvim/event/multiqueue.c b/src/nvim/event/multiqueue.c
index c9aa3acc4d..1e6d62135c 100644
--- a/src/nvim/event/multiqueue.c
+++ b/src/nvim/event/multiqueue.c
@@ -119,8 +119,8 @@ static MultiQueue *multiqueue_new(MultiQueue *parent, put_callback put_cb,
void multiqueue_free(MultiQueue *this)
{
assert(this);
- while (!QUEUE_EMPTY(&this->headtail)) {
- QUEUE *q = QUEUE_HEAD(&this->headtail);
+ QUEUE *q;
+ QUEUE_FOREACH(q, &this->headtail, {
MultiQueueItem *item = multiqueue_node_data(q);
if (this->parent) {
QUEUE_REMOVE(&item->data.item.parent_item->node);
@@ -128,7 +128,7 @@ void multiqueue_free(MultiQueue *this)
}
QUEUE_REMOVE(q);
xfree(item);
- }
+ })
xfree(this);
}
diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c
index 8e9964bd37..b93d6cc0ab 100644
--- a/src/nvim/event/process.c
+++ b/src/nvim/event/process.c
@@ -270,9 +270,6 @@ static void process_close_event(void **argv)
{
Process *proc = argv[0];
shell_free_argv(proc->argv);
- if (proc->type == kProcessTypePty) {
- xfree(((PtyProcess *)proc)->term_name);
- }
if (proc->cb) { // "on_exit" for jobstart(). See channel_job_start().
proc->cb(proc, proc->status, proc->data);
}
diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h
index 84e81238e9..24debdb276 100644
--- a/src/nvim/event/process.h
+++ b/src/nvim/event/process.h
@@ -4,6 +4,7 @@
#include "nvim/event/loop.h"
#include "nvim/event/rstream.h"
#include "nvim/event/wstream.h"
+#include "nvim/eval/typval.h"
typedef enum {
kProcessTypeUv,
@@ -23,7 +24,7 @@ struct process {
uint64_t stopped_time; // process_stop() timestamp
const char *cwd;
char **argv;
- char **env;
+ dict_T *env;
Stream in, out, err;
process_exit_cb cb;
internal_process_cb internal_exit_cb, internal_close_cb;
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index a2487336f1..3e330b88a2 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -712,14 +712,15 @@ void ex_retab(exarg_T *eap)
long len;
long col;
long vcol;
- long start_col = 0; /* For start of white-space string */
- long start_vcol = 0; /* For start of white-space string */
- int temp;
+ long start_col = 0; // For start of white-space string
+ long start_vcol = 0; // For start of white-space string
long old_len;
char_u *ptr;
- char_u *new_line = (char_u *)1; /* init to non-NULL */
- int did_undo; /* called u_save for current line */
- int new_ts;
+ char_u *new_line = (char_u *)1; // init to non-NULL
+ int did_undo; // called u_save for current line
+ long *new_vts_array = NULL;
+ char_u *new_ts_str; // string value of tab argument
+
int save_list;
linenr_T first_line = 0; /* first changed line */
linenr_T last_line = 0; /* last changed line */
@@ -727,14 +728,24 @@ void ex_retab(exarg_T *eap)
save_list = curwin->w_p_list;
curwin->w_p_list = 0; /* don't want list mode here */
- new_ts = getdigits_int(&(eap->arg), false, -1);
- if (new_ts < 0) {
- EMSG(_(e_positive));
+ new_ts_str = eap->arg;
+ if (!tabstop_set(eap->arg, &new_vts_array)) {
return;
}
- if (new_ts == 0)
- new_ts = curbuf->b_p_ts;
- for (lnum = eap->line1; !got_int && lnum <= eap->line2; ++lnum) {
+ while (ascii_isdigit(*(eap->arg)) || *(eap->arg) == ',') {
+ (eap->arg)++;
+ }
+
+ // This ensures that either new_vts_array and new_ts_str are freshly
+ // allocated, or new_vts_array points to an existing array and new_ts_str
+ // is null.
+ if (new_vts_array == NULL) {
+ new_vts_array = curbuf->b_p_vts_array;
+ new_ts_str = NULL;
+ } else {
+ new_ts_str = vim_strnsave(new_ts_str, eap->arg - new_ts_str);
+ }
+ for (lnum = eap->line1; !got_int && lnum <= eap->line2; lnum++) {
ptr = ml_get(lnum);
col = 0;
vcol = 0;
@@ -758,13 +769,12 @@ void ex_retab(exarg_T *eap)
len = num_spaces = vcol - start_vcol;
num_tabs = 0;
if (!curbuf->b_p_et) {
- temp = new_ts - (start_vcol % new_ts);
- if (num_spaces >= temp) {
- num_spaces -= temp;
- num_tabs++;
- }
- num_tabs += num_spaces / new_ts;
- num_spaces -= (num_spaces / new_ts) * new_ts;
+ int t, s;
+
+ tabstop_fromto(start_vcol, vcol,
+ curbuf->b_p_ts, new_vts_array, &t, &s);
+ num_tabs = t;
+ num_spaces = s;
}
if (curbuf->b_p_et || got_tab
|| (num_spaces + num_tabs < len)) {
@@ -780,7 +790,8 @@ void ex_retab(exarg_T *eap)
/* len is actual number of white characters used */
len = num_spaces + num_tabs;
old_len = (long)STRLEN(ptr);
- new_line = xmalloc(old_len - col + start_col + len + 1);
+ long new_len = old_len - col + start_col + len + 1;
+ new_line = xmalloc(new_len);
if (start_col > 0)
memmove(new_line, ptr, (size_t)start_col);
@@ -790,7 +801,12 @@ void ex_retab(exarg_T *eap)
for (col = 0; col < len; col++) {
ptr[col] = (col < num_tabs) ? '\t' : ' ';
}
- ml_replace(lnum, new_line, false);
+ if (ml_replace(lnum, new_line, false) == OK) {
+ // "new_line" may have been copied
+ new_line = curbuf->b_ml.ml_line_ptr;
+ extmark_splice_cols(curbuf, lnum - 1, 0, (colnr_T)old_len,
+ (colnr_T)new_len - 1, kExtmarkUndo);
+ }
if (first_line == 0) {
first_line = lnum;
}
@@ -814,15 +830,42 @@ void ex_retab(exarg_T *eap)
if (got_int)
EMSG(_(e_interr));
- if (curbuf->b_p_ts != new_ts)
+ // If a single value was given then it can be considered equal to
+ // either the value of 'tabstop' or the value of 'vartabstop'.
+ if (tabstop_count(curbuf->b_p_vts_array) == 0
+ && tabstop_count(new_vts_array) == 1
+ && curbuf->b_p_ts == tabstop_first(new_vts_array)) {
+ // not changed
+ } else if (tabstop_count(curbuf->b_p_vts_array) > 0
+ && tabstop_eq(curbuf->b_p_vts_array, new_vts_array)) {
+ // not changed
+ } else {
redraw_curbuf_later(NOT_VALID);
+ }
if (first_line != 0) {
changed_lines(first_line, 0, last_line + 1, 0L, true);
}
curwin->w_p_list = save_list; /* restore 'list' */
- curbuf->b_p_ts = new_ts;
+ if (new_ts_str != NULL) { // set the new tabstop
+ // If 'vartabstop' is in use or if the value given to retab has more
+ // than one tabstop then update 'vartabstop'.
+ long *old_vts_ary = curbuf->b_p_vts_array;
+
+ if (tabstop_count(old_vts_ary) > 0 || tabstop_count(new_vts_array) > 1) {
+ set_string_option_direct("vts", -1, new_ts_str,
+ OPT_FREE | OPT_LOCAL, 0);
+ curbuf->b_p_vts_array = new_vts_array;
+ xfree(old_vts_ary);
+ } else {
+ // 'vartabstop' wasn't in use and a single value was given to
+ // retab then update 'tabstop'.
+ curbuf->b_p_ts = tabstop_first(new_vts_array);
+ xfree(new_vts_array);
+ }
+ xfree(new_ts_str);
+ }
coladvance(curwin->w_curswant);
u_clearline();
@@ -925,12 +968,6 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
mark_adjust_nofold(last_line - num_lines + 1, last_line,
-(last_line - dest - extra), 0L, kExtmarkNOOP);
- // extmarks are handled separately
- extmark_move_region(curbuf, line1-1, 0, start_byte,
- line2-line1+1, 0, extent_byte,
- dest+line_off, 0, dest_byte+byte_off,
- kExtmarkUndo);
-
changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra, false);
// send update regarding the new lines that were added
@@ -952,6 +989,11 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
smsg(_("%" PRId64 " lines moved"), (int64_t)num_lines);
}
+ extmark_move_region(curbuf, line1-1, 0, start_byte,
+ line2-line1+1, 0, extent_byte,
+ dest+line_off, 0, dest_byte+byte_off,
+ kExtmarkUndo);
+
/*
* Leave the cursor on the last of the moved lines.
*/
@@ -1404,19 +1446,20 @@ do_shell(
* For autocommands we want to get the output on the current screen, to
* avoid having to type return below.
*/
- msg_putchar('\r'); /* put cursor at start of line */
- msg_putchar('\n'); /* may shift screen one line up */
+ msg_putchar('\r'); // put cursor at start of line
+ msg_putchar('\n'); // may shift screen one line up
- /* warning message before calling the shell */
+ // warning message before calling the shell
if (p_warn
&& !autocmd_busy
- && msg_silent == 0)
+ && msg_silent == 0) {
FOR_ALL_BUFFERS(buf) {
if (bufIsChanged(buf)) {
MSG_PUTS(_("[No write since last change]\n"));
break;
}
}
+ }
// This ui_cursor_goto is required for when the '\n' resulted in a "delete line
// 1" command to the terminal.
@@ -2178,6 +2221,8 @@ theend:
/// ECMD_OLDBUF: use existing buffer if it exists
/// ECMD_FORCEIT: ! used for Ex command
/// ECMD_ADDBUF: don't edit, just add to buffer list
+/// ECMD_ALTBUF: like ECMD_ADDBUF and also set the alternate
+/// file
/// @param oldwin Should be "curwin" when editing a new buffer in the current
/// window, NULL when splitting the window first. When not NULL
/// info of the previous buffer for "oldwin" is stored.
@@ -2234,8 +2279,10 @@ int do_ecmd(
path_fix_case(sfname); // set correct case for sfname
#endif
- if ((flags & ECMD_ADDBUF) && (ffname == NULL || *ffname == NUL))
+ if ((flags & (ECMD_ADDBUF | ECMD_ALTBUF))
+ && (ffname == NULL || *ffname == NUL)) {
goto theend;
+ }
if (ffname == NULL)
other_file = TRUE;
@@ -2265,15 +2312,16 @@ int do_ecmd(
// If the file was changed we may not be allowed to abandon it:
// - if we are going to re-edit the same file
// - or if we are the only window on this file and if ECMD_HIDE is FALSE
- if ( ((!other_file && !(flags & ECMD_OLDBUF))
- || (curbuf->b_nwindows == 1
- && !(flags & (ECMD_HIDE | ECMD_ADDBUF))))
- && check_changed(curbuf, (p_awa ? CCGD_AW : 0)
- | (other_file ? 0 : CCGD_MULTWIN)
- | ((flags & ECMD_FORCEIT) ? CCGD_FORCEIT : 0)
- | (eap == NULL ? 0 : CCGD_EXCMD))) {
- if (fnum == 0 && other_file && ffname != NULL)
+ if (((!other_file && !(flags & ECMD_OLDBUF))
+ || (curbuf->b_nwindows == 1
+ && !(flags & (ECMD_HIDE | ECMD_ADDBUF | ECMD_ALTBUF))))
+ && check_changed(curbuf, (p_awa ? CCGD_AW : 0)
+ | (other_file ? 0 : CCGD_MULTWIN)
+ | ((flags & ECMD_FORCEIT) ? CCGD_FORCEIT : 0)
+ | (eap == NULL ? 0 : CCGD_EXCMD))) {
+ if (fnum == 0 && other_file && ffname != NULL) {
(void)setaltfname(ffname, sfname, newlnum < 0 ? 0 : newlnum);
+ }
goto theend;
}
@@ -2303,25 +2351,35 @@ int do_ecmd(
* Otherwise we re-use the current buffer.
*/
if (other_file) {
- if (!(flags & ECMD_ADDBUF)) {
- if (!cmdmod.keepalt)
+ if (!(flags & (ECMD_ADDBUF | ECMD_ALTBUF))) {
+ if (!cmdmod.keepalt) {
curwin->w_alt_fnum = curbuf->b_fnum;
- if (oldwin != NULL)
+ }
+ if (oldwin != NULL) {
buflist_altfpos(oldwin);
+ }
}
if (fnum) {
buf = buflist_findnr(fnum);
} else {
- if (flags & ECMD_ADDBUF) {
- linenr_T tlnum = 1L;
+ if (flags & (ECMD_ADDBUF | ECMD_ALTBUF)) {
+ // Default the line number to zero to avoid that a wininfo item
+ // is added for the current window.
+ linenr_T tlnum = 0;
if (command != NULL) {
tlnum = atol((char *)command);
if (tlnum <= 0)
tlnum = 1L;
}
- (void)buflist_new(ffname, sfname, tlnum, BLN_LISTED);
+ // Add BLN_NOCURWIN to avoid a new wininfo items are associated
+ // with the current window.
+ const buf_T *const newbuf
+ = buflist_new(ffname, sfname, tlnum, BLN_LISTED | BLN_NOCURWIN);
+ if (newbuf != NULL && (flags & ECMD_ALTBUF)) {
+ curwin->w_alt_fnum = newbuf->b_fnum;
+ }
goto theend;
}
buf = buflist_new(ffname, sfname, 0L,
@@ -2413,7 +2471,10 @@ int do_ecmd(
(flags & ECMD_HIDE) || curbuf->terminal ? 0 : DOBUF_UNLOAD,
false);
- the_curwin->w_closing = false;
+ // Autocommands may have closed the window.
+ if (win_valid(the_curwin)) {
+ the_curwin->w_closing = false;
+ }
buf->b_locked--;
// autocmds may abort script processing
@@ -2464,8 +2525,7 @@ int do_ecmd(
curwin->w_pcmark.lnum = 1;
curwin->w_pcmark.col = 0;
} else { // !other_file
- if ((flags & ECMD_ADDBUF)
- || check_fname() == FAIL) {
+ if ((flags & (ECMD_ADDBUF | ECMD_ALTBUF)) || check_fname() == FAIL) {
goto theend;
}
oldbuf = (flags & ECMD_OLDBUF);
@@ -2531,13 +2591,13 @@ int do_ecmd(
goto theend;
}
u_unchanged(curbuf);
- buf_updates_unregister_all(curbuf);
+ buf_updates_unload(curbuf, false);
buf_freeall(curbuf, BFA_KEEP_UNDO);
// Tell readfile() not to clear or reload undo info.
readfile_flags = READ_KEEP_UNDO;
} else {
- buf_updates_unregister_all(curbuf);
+ buf_updates_unload(curbuf, false);
buf_freeall(curbuf, 0); // Free all things for buffer.
}
// If autocommands deleted the buffer we were going to re-edit, give
@@ -3126,6 +3186,9 @@ static bool sub_joining_lines(exarg_T *eap, char_u *pat, char_u *sub,
|| *cmd == 'l'
|| *cmd == 'p'
|| *cmd == '#')))) {
+ if (eap->skip) {
+ return true;
+ }
curwin->w_cursor.lnum = eap->line1;
if (*cmd == 'l') {
eap->flags = EXFLAG_LIST;
@@ -3307,11 +3370,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
int save_b_changed = curbuf->b_changed;
bool preview = (State & CMDPREVIEW);
- // inccommand tests fail without this check
- if (!preview) {
- // Required for Undo to work for extmarks.
- u_save_cursor();
- }
+ bool did_save = false;
if (!global_busy) {
sub_nsubs = 0;
@@ -3988,6 +4047,11 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
int matchcols = end.col - ((end.lnum == start.lnum)
? start.col : 0);
int subcols = new_endcol - ((lnum == lnum_start) ? start_col : 0);
+ if (!did_save) {
+ // Required for Undo to work for extmarks.
+ u_save_cursor();
+ did_save = true;
+ }
extmark_splice(curbuf, lnum_start-1, start_col,
end.lnum-start.lnum, matchcols, replaced_bytes,
lnum-lnum_start, subcols, sublen-1, kExtmarkUndo);
@@ -4174,7 +4238,7 @@ skip:
// when interactive leave cursor on the match
if (!subflags.do_ask) {
if (endcolumn) {
- coladvance((colnr_T)MAXCOL);
+ coladvance(MAXCOL);
} else {
beginline(BL_WHITE | BL_FIX);
}
@@ -4217,7 +4281,7 @@ skip:
size_t subsize = preview_lines.subresults.size;
if (preview && !aborting()) {
if (got_quit || profile_passed_limit(timeout)) { // Too slow, disable.
- set_string_option_direct((char_u *)"icm", -1, (char_u *)"", OPT_FREE,
+ set_string_option_direct("icm", -1, (char_u *)"", OPT_FREE,
SID_NONE);
} else if (*p_icm != NUL && pat != NULL) {
if (pre_src_id == 0) {
@@ -4518,7 +4582,7 @@ prepare_tagpreview (
RESET_BINDING(curwin); /* don't take over 'scrollbind'
and 'cursorbind' */
curwin->w_p_diff = false; // no 'diff'
- set_string_option_direct((char_u *)"fdc", -1, // no 'foldcolumn'
+ set_string_option_direct("fdc", -1, // no 'foldcolumn'
(char_u *)"0", OPT_FREE, SID_NONE);
return true;
}
@@ -4996,7 +5060,7 @@ int find_help_tags(const char_u *arg, int *num_matches, char_u ***matches,
if (keep_lang) {
flags |= TAG_KEEP_LANG;
}
- if (find_tags(IObuff, num_matches, matches, flags, (int)MAXCOL, NULL) == OK
+ if (find_tags(IObuff, num_matches, matches, flags, MAXCOL, NULL) == OK
&& *num_matches > 0) {
/* Sort the matches found on the heuristic number that is after the
* tag name. */
@@ -5013,7 +5077,7 @@ int find_help_tags(const char_u *arg, int *num_matches, char_u ***matches,
static void prepare_help_buffer(void)
{
curbuf->b_help = true;
- set_string_option_direct((char_u *)"buftype", -1, (char_u *)"help",
+ set_string_option_direct("buftype", -1, (char_u *)"help",
OPT_FREE|OPT_LOCAL, 0);
// Always set these options after jumping to a help tag, because the
@@ -5023,13 +5087,13 @@ static void prepare_help_buffer(void)
// Only set it when needed, buf_init_chartab() is some work.
char_u *p = (char_u *)"!-~,^*,^|,^\",192-255";
if (STRCMP(curbuf->b_p_isk, p) != 0) {
- set_string_option_direct((char_u *)"isk", -1, p, OPT_FREE|OPT_LOCAL, 0);
+ set_string_option_direct("isk", -1, p, OPT_FREE|OPT_LOCAL, 0);
check_buf_options(curbuf);
(void)buf_init_chartab(curbuf, FALSE);
}
// Don't use the global foldmethod.
- set_string_option_direct((char_u *)"fdm", -1, (char_u *)"manual",
+ set_string_option_direct("fdm", -1, (char_u *)"manual",
OPT_FREE|OPT_LOCAL, 0);
curbuf->b_p_ts = 8; // 'tabstop' is 8.
@@ -5649,7 +5713,7 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr,
cmdmod.tab = 0; // disable :tab modifier
cmdmod.noswapfile = true; // disable swap for preview buffer
// disable file info message
- set_string_option_direct((char_u *)"shm", -1, (char_u *)"F", OPT_FREE,
+ set_string_option_direct("shm", -1, (char_u *)"F", OPT_FREE,
SID_NONE);
bool outside_curline = (eap->line1 != old_cusr.lnum
@@ -5772,7 +5836,7 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr,
update_screen(SOME_VALID);
RedrawingDisabled = save_rd;
- set_string_option_direct((char_u *)"shm", -1, save_shm_p, OPT_FREE, SID_NONE);
+ set_string_option_direct("shm", -1, save_shm_p, OPT_FREE, SID_NONE);
xfree(save_shm_p);
cmdmod = save_cmdmod;
diff --git a/src/nvim/ex_cmds.h b/src/nvim/ex_cmds.h
index b564cde56c..1b54b3a898 100644
--- a/src/nvim/ex_cmds.h
+++ b/src/nvim/ex_cmds.h
@@ -16,7 +16,7 @@
#define ECMD_OLDBUF 0x04 // use existing buffer if it exists
#define ECMD_FORCEIT 0x08 // ! used in Ex command
#define ECMD_ADDBUF 0x10 // don't edit, just add to buffer list
-
+#define ECMD_ALTBUF 0x20 // like ECMD_ADDBUF and set the alternate file
/* for lnum argument in do_ecmd() */
#define ECMD_LASTL (linenr_T)0 /* use last position in loaded file */
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index e9046da800..d99383303b 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -176,6 +176,12 @@ module.cmds = {
func='ex_edit',
},
{
+ command='balt',
+ flags=bit.bor(NEEDARG, FILE1, CMDARG, TRLBAR, CMDWIN),
+ addr_type='ADDR_NONE',
+ func='ex_edit',
+ },
+ {
command='bdelete',
flags=bit.bor(BANG, RANGE, BUFNAME, COUNT, EXTRA, TRLBAR),
addr_type='ADDR_BUFFERS',
@@ -922,6 +928,12 @@ module.cmds = {
func='ex_edit',
},
{
+ command='eval',
+ flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN),
+ addr_type='ADDR_NONE',
+ func='ex_eval',
+ },
+ {
command='ex',
flags=bit.bor(BANG, FILE1, CMDARG, ARGOPT, TRLBAR),
addr_type='ADDR_NONE',
@@ -2515,8 +2527,8 @@ module.cmds = {
},
{
command='source',
- flags=bit.bor(BANG, FILE1, TRLBAR, SBOXOK, CMDWIN),
- addr_type='ADDR_NONE',
+ flags=bit.bor(RANGE, DFLALL, WHOLEFOLD, BANG, FILE1, TRLBAR, SBOXOK, CMDWIN),
+ addr_type='ADDR_LINES',
func='ex_source',
},
{
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index c400975108..950a1a436f 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -29,6 +29,7 @@
#include "nvim/getchar.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
+#include "nvim/memline.h"
#include "nvim/message.h"
#include "nvim/misc1.h"
#include "nvim/garray.h"
@@ -119,6 +120,9 @@ struct source_cookie {
/// batch mode debugging: don't save and restore typeahead.
static bool debug_greedy = false;
+static char *debug_oldval = NULL; // old and newval for debug expressions
+static char *debug_newval = NULL;
+
/// Debug mode. Repeatedly get Ex commands, until told to continue normal
/// execution.
void do_debug(char_u *cmd)
@@ -165,6 +169,16 @@ void do_debug(char_u *cmd)
if (!debug_did_msg) {
MSG(_("Entering Debug mode. Type \"cont\" to continue."));
}
+ if (debug_oldval != NULL) {
+ smsg(_("Oldval = \"%s\""), debug_oldval);
+ xfree(debug_oldval);
+ debug_oldval = NULL;
+ }
+ if (debug_newval != NULL) {
+ smsg(_("Newval = \"%s\""), debug_newval);
+ xfree(debug_newval);
+ debug_newval = NULL;
+ }
if (sourcing_name != NULL) {
msg(sourcing_name);
}
@@ -173,7 +187,6 @@ void do_debug(char_u *cmd)
} else {
smsg(_("cmd: %s"), cmd);
}
-
// Repeat getting a command and executing it.
for (;; ) {
msg_scroll = true;
@@ -513,11 +526,13 @@ bool dbg_check_skipped(exarg_T *eap)
/// This is a grow-array of structs.
struct debuggy {
int dbg_nr; ///< breakpoint number
- int dbg_type; ///< DBG_FUNC or DBG_FILE
- char_u *dbg_name; ///< function or file name
+ int dbg_type; ///< DBG_FUNC or DBG_FILE or DBG_EXPR
+ char_u *dbg_name; ///< function, expression or file name
regprog_T *dbg_prog; ///< regexp program
linenr_T dbg_lnum; ///< line number in function or file
int dbg_forceit; ///< ! used
+ typval_T *dbg_val; ///< last result of watchexpression
+ int dbg_level; ///< stored nested level for expr
};
static garray_T dbg_breakp = { 0, 0, sizeof(struct debuggy), 4, NULL };
@@ -529,6 +544,7 @@ static int last_breakp = 0; // nr of last defined breakpoint
static garray_T prof_ga = { 0, 0, sizeof(struct debuggy), 4, NULL };
#define DBG_FUNC 1
#define DBG_FILE 2
+#define DBG_EXPR 3
/// Parse the arguments of ":profile", ":breakadd" or ":breakdel" and put them
@@ -561,6 +577,8 @@ static int dbg_parsearg(char_u *arg, garray_T *gap)
}
bp->dbg_type = DBG_FILE;
here = true;
+ } else if (gap != &prof_ga && STRNCMP(p, "expr", 4) == 0) {
+ bp->dbg_type = DBG_EXPR;
} else {
EMSG2(_(e_invarg2), p);
return FAIL;
@@ -589,6 +607,9 @@ static int dbg_parsearg(char_u *arg, garray_T *gap)
bp->dbg_name = vim_strsave(p);
} else if (here) {
bp->dbg_name = vim_strsave(curbuf->b_ffname);
+ } else if (bp->dbg_type == DBG_EXPR) {
+ bp->dbg_name = vim_strsave(p);
+ bp->dbg_val = eval_expr(bp->dbg_name);
} else {
// Expand the file name in the same way as do_source(). This means
// doing it twice, so that $DIR/file gets expanded when $DIR is
@@ -620,7 +641,6 @@ static int dbg_parsearg(char_u *arg, garray_T *gap)
void ex_breakadd(exarg_T *eap)
{
struct debuggy *bp;
- char_u *pat;
garray_T *gap;
gap = &dbg_breakp;
@@ -632,22 +652,28 @@ void ex_breakadd(exarg_T *eap)
bp = &DEBUGGY(gap, gap->ga_len);
bp->dbg_forceit = eap->forceit;
- pat = file_pat_to_reg_pat(bp->dbg_name, NULL, NULL, false);
- if (pat != NULL) {
- bp->dbg_prog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
- xfree(pat);
- }
- if (pat == NULL || bp->dbg_prog == NULL) {
- xfree(bp->dbg_name);
- } else {
- if (bp->dbg_lnum == 0) { // default line number is 1
- bp->dbg_lnum = 1;
+ if (bp->dbg_type != DBG_EXPR) {
+ char_u *pat = file_pat_to_reg_pat(bp->dbg_name, NULL, NULL, false);
+ if (pat != NULL) {
+ bp->dbg_prog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
+ xfree(pat);
}
- if (eap->cmdidx != CMD_profile) {
- DEBUGGY(gap, gap->ga_len).dbg_nr = ++last_breakp;
- debug_tick++;
+ if (pat == NULL || bp->dbg_prog == NULL) {
+ xfree(bp->dbg_name);
+ } else {
+ if (bp->dbg_lnum == 0) { // default line number is 1
+ bp->dbg_lnum = 1;
+ }
+ if (eap->cmdidx != CMD_profile) {
+ DEBUGGY(gap, gap->ga_len).dbg_nr = ++last_breakp;
+ debug_tick++;
+ }
+ gap->ga_len++;
}
- gap->ga_len++;
+ } else {
+ // DBG_EXPR
+ DEBUGGY(gap, gap->ga_len++).dbg_nr = ++last_breakp;
+ debug_tick++;
}
}
}
@@ -690,7 +716,7 @@ void ex_breakdel(exarg_T *eap)
todel = 0;
del_all = true;
} else {
- // ":breakdel {func|file} [lnum] {name}"
+ // ":breakdel {func|file|expr} [lnum] {name}"
if (dbg_parsearg(eap->arg, gap) == FAIL) {
return;
}
@@ -715,6 +741,10 @@ void ex_breakdel(exarg_T *eap)
} else {
while (!GA_EMPTY(gap)) {
xfree(DEBUGGY(gap, todel).dbg_name);
+ if (DEBUGGY(gap, todel).dbg_type == DBG_EXPR
+ && DEBUGGY(gap, todel).dbg_val != NULL) {
+ tv_free(DEBUGGY(gap, todel).dbg_val);
+ }
vim_regfree(DEBUGGY(gap, todel).dbg_prog);
gap->ga_len--;
if (todel < gap->ga_len) {
@@ -749,11 +779,15 @@ void ex_breaklist(exarg_T *eap)
if (bp->dbg_type == DBG_FILE) {
home_replace(NULL, bp->dbg_name, NameBuff, MAXPATHL, true);
}
- smsg(_("%3d %s %s line %" PRId64),
- bp->dbg_nr,
- bp->dbg_type == DBG_FUNC ? "func" : "file",
- bp->dbg_type == DBG_FUNC ? bp->dbg_name : NameBuff,
- (int64_t)bp->dbg_lnum);
+ if (bp->dbg_type != DBG_EXPR) {
+ smsg(_("%3d %s %s line %" PRId64),
+ bp->dbg_nr,
+ bp->dbg_type == DBG_FUNC ? "func" : "file",
+ bp->dbg_type == DBG_FUNC ? bp->dbg_name : NameBuff,
+ (int64_t)bp->dbg_lnum);
+ } else {
+ smsg(_("%3d expr %s"), bp->dbg_nr, bp->dbg_name);
+ }
}
}
}
@@ -813,6 +847,7 @@ debuggy_find(
// an already found breakpoint.
bp = &DEBUGGY(gap, i);
if ((bp->dbg_type == DBG_FILE) == file
+ && bp->dbg_type != DBG_EXPR
&& (gap == &prof_ga
|| (bp->dbg_lnum > after && (lnum == 0 || bp->dbg_lnum < lnum)))) {
// Save the value of got_int and reset it. We don't want a
@@ -827,6 +862,46 @@ debuggy_find(
}
}
got_int |= prev_got_int;
+ } else if (bp->dbg_type == DBG_EXPR) {
+ bool line = false;
+
+ prev_got_int = got_int;
+ got_int = false;
+
+ typval_T *tv = eval_expr(bp->dbg_name);
+ if (tv != NULL) {
+ if (bp->dbg_val == NULL) {
+ debug_oldval = typval_tostring(NULL);
+ bp->dbg_val = tv;
+ debug_newval = typval_tostring(bp->dbg_val);
+ line = true;
+ } else {
+ if (typval_compare(tv, bp->dbg_val, EXPR_IS, false) == OK
+ && tv->vval.v_number == false) {
+ line = true;
+ debug_oldval = typval_tostring(bp->dbg_val);
+ // Need to evaluate again, typval_compare() overwrites "tv".
+ typval_T *v = eval_expr(bp->dbg_name);
+ debug_newval = typval_tostring(v);
+ tv_free(bp->dbg_val);
+ bp->dbg_val = v;
+ }
+ tv_free(tv);
+ }
+ } else if (bp->dbg_val != NULL) {
+ debug_oldval = typval_tostring(bp->dbg_val);
+ debug_newval = typval_tostring(NULL);
+ tv_free(bp->dbg_val);
+ bp->dbg_val = NULL;
+ line = true;
+ }
+
+ if (line) {
+ lnum = after > 0 ? after : 1;
+ break;
+ }
+
+ got_int |= prev_got_int;
}
}
if (name != fname) {
@@ -1639,10 +1714,10 @@ int get_arglist_exp(char_u *str, int *fcountp, char_u ***fnamesp, bool wig)
if (wig) {
i = expand_wildcards(ga.ga_len, (char_u **)ga.ga_data,
- fcountp, fnamesp, EW_FILE|EW_NOTFOUND);
+ fcountp, fnamesp, EW_FILE|EW_NOTFOUND|EW_NOTWILD);
} else {
i = gen_expand_wildcards(ga.ga_len, (char_u **)ga.ga_data,
- fcountp, fnamesp, EW_FILE|EW_NOTFOUND);
+ fcountp, fnamesp, EW_FILE|EW_NOTFOUND|EW_NOTWILD);
}
ga_clear(&ga);
@@ -1657,9 +1732,11 @@ int get_arglist_exp(char_u *str, int *fcountp, char_u ***fnamesp, bool wig)
/// AL_DEL: remove files in 'str' from the argument list.
/// @param after
/// 0 means before first one
+/// @param will_edit will edit added argument
///
/// @return FAIL for failure, OK otherwise.
-static int do_arglist(char_u *str, int what, int after)
+static int do_arglist(char_u *str, int what, int after, bool will_edit)
+ FUNC_ATTR_NONNULL_ALL
{
garray_T new_ga;
int exp_count;
@@ -1733,10 +1810,11 @@ static int do_arglist(char_u *str, int what, int after)
}
if (what == AL_ADD) {
- (void)alist_add_list(exp_count, exp_files, after);
+ alist_add_list(exp_count, exp_files, after, will_edit);
xfree(exp_files);
- } else { // what == AL_SET
- alist_set(ALIST(curwin), exp_count, exp_files, false, NULL, 0);
+ } else {
+ assert(what == AL_SET);
+ alist_set(ALIST(curwin), exp_count, exp_files, will_edit, NULL, 0);
}
}
@@ -1956,7 +2034,7 @@ void ex_next(exarg_T *eap)
| (eap->forceit ? CCGD_FORCEIT : 0)
| CCGD_EXCMD)) {
if (*eap->arg != NUL) { // redefine file list
- if (do_arglist(eap->arg, AL_SET, 0) == FAIL) {
+ if (do_arglist(eap->arg, AL_SET, 0, true) == FAIL) {
return;
}
i = 0;
@@ -1974,7 +2052,7 @@ void ex_argedit(exarg_T *eap)
// Whether curbuf will be reused, curbuf->b_ffname will be set.
bool curbuf_is_reusable = curbuf_reusable();
- if (do_arglist(eap->arg, AL_ADD, i) == FAIL) {
+ if (do_arglist(eap->arg, AL_ADD, i, true) == FAIL) {
return;
}
maketitle();
@@ -1994,7 +2072,8 @@ void ex_argedit(exarg_T *eap)
void ex_argadd(exarg_T *eap)
{
do_arglist(eap->arg, AL_ADD,
- eap->addr_count > 0 ? (int)eap->line2 : curwin->w_arg_idx + 1);
+ eap->addr_count > 0 ? (int)eap->line2 : curwin->w_arg_idx + 1,
+ false);
maketitle();
}
@@ -2041,7 +2120,7 @@ void ex_argdelete(exarg_T *eap)
}
}
} else {
- do_arglist(eap->arg, AL_DEL, 0);
+ do_arglist(eap->arg, AL_DEL, 0, false);
}
maketitle();
}
@@ -2292,9 +2371,9 @@ void ex_listdo(exarg_T *eap)
/// Files[] itself is not taken over.
///
/// @param after: where to add: 0 = before first one
-///
-/// @return index of first added argument
-static int alist_add_list(int count, char_u **files, int after)
+/// @param will_edit will edit adding argument
+static void alist_add_list(int count, char_u **files, int after, bool will_edit)
+ FUNC_ATTR_NONNULL_ALL
{
int old_argcount = ARGCOUNT;
ga_grow(&ALIST(curwin)->al_ga, count);
@@ -2310,15 +2389,15 @@ static int alist_add_list(int count, char_u **files, int after)
(size_t)(ARGCOUNT - after) * sizeof(aentry_T));
}
for (int i = 0; i < count; i++) {
+ const int flags = BLN_LISTED | (will_edit ? BLN_CURBUF : 0);
ARGLIST[after + i].ae_fname = files[i];
- ARGLIST[after + i].ae_fnum = buflist_add(files[i],
- BLN_LISTED | BLN_CURBUF);
+ ARGLIST[after + i].ae_fnum = buflist_add(files[i], flags);
}
ALIST(curwin)->al_ga.ga_len += count;
if (old_argcount > 0 && curwin->w_arg_idx >= after) {
curwin->w_arg_idx += count;
}
- return after;
+ return;
}
}
@@ -2375,13 +2454,13 @@ void ex_compiler(exarg_T *eap)
// Set "b:current_compiler" from "current_compiler".
p = get_var_value("g:current_compiler");
if (p != NULL) {
- set_internal_string_var((char_u *)"b:current_compiler", p);
+ set_internal_string_var("b:current_compiler", p);
}
// Restore "current_compiler" for ":compiler {name}".
if (!eap->forceit) {
if (old_cur_comp != NULL) {
- set_internal_string_var((char_u *)"g:current_compiler",
+ set_internal_string_var("g:current_compiler",
old_cur_comp);
xfree(old_cur_comp);
} else {
@@ -2522,7 +2601,7 @@ void ex_pyxdo(exarg_T *eap)
}
}
-/// ":source {fname}"
+/// ":source [{fname}]"
void ex_source(exarg_T *eap)
{
cmd_source(eap->arg, eap);
@@ -2530,8 +2609,8 @@ void ex_source(exarg_T *eap)
static void cmd_source(char_u *fname, exarg_T *eap)
{
- if (*fname == NUL) {
- EMSG(_(e_argreq));
+ if (eap != NULL && *fname == NUL) {
+ cmd_source_buffer(eap);
} else if (eap != NULL && eap->forceit) {
// ":source!": read Normal mode commands
// Need to execute the commands directly. This is required at least
@@ -2549,6 +2628,38 @@ static void cmd_source(char_u *fname, exarg_T *eap)
}
}
+typedef struct {
+ linenr_T curr_lnum;
+ const linenr_T final_lnum;
+} GetBufferLineCookie;
+
+/// Get one line from the current selection in the buffer.
+/// Called by do_cmdline() when it's called from cmd_source_buffer().
+///
+/// @return pointer to allocated line, or NULL for end-of-file or
+/// some error.
+static char_u *get_buffer_line(int c, void *cookie, int indent, bool do_concat)
+{
+ GetBufferLineCookie *p = cookie;
+ if (p->curr_lnum > p->final_lnum) {
+ return NULL;
+ }
+ char_u *curr_line = ml_get(p->curr_lnum);
+ p->curr_lnum++;
+ return (char_u *)xstrdup((const char *)curr_line);
+}
+
+static void cmd_source_buffer(const exarg_T *eap)
+ FUNC_ATTR_NONNULL_ALL
+{
+ GetBufferLineCookie cookie = {
+ .curr_lnum = eap->line1,
+ .final_lnum = eap->line2,
+ };
+ source_using_linegetter((void *)&cookie, get_buffer_line,
+ ":source (no file)");
+}
+
/// ":source" and associated commands.
///
/// @return address holding the next breakpoint line for a source cookie
@@ -2620,10 +2731,9 @@ static char_u *get_str_line(int c, void *cookie, int indent, bool do_concat)
return (char_u *)xstrdup(buf);
}
-/// Executes lines in `src` as Ex commands.
-///
-/// @see do_source()
-int do_source_str(const char *cmd, const char *traceback_name)
+static int source_using_linegetter(void *cookie,
+ LineGetter fgetline,
+ const char *traceback_name)
{
char_u *save_sourcing_name = sourcing_name;
linenr_T save_sourcing_lnum = sourcing_lnum;
@@ -2638,22 +2748,33 @@ int do_source_str(const char *cmd, const char *traceback_name)
}
sourcing_lnum = 0;
- GetStrLineCookie cookie = {
- .buf = (char_u *)cmd,
- .offset = 0,
- };
const sctx_T save_current_sctx = current_sctx;
current_sctx.sc_sid = SID_STR;
current_sctx.sc_seq = 0;
current_sctx.sc_lnum = save_sourcing_lnum;
- int retval = do_cmdline(NULL, get_str_line, (void *)&cookie,
+ funccal_entry_T entry;
+ save_funccal(&entry);
+ int retval = do_cmdline(NULL, fgetline, cookie,
DOCMD_VERBOSE | DOCMD_NOWAIT | DOCMD_REPEAT);
- current_sctx = save_current_sctx;
sourcing_lnum = save_sourcing_lnum;
sourcing_name = save_sourcing_name;
+ current_sctx = save_current_sctx;
+ restore_funccal();
return retval;
}
+/// Executes lines in `src` as Ex commands.
+///
+/// @see do_source()
+int do_source_str(const char *cmd, const char *traceback_name)
+{
+ GetStrLineCookie cookie = {
+ .buf = (char_u *)cmd,
+ .offset = 0,
+ };
+ return source_using_linegetter((void *)&cookie, get_str_line, traceback_name);
+}
+
/// Reads the file `fname` and executes its lines as Ex commands.
///
/// This function may be called recursively!
@@ -2978,6 +3099,7 @@ void scriptnames_slash_adjust(void)
# endif
/// Get a pointer to a script name. Used for ":verbose set".
+/// Message appended to "Last set from "
char_u *get_scriptname(LastSet last_set, bool *should_free)
{
*should_free = false;
@@ -2993,6 +3115,8 @@ char_u *get_scriptname(LastSet last_set, bool *should_free)
return (char_u *)_("environment variable");
case SID_ERROR:
return (char_u *)_("error handler");
+ case SID_WINLAYOUT:
+ return (char_u *)_("changed window size");
case SID_LUA:
return (char_u *)_("Lua");
case SID_API_CLIENT:
@@ -3766,7 +3890,7 @@ void ex_drop(exarg_T *eap)
// and mostly only one file is dropped.
// This also ignores wildcards, since it is very unlikely the user is
// editing a file name with a wildcard character.
- do_arglist(eap->arg, AL_SET, 0);
+ do_arglist(eap->arg, AL_SET, 0, false);
// Expanding wildcards may result in an empty argument list. E.g. when
// editing "foo.pyc" and ".pyc" is in 'wildignore'. Assume that we
diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h
index ca84d375ce..f928c61ea4 100644
--- a/src/nvim/ex_cmds_defs.h
+++ b/src/nvim/ex_cmds_defs.h
@@ -188,8 +188,8 @@ struct exarg {
// used for completion on the command line
struct expand {
- int xp_context; // type of expansion
char_u *xp_pattern; // start of item to expand
+ int xp_context; // type of expansion
size_t xp_pattern_len; // bytes in xp_pattern before cursor
char_u *xp_arg; // completion function
sctx_T xp_script_ctx; // SCTX for completion function
@@ -199,9 +199,9 @@ struct expand {
// characters need to be escaped
#endif
int xp_numfiles; // number of files found by file name completion
+ int xp_col; // cursor position in line
char_u **xp_files; // list of files
char_u *xp_line; // text being completed
- int xp_col; // cursor position in line
};
// values for xp_backslash
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index ccf7dd0f68..ae5c334592 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -317,7 +317,9 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
int count = 0; /* line number count */
int did_inc = FALSE; /* incremented RedrawingDisabled */
int retval = OK;
- cstack_T cstack; // conditional stack
+ cstack_T cstack = { // conditional stack
+ .cs_idx = -1,
+ };
garray_T lines_ga; // keep lines for ":while"/":for"
int current_line = 0; // active line in lines_ga
char_u *fname = NULL; // function or script name
@@ -360,11 +362,6 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
call_depth++;
start_batch_changes();
- cstack.cs_idx = -1;
- cstack.cs_looplevel = 0;
- cstack.cs_trylevel = 0;
- cstack.cs_emsg_silent_list = NULL;
- cstack.cs_lflags = 0;
ga_init(&lines_ga, (int)sizeof(wcmd_T), 10);
real_cookie = getline_cookie(fgetline, cookie);
@@ -1860,6 +1857,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
case CMD_echoerr:
case CMD_echomsg:
case CMD_echon:
+ case CMD_eval:
case CMD_execute:
case CMD_filter:
case CMD_help:
@@ -2182,7 +2180,7 @@ int parse_command_modifiers(exarg_T *eap, char_u **errormsg, bool skip_only)
// Set 'eventignore' to "all". Restore the
// existing option value later.
cmdmod.save_ei = vim_strsave(p_ei);
- set_string_option_direct((char_u *)"ei", -1,
+ set_string_option_direct("ei", -1,
(char_u *)"all", OPT_FREE, SID_NONE);
}
continue;
@@ -2294,9 +2292,8 @@ static void undo_cmdmod(const exarg_T *eap, int save_msg_scroll)
}
if (cmdmod.save_ei != NULL) {
- /* Restore 'eventignore' to the value before ":noautocmd". */
- set_string_option_direct((char_u *)"ei", -1, cmdmod.save_ei,
- OPT_FREE, SID_NONE);
+ // Restore 'eventignore' to the value before ":noautocmd".
+ set_string_option_direct("ei", -1, cmdmod.save_ei, OPT_FREE, SID_NONE);
free_string_option(cmdmod.save_ei);
}
@@ -3041,9 +3038,10 @@ const char * set_one_cmd_context(
p = arg + 1;
arg = (const char *)skip_cmd_arg((char_u *)arg, false);
- /* Still touching the command after '+'? */
- if (*arg == NUL)
+ // Still touching the command after '+'?
+ if (*arg == NUL) {
return p;
+ }
// Skip space(s) after +command to get to the real argument.
arg = (const char *)skipwhite((const char_u *)arg);
@@ -3515,13 +3513,20 @@ const char * set_one_cmd_context(
xp->xp_context = EXPAND_BUFFERS;
xp->xp_pattern = (char_u *)arg;
break;
+ case CMD_diffget:
+ case CMD_diffput:
+ // If current buffer is in diff mode, complete buffer names
+ // which are in diff mode, and different than current buffer.
+ xp->xp_context = EXPAND_DIFF_BUFFERS;
+ xp->xp_pattern = (char_u *)arg;
+ break;
case CMD_USER:
case CMD_USER_BUF:
if (context != EXPAND_NOTHING) {
// EX_XFILE: file names are handled above.
if (!(ea.argt & EX_XFILE)) {
if (context == EXPAND_MENUS) {
- return (const char *)set_context_in_menu_cmd(xp, (char_u *)cmd,
+ return (const char *)set_context_in_menu_cmd(xp, cmd,
(char_u *)arg, forceit);
} else if (context == EXPAND_COMMANDS) {
return arg;
@@ -3601,7 +3606,7 @@ const char * set_one_cmd_context(
case CMD_tmenu: case CMD_tunmenu:
case CMD_popup: case CMD_emenu:
return (const char *)set_context_in_menu_cmd(
- xp, (char_u *)cmd, (char_u *)arg, forceit);
+ xp, cmd, (char_u *)arg, forceit);
case CMD_colorscheme:
xp->xp_context = EXPAND_COLORS;
@@ -3680,6 +3685,10 @@ const char * set_one_cmd_context(
xp->xp_pattern = (char_u *)arg;
break;
+ case CMD_lua:
+ xp->xp_context = EXPAND_LUA;
+ break;
+
default:
break;
}
@@ -3920,7 +3929,7 @@ static linenr_T get_address(exarg_T *eap,
}
searchcmdlen = 0;
flags = silent ? 0 : SEARCH_HIS | SEARCH_MSG;
- if (!do_search(NULL, c, cmd, 1L, flags, NULL)) {
+ if (!do_search(NULL, c, c, cmd, 1L, flags, NULL)) {
curwin->w_cursor = pos;
cmd = NULL;
goto error;
@@ -3968,7 +3977,7 @@ static linenr_T get_address(exarg_T *eap,
break;
default:
- if (ascii_isdigit(*cmd)) { // absolute line number
+ if (ascii_isdigit(*cmd)) { // absolute line number
lnum = getdigits_long(&cmd, false, 0);
}
}
@@ -5173,6 +5182,7 @@ static const char *command_complete[] =
[EXPAND_CSCOPE] = "cscope",
[EXPAND_USER_DEFINED] = "custom",
[EXPAND_USER_LIST] = "customlist",
+ [EXPAND_DIFF_BUFFERS] = "diff_buffer",
[EXPAND_DIRECTORIES] = "dir",
[EXPAND_ENV_VARS] = "environment",
[EXPAND_EVENTS] = "event",
@@ -5187,6 +5197,7 @@ static const char *command_complete[] =
#ifdef HAVE_WORKING_LIBINTL
[EXPAND_LOCALES] = "locale",
#endif
+ [EXPAND_LUA] = "lua",
[EXPAND_MAPCLEAR] = "mapclear",
[EXPAND_MAPPINGS] = "mapping",
[EXPAND_MENUS] = "menu",
@@ -5400,8 +5411,8 @@ static int uc_scan_attr(char_u *attr, size_t len, uint32_t *argt, long *def,
size_t vallen = 0;
size_t attrlen = len;
- /* Look for the attribute name - which is the part before any '=' */
- for (i = 0; i < (int)len; ++i) {
+ // Look for the attribute name - which is the part before any '='
+ for (i = 0; i < (int)len; i++) {
if (attr[i] == '=') {
val = &attr[i + 1];
vallen = len - i - 1;
@@ -6272,14 +6283,14 @@ int parse_compl_arg(const char_u *value, int vallen, int *complp,
return OK;
}
-int cmdcomplete_str_to_type(char_u *complete_str)
+int cmdcomplete_str_to_type(const char *complete_str)
{
for (int i = 0; i < (int)(ARRAY_SIZE(command_complete)); i++) {
char *cmd_compl = get_command_complete(i);
if (cmd_compl == NULL) {
continue;
}
- if (STRCMP(complete_str, command_complete[i]) == 0) {
+ if (strcmp(complete_str, command_complete[i]) == 0) {
return i;
}
}
@@ -7269,9 +7280,7 @@ static void ex_find(exarg_T *eap)
}
}
-/*
- * ":edit", ":badd", ":visual".
- */
+/// ":edit", ":badd", ":balt", ":visual".
static void ex_edit(exarg_T *eap)
{
do_exedit(eap, NULL);
@@ -7345,7 +7354,9 @@ do_exedit(
else if (eap->cmdidx == CMD_enew)
readonlymode = FALSE; /* 'readonly' doesn't make sense in an
empty buffer */
- setpcmark();
+ if (eap->cmdidx != CMD_balt && eap->cmdidx != CMD_badd) {
+ setpcmark();
+ }
if (do_ecmd(0, (eap->cmdidx == CMD_enew ? NULL : eap->arg),
NULL, eap, eap->do_ecmd_lnum,
(buf_hide(curbuf) ? ECMD_HIDE : 0)
@@ -7353,6 +7364,7 @@ do_exedit(
// After a split we can use an existing buffer.
+ (old_curwin != NULL ? ECMD_OLDBUF : 0)
+ (eap->cmdidx == CMD_badd ? ECMD_ADDBUF : 0)
+ + (eap->cmdidx == CMD_balt ? ECMD_ALTBUF : 0)
, old_curwin == NULL ? curwin : NULL) == FAIL) {
// Editing the file failed. If the window was split, close it.
if (old_curwin != NULL) {
@@ -7483,7 +7495,7 @@ static void ex_syncbind(exarg_T *eap)
ctrl_o[0] = Ctrl_O;
ctrl_o[1] = 0;
- ins_typebuf(ctrl_o, REMAP_NONE, 0, TRUE, FALSE);
+ ins_typebuf(ctrl_o, REMAP_NONE, 0, true, false);
}
}
}
@@ -7503,8 +7515,9 @@ static void ex_read(exarg_T *eap)
}
if (*eap->arg == NUL) {
- if (check_fname() == FAIL) /* check for no file name */
+ if (check_fname() == FAIL) { // check for no file name
return;
+ }
i = readfile(curbuf->b_ffname, curbuf->b_fname,
eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0);
} else {
@@ -7587,7 +7600,7 @@ void post_chdir(CdScope scope, bool trigger_dirchanged)
curwin->w_localdir = (char_u *)xstrdup(cwd);
break;
case kCdScopeInvalid:
- assert(false);
+ abort();
}
shorten_fnames(true);
@@ -7752,6 +7765,11 @@ static void do_exmap(exarg_T *eap, int isabbrev)
static void ex_winsize(exarg_T *eap)
{
char_u *arg = eap->arg;
+
+ if (!ascii_isdigit(*arg)) {
+ EMSG2(_(e_invarg2), arg);
+ return;
+ }
int w = getdigits_int(&arg, false, 10);
arg = skipwhite(arg);
char_u *p = arg;
@@ -8717,7 +8735,7 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen)
* Evaluate cmdline variables.
*
* change '%' to curbuf->b_ffname
- * '#' to curwin->w_altfile
+ * '#' to curwin->w_alt_fnum
* '<cword>' to word under the cursor
* '<cWORD>' to WORD under the cursor
* '<cexpr>' to C-expression under the cursor
diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c
index 0917c6dd02..5ca88002f1 100644
--- a/src/nvim/ex_eval.c
+++ b/src/nvim/ex_eval.c
@@ -788,6 +788,15 @@ void report_discard_pending(int pending, void *value)
}
}
+// ":eval".
+void ex_eval(exarg_T *eap)
+{
+ typval_T tv;
+
+ if (eval0(eap->arg, &tv, &eap->nextcmd, !eap->skip) == OK) {
+ tv_clear(&tv);
+ }
+}
/*
* ":if".
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 2aa66f6a8c..7159b27665 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -69,6 +69,7 @@
#include "nvim/lib/kvec.h"
#include "nvim/api/private/helpers.h"
#include "nvim/highlight_defs.h"
+#include "nvim/lua/executor.h"
#include "nvim/viml/parser/parser.h"
#include "nvim/viml/parser/expressions.h"
@@ -274,8 +275,9 @@ static void init_incsearch_state(incsearch_state_T *s)
// Return true when 'incsearch' highlighting is to be done.
// Sets search_first_line and search_last_line to the address range.
-static bool do_incsearch_highlighting(int firstc, incsearch_state_T *s,
- int *skiplen, int *patlen)
+static bool do_incsearch_highlighting(int firstc, int *search_delim,
+ incsearch_state_T *s, int *skiplen,
+ int *patlen)
FUNC_ATTR_NONNULL_ALL
{
char_u *cmd;
@@ -302,6 +304,7 @@ static bool do_incsearch_highlighting(int firstc, incsearch_state_T *s,
search_last_line = MAXLNUM;
if (firstc == '/' || firstc == '?') {
+ *search_delim = firstc;
return true;
}
if (firstc != ':') {
@@ -370,6 +373,7 @@ static bool do_incsearch_highlighting(int firstc, incsearch_state_T *s,
p = skipwhite(p);
delim = (delim_optional && vim_isIDc(*p)) ? ' ' : *p++;
+ *search_delim = delim;
end = skip_regexp(p, delim, p_magic, NULL);
use_last_pat = end == p && *end == delim;
@@ -430,12 +434,14 @@ static void may_do_incsearch_highlighting(int firstc, long count,
int skiplen, patlen;
char_u next_char;
char_u use_last_pat;
+ int search_delim;
// Parsing range may already set the last search pattern.
// NOTE: must call restore_last_search_pattern() before returning!
save_last_search_pattern();
- if (!do_incsearch_highlighting(firstc, s, &skiplen, &patlen)) {
+ if (!do_incsearch_highlighting(firstc, &search_delim, s, &skiplen,
+ &patlen)) {
restore_last_search_pattern();
finish_incsearch_highlighting(false, s, true);
return;
@@ -489,7 +495,7 @@ static void may_do_incsearch_highlighting(int firstc, long count,
ccline.cmdbuff[skiplen + patlen] = NUL;
memset(&sia, 0, sizeof(sia));
sia.sa_tm = &tm;
- found = do_search(NULL, firstc == ':' ? '/' : firstc,
+ found = do_search(NULL, firstc == ':' ? '/' : firstc, search_delim,
ccline.cmdbuff + skiplen, count,
search_flags, &sia);
ccline.cmdbuff[skiplen + patlen] = next_char;
@@ -580,13 +586,15 @@ static int may_add_char_to_search(int firstc, int *c, incsearch_state_T *s)
FUNC_ATTR_NONNULL_ALL
{
int skiplen, patlen;
+ int search_delim;
// Parsing range may already set the last search pattern.
// NOTE: must call restore_last_search_pattern() before returning!
save_last_search_pattern();
// Add a character from under the cursor for 'incsearch'
- if (!do_incsearch_highlighting(firstc, s, &skiplen, &patlen)) {
+ if (!do_incsearch_highlighting(firstc, &search_delim, s, &skiplen,
+ &patlen)) {
restore_last_search_pattern();
return FAIL;
}
@@ -603,7 +611,7 @@ static int may_add_char_to_search(int firstc, int *c, incsearch_state_T *s)
&& !pat_has_uppercase(ccline.cmdbuff + skiplen)) {
*c = mb_tolower(*c);
}
- if (*c == firstc
+ if (*c == search_delim
|| vim_strchr((char_u *)(p_magic ? "\\~^$.*[" : "\\^$"), *c)
!= NULL) {
// put a backslash before special characters
@@ -774,11 +782,20 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
redrawcmd();
}
- // redraw the statusline for statuslines that display the current mode
- // using the mode() function.
+ // Redraw the statusline in case it uses the current mode using the mode()
+ // function.
if (!cmd_silent && msg_scrolled == 0) {
- curwin->w_redr_status = true;
- redraw_statuslines();
+ bool found_one = false;
+
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (*p_stl != NUL || *wp->w_p_stl != NUL) {
+ wp->w_redr_status = true;
+ found_one = true;
+ }
+ }
+ if (found_one) {
+ redraw_statuslines();
+ }
}
did_emsg = false;
@@ -885,7 +902,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
need_wait_return = false;
}
- set_string_option_direct((char_u *)"icm", -1, s->save_p_icm, OPT_FREE,
+ set_string_option_direct("icm", -1, s->save_p_icm, OPT_FREE,
SID_NONE);
State = s->save_State;
setmouse();
@@ -934,7 +951,7 @@ static int command_line_execute(VimState *state, int key)
if (s->c == K_EVENT || s->c == K_COMMAND) {
if (s->c == K_EVENT) {
- multiqueue_process_events(main_loop.events);
+ state_handle_k_event();
} else {
do_cmdline(NULL, getcmdkeycmd, NULL, DOCMD_NOWAIT);
}
@@ -1464,13 +1481,14 @@ static int may_do_command_line_next_incsearch(int firstc, long count,
bool next_match)
FUNC_ATTR_NONNULL_ALL
{
- int skiplen, patlen;
+ int skiplen, patlen, search_delim;
// Parsing range may already set the last search pattern.
// NOTE: must call restore_last_search_pattern() before returning!
save_last_search_pattern();
- if (!do_incsearch_highlighting(firstc, s, &skiplen, &patlen)) {
+ if (!do_incsearch_highlighting(firstc, &search_delim, s, &skiplen,
+ &patlen)) {
restore_last_search_pattern();
return OK;
}
@@ -1488,7 +1506,7 @@ static int may_do_command_line_next_incsearch(int firstc, long count,
char_u save;
- if (firstc == ccline.cmdbuff[skiplen]) {
+ if (search_delim == ccline.cmdbuff[skiplen]) {
pat = last_search_pattern();
skiplen = 0;
patlen = (int)STRLEN(pat);
@@ -3945,6 +3963,12 @@ nextwild (
p2 = ExpandOne(xp, p1, vim_strnsave(&ccline.cmdbuff[i], xp->xp_pattern_len),
use_options, type);
xfree(p1);
+
+ // xp->xp_pattern might have been modified by ExpandOne (for example,
+ // in lua completion), so recompute the pattern index and length
+ i = (int)(xp->xp_pattern - ccline.cmdbuff);
+ xp->xp_pattern_len = (size_t)ccline.cmdpos - (size_t)i;
+
// Longest match: make sure it is not shorter, happens with :help.
if (p2 != NULL && type == WILD_LONGEST) {
for (j = 0; (size_t)j < xp->xp_pattern_len; j++) {
@@ -3960,7 +3984,7 @@ nextwild (
}
if (p2 != NULL && !got_int) {
- difflen = (int)STRLEN(p2) - (int)xp->xp_pattern_len;
+ difflen = (int)STRLEN(p2) - (int)(xp->xp_pattern_len);
if (ccline.cmdlen + difflen + 4 > ccline.cmdbufflen) {
realloc_cmdbuff(ccline.cmdlen + difflen + 4);
xp->xp_pattern = ccline.cmdbuff + i;
@@ -4086,9 +4110,10 @@ ExpandOne (
}
if (mode == WILD_CANCEL) {
- ss = vim_strsave(orig_save);
+ ss = vim_strsave(orig_save ? orig_save : (char_u *)"");
} else if (mode == WILD_APPLY) {
- ss = vim_strsave(findex == -1 ? orig_save : xp->xp_files[findex]);
+ ss = vim_strsave(findex == -1 ? (orig_save ? orig_save : (char_u *)"") :
+ xp->xp_files[findex]);
}
/* free old names */
@@ -5077,9 +5102,13 @@ ExpandFromContext (
}
if (xp->xp_context == EXPAND_BUFFERS)
return ExpandBufnames(pat, num_file, file, options);
+ if (xp->xp_context == EXPAND_DIFF_BUFFERS) {
+ return ExpandBufnames(pat, num_file, file, options | BUF_DIFF_FILTER);
+ }
if (xp->xp_context == EXPAND_TAGS
- || xp->xp_context == EXPAND_TAGS_LISTFILES)
+ || xp->xp_context == EXPAND_TAGS_LISTFILES) {
return expand_tags(xp->xp_context == EXPAND_TAGS, pat, num_file, file);
+ }
if (xp->xp_context == EXPAND_COLORS) {
char *directories[] = { "colors", NULL };
return ExpandRTDir(pat, DIP_START + DIP_OPT, num_file, file, directories);
@@ -5107,6 +5136,22 @@ ExpandFromContext (
return ExpandPackAddDir(pat, num_file, file);
}
+ // When expanding a function name starting with s:, match the <SNR>nr_
+ // prefix.
+ char_u *tofree = NULL;
+ if (xp->xp_context == EXPAND_USER_FUNC && STRNCMP(pat, "^s:", 3) == 0) {
+ const size_t len = STRLEN(pat) + 20;
+
+ tofree = xmalloc(len);
+ snprintf((char *)tofree, len, "^<SNR>\\d\\+_%s", pat + 3);
+ pat = tofree;
+ }
+
+ if (xp->xp_context == EXPAND_LUA) {
+ ILOG("PAT %s", pat);
+ return nlua_expand_pat(xp, pat, num_file, file);
+ }
+
regmatch.regprog = vim_regcomp(pat, p_magic ? RE_MAGIC : 0);
if (regmatch.regprog == NULL)
return FAIL;
@@ -5180,6 +5225,7 @@ ExpandFromContext (
}
vim_regfree(regmatch.regprog);
+ xfree(tofree);
return ret;
}
@@ -6372,7 +6418,7 @@ int hist_type2char(int type)
return '>';
}
default: {
- assert(false);
+ abort();
}
}
return NUL;
@@ -6430,7 +6476,7 @@ static int open_cmdwin(void)
cmdwin_level = ccline.level;
// Create empty command-line buffer.
- buf_open_scratch(0, "[Command Line]");
+ buf_open_scratch(0, _("[Command Line]"));
// Command-line buffer has bufhidden=wipe, unlike a true "scratch" buffer.
set_option_value("bh", 0L, "wipe", OPT_LOCAL);
curwin->w_p_rl = cmdmsg_rl;
@@ -6759,6 +6805,6 @@ static void set_search_match(pos_T *t)
t->col = search_match_endcol;
if (t->lnum > curbuf->b_ml.ml_line_count) {
t->lnum = curbuf->b_ml.ml_line_count;
- coladvance((colnr_T)MAXCOL);
+ coladvance(MAXCOL);
}
}
diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h
index dc4395e081..3727aa5e62 100644
--- a/src/nvim/ex_getln.h
+++ b/src/nvim/ex_getln.h
@@ -32,6 +32,7 @@
#define WILD_IGNORE_COMPLETESLASH 0x400
#define WILD_NOERROR 0x800 // sets EW_NOERROR
#define WILD_BUFLASTUSED 0x1000
+#define BUF_DIFF_FILTER 0x2000
/// Present history tables
typedef enum {
diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c
index 14dac9a126..09453e100d 100644
--- a/src/nvim/ex_session.c
+++ b/src/nvim/ex_session.c
@@ -384,6 +384,19 @@ static int put_view(
xfree(fname_esc);
}
+ if (wp->w_alt_fnum) {
+ buf_T *const alt = buflist_findnr(wp->w_alt_fnum);
+
+ // Set the alternate file if the buffer is listed.
+ if ((flagp == &ssop_flags) && alt != NULL && alt->b_fname != NULL
+ && *alt->b_fname != NUL
+ && alt->b_p_bl
+ && (fputs("balt ", fd) < 0
+ || ses_fname(fd, alt, flagp, true) == FAIL)) {
+ return FAIL;
+ }
+ }
+
//
// Local mappings and abbreviations.
//
@@ -438,9 +451,9 @@ static int put_view(
"let s:l = %" PRId64 " - ((%" PRId64
" * winheight(0) + %" PRId64 ") / %" PRId64 ")\n"
"if s:l < 1 | let s:l = 1 | endif\n"
- "exe s:l\n"
+ "keepjumps exe s:l\n"
"normal! zt\n"
- "%" PRId64 "\n",
+ "keepjumps %" PRId64 "\n",
(int64_t)wp->w_cursor.lnum,
(int64_t)(wp->w_cursor.lnum - wp->w_topline),
(int64_t)(wp->w_height_inner / 2),
diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c
index cacbeddb32..2906a2196b 100644
--- a/src/nvim/extmark.c
+++ b/src/nvim/extmark.c
@@ -702,6 +702,7 @@ void extmark_move_region(
int new_row, colnr_T new_col, bcount_t new_byte,
ExtmarkOp undo)
{
+ curbuf->deleted_bytes2 = 0;
// TODO(bfredl): this is not synced to the buffer state inside the callback.
// But unless we make the undo implementation smarter, this is not ensured
// anyway.
diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c
index b1fa0b6779..8beba38509 100644
--- a/src/nvim/file_search.c
+++ b/src/nvim/file_search.c
@@ -1595,7 +1595,7 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, bool changed_window)
}
case kCdScopeInvalid: {
// Should never happen.
- assert(false);
+ abort();
}
}
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index a542bb19dd..792ef81665 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -1644,8 +1644,7 @@ failed:
save_file_ff(curbuf);
// If editing a new file: set 'fenc' for the current buffer.
// Also for ":read ++edit file".
- set_string_option_direct((char_u *)"fenc", -1, fenc,
- OPT_FREE | OPT_LOCAL, 0);
+ set_string_option_direct("fenc", -1, fenc, OPT_FREE | OPT_LOCAL, 0);
}
if (fenc_alloced)
xfree(fenc);
@@ -2002,7 +2001,7 @@ void set_forced_fenc(exarg_T *eap)
{
if (eap->force_enc != 0) {
char_u *fenc = enc_canonize(eap->cmd + eap->force_enc);
- set_string_option_direct((char_u *)"fenc", -1, fenc, OPT_FREE|OPT_LOCAL, 0);
+ set_string_option_direct("fenc", -1, fenc, OPT_FREE|OPT_LOCAL, 0);
xfree(fenc);
}
}
@@ -4281,7 +4280,7 @@ char *modname(const char *fname, const char *ext, bool prepend_dot)
if (fname == NULL || *fname == NUL) {
retval = xmalloc(MAXPATHL + extlen + 3); // +3 for PATHSEP, "_" (Win), NUL
if (os_dirname((char_u *)retval, MAXPATHL) == FAIL
- || (fnamelen = strlen(retval)) == 0) {
+ || strlen(retval) == 0) {
xfree(retval);
return NULL;
}
@@ -4948,11 +4947,11 @@ int buf_check_timestamp(buf_T *buf)
(void)msg_end();
if (emsg_silent == 0) {
ui_flush();
- /* give the user some time to think about it */
- os_delay(1000L, true);
+ // give the user some time to think about it
+ os_delay(1004L, true);
- /* don't redraw and erase the message */
- redraw_cmdline = FALSE;
+ // don't redraw and erase the message
+ redraw_cmdline = false;
}
}
already_warned = TRUE;
@@ -5077,7 +5076,8 @@ void buf_reload(buf_T *buf, int orig_mode)
// Mark all undo states as changed.
u_unchanged(curbuf);
}
- buf_updates_unregister_all(curbuf);
+ buf_updates_unload(curbuf, true);
+ curbuf->b_mod_set = true;
}
}
xfree(ea.cmd);
diff --git a/src/nvim/fold.c b/src/nvim/fold.c
index 0593c16999..5032646d7e 100644
--- a/src/nvim/fold.c
+++ b/src/nvim/fold.c
@@ -2999,7 +2999,6 @@ static void foldlevelDiff(fline_T *flp)
static void foldlevelExpr(fline_T *flp)
{
win_T *win;
- int n;
int c;
linenr_T lnum = flp->lnum + flp->off;
@@ -3017,7 +3016,7 @@ static void foldlevelExpr(fline_T *flp)
/* KeyTyped may be reset to 0 when calling a function which invokes
* do_cmdline(). To make 'foldopen' work correctly restore KeyTyped. */
const bool save_keytyped = KeyTyped;
- n = (int)eval_foldexpr(flp->wp->w_p_fde, &c);
+ const int n = eval_foldexpr(flp->wp->w_p_fde, &c);
KeyTyped = save_keytyped;
switch (c) {
@@ -3202,8 +3201,10 @@ int put_folds(FILE *fd, win_T *wp)
{
if (foldmethodIsManual(wp)) {
if (put_line(fd, "silent! normal! zE") == FAIL
- || put_folds_recurse(fd, &wp->w_folds, (linenr_T)0) == FAIL)
+ || put_folds_recurse(fd, &wp->w_folds, (linenr_T)0) == FAIL
+ || put_line(fd, "let &fdl = &fdl") == FAIL) {
return FAIL;
+ }
}
/* If some folds are manually opened/closed, need to restore that. */
diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua
index 7e78b9e9d6..d2a7c16186 100644
--- a/src/nvim/generators/gen_api_dispatch.lua
+++ b/src/nvim/generators/gen_api_dispatch.lua
@@ -104,8 +104,11 @@ for _,f in ipairs(shallowcopy(functions)) do
elseif startswith(f.name, "nvim_tabpage_") then
ismethod = true
end
+ f.remote = f.remote_only or not f.lua_only
+ f.lua = f.lua_only or not f.remote_only
+ f.eval = (not f.lua_only) and (not f.remote_only)
else
- f.remote_only = true
+ f.remote = true
f.since = 0
f.deprecated_since = 1
end
@@ -127,7 +130,8 @@ for _,f in ipairs(shallowcopy(functions)) do
newf.return_type = "Object"
end
newf.impl_name = f.name
- newf.remote_only = true
+ newf.lua = false
+ newf.eval = false
newf.since = 0
newf.deprecated_since = 1
functions[#functions+1] = newf
@@ -192,7 +196,7 @@ end
-- the real API.
for i = 1, #functions do
local fn = functions[i]
- if fn.impl_name == nil and not fn.lua_only then
+ if fn.impl_name == nil and fn.remote then
local args = {}
output:write('Object handle_'..fn.name..'(uint64_t channel_id, Array args, Error *error)')
@@ -323,7 +327,7 @@ void msgpack_rpc_init_method_table(void)
for i = 1, #functions do
local fn = functions[i]
- if not fn.lua_only then
+ if fn.remote then
output:write(' msgpack_rpc_add_method_handler('..
'(String) {.data = "'..fn.name..'", '..
'.size = sizeof("'..fn.name..'") - 1}, '..
@@ -492,7 +496,7 @@ local function process_function(fn)
end
for _, fn in ipairs(functions) do
- if not fn.remote_only or fn.name:sub(1, 4) == '_vim' then
+ if fn.lua or fn.name:sub(1, 4) == '_vim' then
process_function(fn)
end
end
diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua
index d16453530f..679895421a 100644
--- a/src/nvim/generators/gen_eval.lua
+++ b/src/nvim/generators/gen_eval.lua
@@ -25,7 +25,7 @@ local gperfpipe = io.open(funcsfname .. '.gperf', 'wb')
local funcs = require('eval').funcs
local metadata = mpack.unpack(io.open(metadata_file, 'rb'):read("*all"))
for _,fun in ipairs(metadata) do
- if not (fun.remote_only or fun.lua_only) then
+ if fun.eval then
funcs[fun.name] = {
args=#fun.parameters,
func='api_wrapper',
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 2e2993ed26..9afce6e9d5 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -78,7 +78,7 @@ FileDescriptor *scriptin[NSCRIPT] = { NULL };
* Un-escaping is done by vgetc().
*/
-#define MINIMAL_SIZE 20 /* minimal size for b_str */
+#define MINIMAL_SIZE 20 // minimal size for b_str
static buffheader_T redobuff = { { NULL, { NUL } }, NULL, 0, 0 };
static buffheader_T old_redobuff = { { NULL, { NUL } }, NULL, 0, 0 };
@@ -90,7 +90,7 @@ static buffheader_T readbuf1 = { { NULL, { NUL } }, NULL, 0, 0 };
// Second read ahead buffer. Used for redo.
static buffheader_T readbuf2 = { { NULL, { NUL } }, NULL, 0, 0 };
-static int typeahead_char = 0; /* typeahead char that's not flushed */
+static int typeahead_char = 0; // typeahead char that's not flushed
/*
* when block_redo is TRUE redo buffer will not be changed
@@ -116,9 +116,9 @@ static bool maphash_valid = false;
/*
* List used for abbreviations.
*/
-static mapblock_T *first_abbr = NULL; /* first entry in abbrlist */
+static mapblock_T *first_abbr = NULL; // first entry in abbrlist
-static int KeyNoremap = 0; /* remapping flags */
+static int KeyNoremap = 0; // remapping flags
/*
* Variables used by vgetorpeek() and flush_buffers()
@@ -139,18 +139,18 @@ static int KeyNoremap = 0; /* remapping flags */
* typebuf.tb_noremap[typebuf.tb_off] is the first valid flag.
* (typebuf has been put in globals.h, because check_termcode() needs it).
*/
-#define RM_YES 0 /* tb_noremap: remap */
-#define RM_NONE 1 /* tb_noremap: don't remap */
-#define RM_SCRIPT 2 /* tb_noremap: remap local script mappings */
-#define RM_ABBR 4 /* tb_noremap: don't remap, do abbrev. */
+#define RM_YES 0 // tb_noremap: remap
+#define RM_NONE 1 // tb_noremap: don't remap
+#define RM_SCRIPT 2 // tb_noremap: remap local script mappings
+#define RM_ABBR 4 // tb_noremap: don't remap, do abbrev.
/* typebuf.tb_buf has three parts: room in front (for result of mappings), the
* middle for typeahead and room for new characters (which needs to be 3 *
* MAXMAPLEN) for the Amiga).
*/
#define TYPELEN_INIT (5 * (MAXMAPLEN + 3))
-static char_u typebuf_init[TYPELEN_INIT]; /* initial typebuf.tb_buf */
-static char_u noremapbuf_init[TYPELEN_INIT]; /* initial typebuf.tb_noremap */
+static char_u typebuf_init[TYPELEN_INIT]; // initial typebuf.tb_buf
+static char_u noremapbuf_init[TYPELEN_INIT]; // initial typebuf.tb_noremap
static size_t last_recorded_len = 0; // number of last recorded chars
@@ -707,8 +707,9 @@ static int read_redo(bool init, bool old_redo)
break;
}
c = *p;
- if (c == NUL) /* cannot happen? */
+ if (c == NUL) { // cannot happen?
break;
+ }
}
return c;
@@ -744,14 +745,15 @@ int start_redo(long count, bool old_redo)
c = read_redo(false, old_redo);
- /* copy the buffer name, if present */
+ // copy the buffer name, if present
if (c == '"') {
add_buff(&readbuf2, "\"", 1L);
c = read_redo(false, old_redo);
- /* if a numbered buffer is used, increment the number */
- if (c >= '1' && c < '9')
- ++c;
+ // if a numbered buffer is used, increment the number
+ if (c >= '1' && c < '9') {
+ c++;
+ }
add_char_buff(&readbuf2, c);
// the expression register should be re-evaluated
@@ -763,7 +765,7 @@ int start_redo(long count, bool old_redo)
c = read_redo(false, old_redo);
}
- if (c == 'v') { /* redo Visual */
+ if (c == 'v') { // redo Visual
VIsual = curwin->w_cursor;
VIsual_active = true;
VIsual_select = false;
@@ -780,7 +782,7 @@ int start_redo(long count, bool old_redo)
add_num_buff(&readbuf2, count);
}
- /* copy from the redo buffer into the stuff buffer */
+ // copy from the redo buffer into the stuff buffer
add_char_buff(&readbuf2, c);
copy_redo(old_redo);
return OK;
@@ -838,26 +840,25 @@ static void init_typebuf(void)
}
}
-/*
- * insert a string in position 'offset' in the typeahead buffer (for "@r"
- * and ":normal" command, vgetorpeek() and check_termcode())
- *
- * If noremap is REMAP_YES, new string can be mapped again.
- * If noremap is REMAP_NONE, new string cannot be mapped again.
- * If noremap is REMAP_SKIP, fist char of new string cannot be mapped again,
- * but abbreviations are allowed.
- * If noremap is REMAP_SCRIPT, new string cannot be mapped again, except for
- * script-local mappings.
- * If noremap is > 0, that many characters of the new string cannot be mapped.
- *
- * If nottyped is TRUE, the string does not return KeyTyped (don't use when
- * offset is non-zero!).
- *
- * If silent is true, cmd_silent is set when the characters are obtained.
- *
- * return FAIL for failure, OK otherwise
- */
-int ins_typebuf(char_u *str, int noremap, int offset, int nottyped, bool silent)
+// Insert a string in position 'offset' in the typeahead buffer (for "@r"
+// and ":normal" command, vgetorpeek() and check_termcode())
+//
+// If noremap is REMAP_YES, new string can be mapped again.
+// If noremap is REMAP_NONE, new string cannot be mapped again.
+// If noremap is REMAP_SKIP, fist char of new string cannot be mapped again,
+// but abbreviations are allowed.
+// If noremap is REMAP_SCRIPT, new string cannot be mapped again, except for
+// script-local mappings.
+// If noremap is > 0, that many characters of the new string cannot be mapped.
+//
+// If nottyped is true, the string does not return KeyTyped (don't use when
+// offset is non-zero!).
+//
+// If silent is true, cmd_silent is set when the characters are obtained.
+//
+// return FAIL for failure, OK otherwise
+int ins_typebuf(char_u *str, int noremap, int offset,
+ bool nottyped, bool silent)
{
char_u *s1, *s2;
int newlen;
@@ -890,8 +891,8 @@ int ins_typebuf(char_u *str, int noremap, int offset, int nottyped, bool silent)
// often.
newoff = MAXMAPLEN + 4;
newlen = typebuf.tb_len + addlen + newoff + 4 * (MAXMAPLEN + 4);
- if (newlen < 0) { /* string is getting too long */
- EMSG(_(e_toocompl)); /* also calls flush_buffers */
+ if (newlen < 0) { // string is getting too long
+ EMSG(_(e_toocompl)); // also calls flush_buffers
setcursor();
return FAIL;
}
@@ -927,13 +928,14 @@ int ins_typebuf(char_u *str, int noremap, int offset, int nottyped, bool silent)
}
typebuf.tb_len += addlen;
- /* If noremap == REMAP_SCRIPT: do remap script-local mappings. */
- if (noremap == REMAP_SCRIPT)
+ // If noremap == REMAP_SCRIPT: do remap script-local mappings.
+ if (noremap == REMAP_SCRIPT) {
val = RM_SCRIPT;
- else if (noremap == REMAP_SKIP)
+ } else if (noremap == REMAP_SKIP) {
val = RM_ABBR;
- else
+ } else {
val = RM_NONE;
+ }
/*
* Adjust typebuf.tb_noremap[] for the new characters:
@@ -962,8 +964,9 @@ int ins_typebuf(char_u *str, int noremap, int offset, int nottyped, bool silent)
typebuf.tb_silent += addlen;
cmd_silent = true;
}
- if (typebuf.tb_no_abbr_cnt && offset == 0) /* and not used for abbrev.s */
+ if (typebuf.tb_no_abbr_cnt && offset == 0) { // and not used for abbrev.s
typebuf.tb_no_abbr_cnt += addlen;
+ }
return OK;
}
@@ -997,9 +1000,8 @@ void ins_char_typebuf(int c)
* Or "typebuf.tb_off" may have been changed and we would overwrite characters
* that was just added.
*/
-int
-typebuf_changed (
- int tb_change_cnt /* old value of typebuf.tb_change_cnt */
+bool typebuf_changed(
+ int tb_change_cnt // old value of typebuf.tb_change_cnt
)
{
return tb_change_cnt != 0 && (typebuf.tb_change_cnt != tb_change_cnt
@@ -1031,8 +1033,9 @@ void del_typebuf(int len, int offset)
{
int i;
- if (len == 0)
- return; /* nothing to do */
+ if (len == 0) {
+ return; // nothing to do
+ }
typebuf.tb_len -= len;
@@ -1068,23 +1071,26 @@ void del_typebuf(int len, int offset)
(size_t)(typebuf.tb_len - offset));
}
- if (typebuf.tb_maplen > offset) { /* adjust tb_maplen */
- if (typebuf.tb_maplen < offset + len)
+ if (typebuf.tb_maplen > offset) { // adjust tb_maplen
+ if (typebuf.tb_maplen < offset + len) {
typebuf.tb_maplen = offset;
- else
+ } else {
typebuf.tb_maplen -= len;
+ }
}
- if (typebuf.tb_silent > offset) { /* adjust tb_silent */
- if (typebuf.tb_silent < offset + len)
+ if (typebuf.tb_silent > offset) { // adjust tb_silent
+ if (typebuf.tb_silent < offset + len) {
typebuf.tb_silent = offset;
- else
+ } else {
typebuf.tb_silent -= len;
+ }
}
- if (typebuf.tb_no_abbr_cnt > offset) { /* adjust tb_no_abbr_cnt */
- if (typebuf.tb_no_abbr_cnt < offset + len)
+ if (typebuf.tb_no_abbr_cnt > offset) { // adjust tb_no_abbr_cnt
+ if (typebuf.tb_no_abbr_cnt < offset + len) {
typebuf.tb_no_abbr_cnt = offset;
- else
+ } else {
typebuf.tb_no_abbr_cnt -= len;
+ }
}
/* Reset the flag that text received from a client or from feedkeys()
@@ -1135,8 +1141,8 @@ static void gotchars(const char_u *chars, size_t len)
may_sync_undo();
- /* output "debug mode" message next time in debug mode */
- debug_did_msg = FALSE;
+ // output "debug mode" message next time in debug mode
+ debug_did_msg = false;
/* Since characters have been typed, consider the following to be in
* another mapping. Search string will be kept in history. */
@@ -1253,10 +1259,9 @@ void restore_typeahead(tasave_T *tp)
/*
* Open a new script file for the ":source!" command.
*/
-void
-openscript (
+void openscript(
char_u *name,
- int directly /* when TRUE execute directly */
+ bool directly // when true execute directly
)
{
if (curscript + 1 == NSCRIPT) {
@@ -1275,9 +1280,10 @@ openscript (
return;
}
- if (scriptin[curscript] != NULL) /* already reading script */
- ++curscript;
- /* use NameBuff for expanded name */
+ if (scriptin[curscript] != NULL) { // already reading script
+ curscript++;
+ }
+ // use NameBuff for expanded name
expand_env(name, NameBuff, MAXPATHL);
int error;
if ((scriptin[curscript] = file_open_new(&error, (char *)NameBuff,
@@ -1306,11 +1312,11 @@ openscript (
int save_msg_scroll = msg_scroll;
State = NORMAL;
- msg_scroll = FALSE; /* no msg scrolling in Normal mode */
- restart_edit = 0; /* don't go to Insert mode */
- p_im = FALSE; /* don't use 'insertmode' */
+ msg_scroll = false; // no msg scrolling in Normal mode
+ restart_edit = 0; // don't go to Insert mode
+ p_im = false; // don't use 'insertmode'
clear_oparg(&oa);
- finish_op = FALSE;
+ finish_op = false;
oldcurscript = curscript;
do {
@@ -1626,10 +1632,8 @@ int char_avail(void)
return retval != NUL;
}
-void
-vungetc ( /* unget one character (can only be done once!) */
- int c
-)
+// unget one character (can only be done once!)
+void vungetc(int c)
{
old_char = c;
old_mod_mask = mod_mask;
@@ -1669,10 +1673,10 @@ static int vgetorpeek(bool advance)
mapblock_T *mp2;
mapblock_T *mp_match;
int mp_match_len = 0;
- int timedout = FALSE; /* waited for more than 1 second
- for mapping to complete */
- int mapdepth = 0; /* check for recursive mapping */
- int mode_deleted = FALSE; /* set when mode has been deleted */
+ bool timedout = false; // waited for more than 1 second
+ // for mapping to complete
+ int mapdepth = 0; // check for recursive mapping
+ bool mode_deleted = false; // set when mode has been deleted
int local_State;
int mlen;
int max_mlen;
@@ -1729,8 +1733,9 @@ static int vgetorpeek(bool advance)
// needed for CTRL-W CTRL-] to open a fold, for example.
KeyStuffed = true;
}
- if (typebuf.tb_no_abbr_cnt == 0)
- typebuf.tb_no_abbr_cnt = 1; /* no abbreviations now */
+ if (typebuf.tb_no_abbr_cnt == 0) {
+ typebuf.tb_no_abbr_cnt = 1; // no abbreviations now
+ }
} else {
/*
* Loop until we either find a matching mapped key, or we
@@ -1744,10 +1749,11 @@ static int vgetorpeek(bool advance)
* inside a mapping. But call it each time for typed
* characters.
*/
- if (typebuf.tb_maplen)
+ if (typebuf.tb_maplen) {
line_breakcheck();
- else
- os_breakcheck(); /* check for CTRL-C */
+ } else {
+ os_breakcheck(); // check for CTRL-C
+ }
keylen = 0;
if (got_int) {
// flush all input
@@ -1814,11 +1820,11 @@ static int vgetorpeek(bool advance)
&& get_real_state() != SELECTMODE);
nolmaplen = 0;
}
- /* First try buffer-local mappings. */
+ // First try buffer-local mappings.
mp = curbuf->b_maphash[MAP_HASH(local_State, c1)];
mp2 = maphash[MAP_HASH(local_State, c1)];
if (mp == NULL) {
- /* There are no buffer-local mappings. */
+ // There are no buffer-local mappings.
mp = mp2;
mp2 = NULL;
}
@@ -1845,8 +1851,8 @@ static int vgetorpeek(bool advance)
|| typebuf.tb_maplen == 0)) {
int nomap = nolmaplen;
int c2;
- /* find the match length of this mapping */
- for (mlen = 1; mlen < typebuf.tb_len; ++mlen) {
+ // find the match length of this mapping
+ for (mlen = 1; mlen < typebuf.tb_len; mlen++) {
c2 = typebuf.tb_buf[typebuf.tb_off + mlen];
if (nomap > 0)
--nomap;
@@ -1901,7 +1907,7 @@ static int vgetorpeek(bool advance)
if (keylen > typebuf.tb_len) {
if (!timedout && !(mp_match != NULL
&& mp_match->m_nowait)) {
- /* break at a partly match */
+ // break at a partly match
keylen = KEYLEN_PART_MAP;
break;
}
@@ -1953,13 +1959,14 @@ static int vgetorpeek(bool advance)
setcursor();
continue;
}
- /* Need more chars for partly match. */
- if (mlen == typebuf.tb_len)
+ // Need more chars for partly match.
+ if (mlen == typebuf.tb_len) {
keylen = KEYLEN_PART_KEY;
- else if (max_mlen < mlen)
- /* no match, may have to check for termcode at
- * next character */
+ } else if (max_mlen < mlen) {
+ // no match, may have to check for termcode at
+ // next character
max_mlen = mlen + 1;
+ }
}
if ((mp == NULL || max_mlen >= mp_match_len)
@@ -1991,13 +1998,11 @@ static int vgetorpeek(bool advance)
}
}
- /* complete match */
+ // complete match
if (keylen >= 0 && keylen <= typebuf.tb_len) {
int save_m_expr;
int save_m_noremap;
int save_m_silent;
- char_u *save_m_keys;
- char_u *save_m_str;
// Write chars to script file(s)
// Note: :lmap mappings are written *after* being applied. #5658
@@ -2007,7 +2012,7 @@ static int vgetorpeek(bool advance)
}
cmd_silent = (typebuf.tb_silent > 0);
- del_typebuf(keylen, 0); /* remove the mapped keys */
+ del_typebuf(keylen, 0); // remove the mapped keys
/*
* Put the replacement string in front of mapstr.
@@ -2032,9 +2037,8 @@ static int vgetorpeek(bool advance)
*/
if (VIsual_active && VIsual_select
&& (mp->m_mode & VISUAL)) {
- VIsual_select = FALSE;
- (void)ins_typebuf(K_SELECT_STRING, REMAP_NONE,
- 0, TRUE, FALSE);
+ VIsual_select = false;
+ (void)ins_typebuf(K_SELECT_STRING, REMAP_NONE, 0, true, false);
}
/* Copy the values from *mp that are used, because
@@ -2044,8 +2048,8 @@ static int vgetorpeek(bool advance)
save_m_expr = mp->m_expr;
save_m_noremap = mp->m_noremap;
save_m_silent = mp->m_silent;
- save_m_keys = NULL; /* only saved when needed */
- save_m_str = NULL; /* only saved when needed */
+ char_u *save_m_keys = NULL; // only saved when needed
+ char_u *save_m_str = NULL; // only saved when needed
/*
* Handle ":map <expr>": evaluate the {rhs} as an
@@ -2141,14 +2145,14 @@ static int vgetorpeek(bool advance)
char_u *ptr;
if (mode_displayed) {
- unshowmode(TRUE);
- mode_deleted = TRUE;
+ unshowmode(true);
+ mode_deleted = true;
}
validate_cursor();
old_wcol = curwin->w_wcol;
old_wrow = curwin->w_wrow;
- /* move cursor left, if possible */
+ // move cursor left, if possible
if (curwin->w_cursor.col != 0) {
if (curwin->w_wcol > 0) {
if (did_ai) {
@@ -2170,7 +2174,7 @@ static int vgetorpeek(bool advance)
+ curwin->w_wcol / curwin->w_width_inner;
curwin->w_wcol %= curwin->w_width_inner;
curwin->w_wcol += curwin_col_off();
- col = 0; /* no correction needed */
+ col = 0; // no correction needed
} else {
--curwin->w_wcol;
col = curwin->w_cursor.col - 1;
@@ -2197,8 +2201,9 @@ static int vgetorpeek(bool advance)
curwin->w_wcol = old_wcol;
curwin->w_wrow = old_wrow;
}
- if (c < 0)
- continue; /* end of input script reached */
+ if (c < 0) {
+ continue; // end of input script reached
+ }
// Allow mapping for just typed characters. When we get here c
// is the number of extra bytes and typebuf.tb_len is 1.
@@ -2207,20 +2212,20 @@ static int vgetorpeek(bool advance)
}
typebuf.tb_len += c;
- /* buffer full, don't map */
+ // buffer full, don't map
if (typebuf.tb_len >= typebuf.tb_maplen + MAXMAPLEN) {
- timedout = TRUE;
+ timedout = true;
continue;
}
if (ex_normal_busy > 0) {
static int tc = 0;
- /* No typeahead left and inside ":normal". Must return
- * something to avoid getting stuck. When an incomplete
- * mapping is present, behave like it timed out. */
+ // No typeahead left and inside ":normal". Must return
+ // something to avoid getting stuck. When an incomplete
+ // mapping is present, behave like it timed out.
if (typebuf.tb_len > 0) {
- timedout = TRUE;
+ timedout = true;
continue;
}
/* When 'insertmode' is set, ESC just beeps in Insert
@@ -2254,7 +2259,7 @@ static int vgetorpeek(bool advance)
if (((State & INSERT) != 0 || p_lz) && (State & CMDLINE) == 0
&& advance && must_redraw != 0 && !need_wait_return) {
update_screen(0);
- setcursor(); /* put cursor back where it belongs */
+ setcursor(); // put cursor back where it belongs
}
/*
@@ -2267,16 +2272,16 @@ static int vgetorpeek(bool advance)
if (typebuf.tb_len > 0 && advance && !exmode_active) {
if (((State & (NORMAL | INSERT)) || State == LANGMAP)
&& State != HITRETURN) {
- /* this looks nice when typing a dead character map */
+ // this looks nice when typing a dead character map
if (State & INSERT
&& ptr2cells(typebuf.tb_buf + typebuf.tb_off
- + typebuf.tb_len - 1) == 1) {
- edit_putchar(typebuf.tb_buf[typebuf.tb_off
- + typebuf.tb_len - 1], FALSE);
- setcursor(); /* put cursor back where it belongs */
+ + typebuf.tb_len - 1) == 1) {
+ edit_putchar(typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len - 1],
+ false);
+ setcursor(); // put cursor back where it belongs
c1 = 1;
}
- /* need to use the col and row from above here */
+ // need to use the col and row from above here
old_wcol = curwin->w_wcol;
old_wrow = curwin->w_wrow;
curwin->w_wcol = new_wcol;
@@ -2332,31 +2337,34 @@ static int vgetorpeek(bool advance)
if (i != 0)
pop_showcmd();
if (c1 == 1) {
- if (State & INSERT)
+ if (State & INSERT) {
edit_unputchar();
- if (State & CMDLINE)
+ }
+ if (State & CMDLINE) {
unputcmdline();
- else
- setcursor(); /* put cursor back where it belongs */
+ } else {
+ setcursor(); // put cursor back where it belongs
+ }
}
- if (c < 0)
- continue; /* end of input script reached */
- if (c == NUL) { /* no character available */
- if (!advance)
+ if (c < 0) {
+ continue; // end of input script reached
+ }
+ if (c == NUL) { // no character available
+ if (!advance) {
break;
- if (wait_tb_len > 0) { /* timed out */
- timedout = TRUE;
+ }
+ if (wait_tb_len > 0) { // timed out
+ timedout = true;
continue;
}
- } else { /* allow mapping for just typed characters */
- while (typebuf.tb_buf[typebuf.tb_off
- + typebuf.tb_len] != NUL)
- typebuf.tb_noremap[typebuf.tb_off
- + typebuf.tb_len++] = RM_YES;
+ } else { // allow mapping for just typed characters
+ while (typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len] != NUL) {
+ typebuf.tb_noremap[typebuf.tb_off + typebuf.tb_len++] = RM_YES;
+ }
}
- } /* for (;;) */
- } /* if (!character from stuffbuf) */
+ } // for (;;)
+ } // if (!character from stuffbuf)
// if advance is false don't loop on NULs
} while (c < 0 || (advance && c == NUL));
@@ -2368,15 +2376,17 @@ static int vgetorpeek(bool advance)
*/
if (advance && p_smd && msg_silent == 0 && (State & INSERT)) {
if (c == ESC && !mode_deleted && !no_mapping && mode_displayed) {
- if (typebuf.tb_len && !KeyTyped)
- redraw_cmdline = TRUE; /* delete mode later */
- else
- unshowmode(FALSE);
+ if (typebuf.tb_len && !KeyTyped) {
+ redraw_cmdline = true; // delete mode later
+ } else {
+ unshowmode(false);
+ }
} else if (c != ESC && mode_deleted) {
- if (typebuf.tb_len && !KeyTyped)
- redraw_cmdline = TRUE; /* show mode later */
- else
+ if (typebuf.tb_len && !KeyTyped) {
+ redraw_cmdline = true; // show mode later
+ } else {
showmode();
+ }
}
}
@@ -2440,8 +2450,8 @@ int inchar(
* on char wait, flush swapfile, write error....).
*/
if (State != HITRETURN) {
- did_outofmem_msg = FALSE; /* display out of memory message (again) */
- did_swapwrite_msg = FALSE; /* display swap file write error again */
+ did_outofmem_msg = false; // display out of memory message (again)
+ did_swapwrite_msg = false; // display swap file write error again
}
// Get a character from a script file if there is one.
@@ -3175,7 +3185,7 @@ static void validate_maphash(void)
/*
* Get the mapping mode from the command name.
*/
-int get_map_mode(char_u **cmdp, int forceit)
+int get_map_mode(char_u **cmdp, bool forceit)
{
char_u *p;
int modec;
@@ -3183,30 +3193,31 @@ int get_map_mode(char_u **cmdp, int forceit)
p = *cmdp;
modec = *p++;
- if (modec == 'i')
- mode = INSERT; /* :imap */
- else if (modec == 'l')
- mode = LANGMAP; /* :lmap */
- else if (modec == 'c')
- mode = CMDLINE; /* :cmap */
- else if (modec == 'n' && *p != 'o') /* avoid :noremap */
- mode = NORMAL; /* :nmap */
- else if (modec == 'v')
- mode = VISUAL + SELECTMODE; /* :vmap */
- else if (modec == 'x')
- mode = VISUAL; /* :xmap */
- else if (modec == 's')
- mode = SELECTMODE; /* :smap */
- else if (modec == 'o')
- mode = OP_PENDING; /* :omap */
- else if (modec == 't')
- mode = TERM_FOCUS; // :tmap
- else {
- --p;
- if (forceit)
- mode = INSERT + CMDLINE; /* :map ! */
- else
- mode = VISUAL + SELECTMODE + NORMAL + OP_PENDING; /* :map */
+ if (modec == 'i') {
+ mode = INSERT; // :imap
+ } else if (modec == 'l') {
+ mode = LANGMAP; // :lmap
+ } else if (modec == 'c') {
+ mode = CMDLINE; // :cmap
+ } else if (modec == 'n' && *p != 'o') { // avoid :noremap
+ mode = NORMAL; // :nmap
+ } else if (modec == 'v') {
+ mode = VISUAL + SELECTMODE; // :vmap
+ } else if (modec == 'x') {
+ mode = VISUAL; // :xmap
+ } else if (modec == 's') {
+ mode = SELECTMODE; // :smap
+ } else if (modec == 'o') {
+ mode = OP_PENDING; // :omap
+ } else if (modec == 't') {
+ mode = TERM_FOCUS; // :tmap
+ } else {
+ p--;
+ if (forceit) {
+ mode = INSERT + CMDLINE; // :map !
+ } else {
+ mode = VISUAL + SELECTMODE + NORMAL + OP_PENDING; // :map
+ }
}
*cmdp = p;
@@ -3237,12 +3248,11 @@ void map_clear_mode(char_u *cmdp, char_u *arg, int forceit, int abbr)
/*
* Clear all mappings in "mode".
*/
-void
-map_clear_int (
- buf_T *buf, /* buffer for local mappings */
- int mode, /* mode in which to delete */
- int local, /* TRUE for buffer-local mappings */
- int abbr /* TRUE for abbreviations */
+void map_clear_int(
+ buf_T *buf, // buffer for local mappings
+ int mode, // mode in which to delete
+ bool local, // true for buffer-local mappings
+ bool abbr // true for abbreviations
)
{
mapblock_T *mp, **mpp;
@@ -3253,12 +3263,14 @@ map_clear_int (
for (hash = 0; hash < 256; ++hash) {
if (abbr) {
- if (hash > 0) /* there is only one abbrlist */
+ if (hash > 0) { // there is only one abbrlist
break;
- if (local)
+ }
+ if (local) {
mpp = &buf->b_first_abbr;
- else
+ } else {
mpp = &first_abbr;
+ }
} else {
if (local)
mpp = &buf->b_maphash[hash];
@@ -3286,7 +3298,7 @@ map_clear_int (
mp->m_next = maphash[new_hash];
maphash[new_hash] = mp;
}
- continue; /* continue with *mpp */
+ continue; // continue with *mpp
}
}
mpp = &(mp->m_next);
@@ -3341,10 +3353,9 @@ char *map_mode_to_chars(int mode)
return (char *)mapmode.ga_data;
}
-static void
-showmap (
+static void showmap(
mapblock_T *mp,
- int local /* TRUE for buffer-local map */
+ bool local // true for buffer-local map
)
{
size_t len = 1;
@@ -3355,8 +3366,9 @@ showmap (
if (msg_didout || msg_silent != 0) {
msg_putchar('\n');
- if (got_int) /* 'q' typed at MORE prompt */
+ if (got_int) { // 'q' typed at MORE prompt
return;
+ }
}
{
@@ -3372,8 +3384,8 @@ showmap (
// Display the LHS. Get length of what we write.
len = (size_t)msg_outtrans_special(mp->m_keys, true, 0);
do {
- msg_putchar(' '); /* padd with blanks */
- ++len;
+ msg_putchar(' '); // padd with blanks
+ len++;
} while (len < 12);
if (mp->m_noremap == REMAP_NONE) {
@@ -3506,21 +3518,20 @@ int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr)
* Used below when expanding mapping/abbreviation names.
*/
static int expand_mapmodes = 0;
-static int expand_isabbrev = 0;
-static int expand_buffer = FALSE;
+static bool expand_isabbrev = false;
+static bool expand_buffer = false;
/*
* Work out what to complete when doing command line completion of mapping
* or abbreviation names.
*/
-char_u *
-set_context_in_map_cmd (
+char_u *set_context_in_map_cmd(
expand_T *xp,
char_u *cmd,
char_u *arg,
- int forceit, /* TRUE if '!' given */
- int isabbrev, /* TRUE if abbreviation */
- int isunmap, /* TRUE if unmap/unabbrev command */
+ bool forceit, // true if '!' given
+ bool isabbrev, // true if abbreviation
+ bool isunmap, // true if unmap/unabbrev command
cmdidx_T cmdidx
)
{
@@ -3536,10 +3547,10 @@ set_context_in_map_cmd (
}
expand_isabbrev = isabbrev;
xp->xp_context = EXPAND_MAPPINGS;
- expand_buffer = FALSE;
+ expand_buffer = false;
for (;; ) {
if (STRNCMP(arg, "<buffer>", 8) == 0) {
- expand_buffer = TRUE;
+ expand_buffer = true;
arg = skipwhite(arg + 8);
continue;
}
@@ -3589,7 +3600,7 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file)
validate_maphash();
- *num_file = 0; /* return values in case of FAIL */
+ *num_file = 0; // return values in case of FAIL
*file = NULL;
/*
@@ -3628,8 +3639,9 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file)
for (hash = 0; hash < 256; ++hash) {
if (expand_isabbrev) {
- if (hash > 0) /* only one abbrev list */
- break; /* for (hash) */
+ if (hash > 0) { // only one abbrev list
+ break; // for (hash)
+ }
mp = first_abbr;
} else if (expand_buffer)
mp = curbuf->b_maphash[hash];
@@ -3648,26 +3660,27 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file)
}
xfree(p);
}
- } /* for (mp) */
- } /* for (hash) */
+ } // for (mp)
+ } // for (hash)
- if (count == 0) /* no match found */
- break; /* for (round) */
+ if (count == 0) { // no match found
+ break; // for (round)
+ }
if (round == 1) {
*file = (char_u **)xmalloc((size_t)count * sizeof(char_u *));
}
- } /* for (round) */
+ } // for (round)
if (count > 1) {
char_u **ptr1;
char_u **ptr2;
char_u **ptr3;
- /* Sort the matches */
+ // Sort the matches
sort_strings(*file, count);
- /* Remove multiple entries */
+ // Remove multiple entries
ptr1 = *file;
ptr2 = ptr1 + 1;
ptr3 = ptr1 + count;
@@ -3705,7 +3718,7 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file)
bool check_abbr(int c, char_u *ptr, int col, int mincol)
{
int len;
- int scol; /* starting column of the abbr. */
+ int scol; // starting column of the abbr.
int j;
char_u *s;
char_u tb[MB_MAXBYTES + 4];
@@ -3756,7 +3769,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol)
if (scol < mincol)
scol = mincol;
- if (scol < col) { /* there is a word in front of the cursor */
+ if (scol < col) { // there is a word in front of the cursor
ptr += scol;
len = col - scol;
mp = curbuf->b_first_abbr;
@@ -3778,7 +3791,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol)
vim_unescape_csi(q);
qlen = (int)STRLEN(q);
}
- /* find entries with right mode and keys */
+ // find entries with right mode and keys
match = (mp->m_mode & State)
&& qlen == len
&& !STRNCMP(q, ptr, (size_t)len);
@@ -3805,7 +3818,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol)
*/
j = 0;
if (c != Ctrl_RSB) {
- /* special key code, split up */
+ // special key code, split up
if (IS_SPECIAL(c) || c == K_SPECIAL) {
tb[j++] = K_SPECIAL;
tb[j++] = (char_u)K_SECOND(c);
@@ -3821,17 +3834,17 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol)
j += utf_char2bytes(c, tb + j);
}
tb[j] = NUL;
- /* insert the last typed char */
- (void)ins_typebuf(tb, 1, 0, TRUE, mp->m_silent);
+ // insert the last typed char
+ (void)ins_typebuf(tb, 1, 0, true, mp->m_silent);
}
if (mp->m_expr)
s = eval_map_expr(mp->m_str, c);
else
s = mp->m_str;
if (s != NULL) {
- /* insert the to string */
- (void)ins_typebuf(s, mp->m_noremap, 0, TRUE, mp->m_silent);
- /* no abbrev. for these chars */
+ // insert the to string
+ (void)ins_typebuf(s, mp->m_noremap, 0, true, mp->m_silent);
+ // no abbrev. for these chars
typebuf.tb_no_abbr_cnt += (int)STRLEN(s) + j + 1;
if (mp->m_expr)
xfree(s);
@@ -3856,7 +3869,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol)
static char_u *
eval_map_expr (
char_u *str,
- int c /* NUL or typed character for abbreviation */
+ int c // NUL or typed character for abbreviation
)
{
char_u *res;
@@ -3874,11 +3887,11 @@ eval_map_expr (
save_cmd = save_cmdline_alloc();
- /* Forbid changing text or using ":normal" to avoid most of the bad side
- * effects. Also restore the cursor position. */
- ++textlock;
- ++ex_normal_lock;
- set_vim_var_char(c); /* set v:char to the typed character */
+ // Forbid changing text or using ":normal" to avoid most of the bad side
+ // effects. Also restore the cursor position.
+ textlock++;
+ ex_normal_lock++;
+ set_vim_var_char(c); // set v:char to the typed character
save_cursor = curwin->w_cursor;
save_msg_col = msg_col;
save_msg_row = msg_row;
@@ -3894,7 +3907,7 @@ eval_map_expr (
if (p == NULL)
return NULL;
- /* Escape CSI in the result to be able to use the string as typeahead. */
+ // Escape CSI in the result to be able to use the string as typeahead.
res = vim_strsave_escape_csi(p);
xfree(p);
@@ -3914,7 +3927,7 @@ char_u *vim_strsave_escape_csi(char_u *p)
char_u *d = res;
for (char_u *s = p; *s != NUL; ) {
if (s[0] == K_SPECIAL && s[1] != NUL && s[2] != NUL) {
- /* Copy special key unmodified. */
+ // Copy special key unmodified.
*d++ = *s++;
*d++ = *s++;
*d++ = *s++;
@@ -4213,9 +4226,10 @@ int put_escstr(FILE *fd, char_u *strstart, int what)
c = TO_SPECIAL(str[1], str[2]);
str += 2;
}
- if (IS_SPECIAL(c) || modifiers) { /* special key */
- if (fputs((char *)get_special_key_name(c, modifiers), fd) < 0)
+ if (IS_SPECIAL(c) || modifiers) { // special key
+ if (fputs((char *)get_special_key_name(c, modifiers), fd) < 0) {
return FAIL;
+ }
continue;
}
}
@@ -4271,35 +4285,36 @@ char_u *
check_map (
char_u *keys,
int mode,
- int exact, /* require exact match */
- int ign_mod, /* ignore preceding modifier */
- int abbr, /* do abbreviations */
- mapblock_T **mp_ptr, /* return: pointer to mapblock or NULL */
- int *local_ptr /* return: buffer-local mapping or NULL */
+ int exact, // require exact match
+ int ign_mod, // ignore preceding modifier
+ int abbr, // do abbreviations
+ mapblock_T **mp_ptr, // return: pointer to mapblock or NULL
+ int *local_ptr // return: buffer-local mapping or NULL
)
{
- int hash;
int len, minlen;
mapblock_T *mp;
- int local;
validate_maphash();
len = (int)STRLEN(keys);
- for (local = 1; local >= 0; --local)
- /* loop over all hash lists */
- for (hash = 0; hash < 256; ++hash) {
+ for (int local = 1; local >= 0; local--) {
+ // loop over all hash lists
+ for (int hash = 0; hash < 256; hash++) {
if (abbr) {
- if (hash > 0) /* there is only one list. */
+ if (hash > 0) { // there is only one list.
break;
- if (local)
+ }
+ if (local) {
mp = curbuf->b_first_abbr;
- else
+ } else {
mp = first_abbr;
- } else if (local)
+ }
+ } else if (local) {
mp = curbuf->b_maphash[hash];
- else
+ } else {
mp = maphash[hash];
+ }
for (; mp != NULL; mp = mp->m_next) {
/* skip entries with wrong mode, wrong length and not matching
* ones */
@@ -4322,6 +4337,7 @@ check_map (
}
}
}
+ }
return NULL;
}
@@ -4336,7 +4352,7 @@ void add_map(char_u *map, int mode)
char_u *s;
char_u *cpo_save = p_cpo;
- p_cpo = (char_u *)""; /* Allow <> notation */
+ p_cpo = (char_u *)""; // Allow <> notation
s = vim_strsave(map);
(void)do_map(0, s, mode, FALSE);
xfree(s);
@@ -4384,7 +4400,7 @@ static char_u * translate_mapping (
}
if (IS_SPECIAL(c) || modifiers) { // special key
ga_concat(&ga, get_special_key_name(c, modifiers));
- continue; /* for (str) */
+ continue; // for (str)
}
}
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 3b8f4116b7..624b7c93f3 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -180,6 +180,11 @@ EXTERN int compl_cont_status INIT(= 0);
# define CONT_LOCAL 32 // for ctrl_x_mode 0, ^X^P/^X^N do a local
// expansion, (eg use complete=.)
+EXTERN char_u *edit_submode INIT(= NULL); // msg for CTRL-X submode
+EXTERN char_u *edit_submode_pre INIT(= NULL); // prepended to edit_submode
+EXTERN char_u *edit_submode_extra INIT(= NULL); // appended to edit_submode
+EXTERN hlf_T edit_submode_highl; // highl. method for extra info
+
// state for putting characters in the message area
EXTERN int cmdmsg_rl INIT(= false); // cmdline is drawn right to left
EXTERN int msg_col;
@@ -251,7 +256,7 @@ EXTERN linenr_T sourcing_lnum INIT(= 0); // line number of the source file
EXTERN int ex_nesting_level INIT(= 0); // nesting level
EXTERN int debug_break_level INIT(= -1); // break below this level
-EXTERN int debug_did_msg INIT(= false); // did "debug mode" message
+EXTERN bool debug_did_msg INIT(= false); // did "debug mode" message
EXTERN int debug_tick INIT(= 0); // breakpoint change count
EXTERN int debug_backtrace_level INIT(= 0); // breakpoint backtrace level
@@ -328,9 +333,10 @@ EXTERN int garbage_collect_at_exit INIT(= false);
#define SID_ENV -4 // for sourcing environment variable
#define SID_ERROR -5 // option was reset because of an error
#define SID_NONE -6 // don't set scriptID
-#define SID_LUA -7 // for Lua scripts/chunks
-#define SID_API_CLIENT -8 // for API clients
-#define SID_STR -9 // for sourcing a string
+#define SID_WINLAYOUT -7 // changing window size
+#define SID_LUA -8 // for Lua scripts/chunks
+#define SID_API_CLIENT -9 // for API clients
+#define SID_STR -10 // for sourcing a string
// Script CTX being sourced or was sourced to define the current function.
EXTERN sctx_T current_sctx INIT(= { 0 COMMA 0 COMMA 0 });
@@ -463,7 +469,7 @@ EXTERN buf_T *curbuf INIT(= NULL); // currently active buffer
// Iterate through all the signs placed in a buffer
#define FOR_ALL_SIGNS_IN_BUF(buf, sign) \
- for (sign = buf->b_signlist; sign != NULL; sign = sign->next) // NOLINT
+ for (sign = buf->b_signlist; sign != NULL; sign = sign->se_next) // NOLINT
// List of files being edited (global argument list). curwin->w_alist points
@@ -639,10 +645,6 @@ EXTERN int arrow_used; // Normally false, set to true after
// to call u_sync()
EXTERN bool ins_at_eol INIT(= false); // put cursor after eol when
// restarting edit after CTRL-O
-EXTERN char_u *edit_submode INIT(= NULL); // msg for CTRL-X submode
-EXTERN char_u *edit_submode_pre INIT(= NULL); // prepended to edit_submode
-EXTERN char_u *edit_submode_extra INIT(= NULL); // appended to edit_submode
-EXTERN hlf_T edit_submode_highl; // highl. method for extra info
EXTERN int no_abbr INIT(= true); // true when no abbreviations loaded
@@ -1009,6 +1011,8 @@ EXTERN char_u e_floatonly[] INIT(=N_(
EXTERN char_u e_floatexchange[] INIT(=N_(
"E5602: Cannot exchange or rotate float"));
+EXTERN char e_cannot_define_autocommands_for_all_events[] INIT(= N_(
+ "E1155: Cannot define autocommands for ALL events"));
EXTERN char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM"));
EXTERN char bot_top_msg[] INIT(= N_("search hit BOTTOM, continuing at TOP"));
diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h
index e14aae73d8..3b34af46e4 100644
--- a/src/nvim/grid_defs.h
+++ b/src/nvim/grid_defs.h
@@ -7,7 +7,7 @@
#include "nvim/types.h"
-#define MAX_MCO 6 // maximum value for 'maxcombine'
+#define MAX_MCO 6 // fixed value for 'maxcombine'
// The characters and attributes drawn on grids.
typedef char_u schar_T[(MAX_MCO+1) * 4 + 1];
@@ -35,7 +35,8 @@ typedef int sattr_T;
/// line_wraps[] is an array of boolean flags indicating if the screen line
/// wraps to the next line. It can only be true if a window occupies the entire
/// screen width.
-typedef struct {
+typedef struct ScreenGrid ScreenGrid;
+struct ScreenGrid {
handle_T handle;
schar_T *chars;
@@ -58,10 +59,13 @@ typedef struct {
// external UI.
bool throttled;
- // offsets for the grid relative to the global screen. Used by screen.c
- // for windows that don't have w_grid->chars etc allocated
+ // TODO(bfredl): maybe physical grids and "views" (i e drawing
+ // specifications) should be two separate types?
+ // offsets for the grid relative to another grid. Used for grids
+ // that are views into another, actually allocated grid 'target'
int row_offset;
int col_offset;
+ ScreenGrid *target;
// whether the compositor should blend the grid with the background grid
bool blending;
@@ -76,6 +80,12 @@ typedef struct {
int comp_row;
int comp_col;
+ // Requested width and height of the grid upon resize. Used by
+ // `ui_compositor` to correctly determine which regions need to
+ // be redrawn.
+ int comp_width;
+ int comp_height;
+
// z-index of the grid. Grids with higher index is draw on top.
// default_grid.comp_index is always zero.
size_t comp_index;
@@ -83,9 +93,10 @@ typedef struct {
// compositor should momentarily ignore the grid. Used internally when
// moving around grids etc.
bool comp_disabled;
-} ScreenGrid;
+};
#define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, NULL, 0, 0, false, \
- false, 0, 0, false, true, 0, 0, 0, false }
+ false, 0, 0, NULL, false, true, \
+ 0, 0, 0, 0, 0, false }
#endif // NVIM_GRID_DEFS_H
diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c
index 4ec949759c..abba5425e7 100644
--- a/src/nvim/hardcopy.c
+++ b/src/nvim/hardcopy.c
@@ -890,8 +890,11 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T
* Appropriately expand any tabs to spaces.
*/
if (line[col] == TAB || tab_spaces != 0) {
- if (tab_spaces == 0)
- tab_spaces = (int)(curbuf->b_p_ts - (print_pos % curbuf->b_p_ts));
+ if (tab_spaces == 0) {
+ tab_spaces = tabstop_padding(print_pos,
+ curbuf->b_p_ts,
+ curbuf->b_p_vts_array);
+ }
while (tab_spaces > 0) {
need_break = mch_print_text_out((char_u *)" ", 1);
diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c
index b01cdde236..79801262cb 100644
--- a/src/nvim/highlight.c
+++ b/src/nvim/highlight.c
@@ -8,6 +8,7 @@
#include "nvim/highlight_defs.h"
#include "nvim/map.h"
#include "nvim/message.h"
+#include "nvim/option.h"
#include "nvim/popupmnu.h"
#include "nvim/screen.h"
#include "nvim/syntax.h"
@@ -151,7 +152,7 @@ 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)
{
- DecorProvider *p = get_provider(ns_id, true);
+ 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;
@@ -175,7 +176,7 @@ int ns_get_hl(NS ns_id, int hl_id, bool link, bool nodefault)
ns_id = ns_hl_active;
}
- DecorProvider *p = get_provider(ns_id, true);
+ DecorProvider *p = get_decor_provider(ns_id, true);
ColorItem it = map_get(ColorKey, ColorItem)(ns_hl, ColorKey(ns_id, hl_id));
// TODO(bfredl): map_ref true even this?
bool valid_cache = it.version >= p->hl_valid;
@@ -341,6 +342,25 @@ void update_window_hl(win_T *wp, bool invalid)
}
wp->w_hl_attrs[hlf] = attr;
}
+
+ wp->w_float_config.shadow = false;
+ if (wp->w_floating && wp->w_float_config.border) {
+ for (int i = 0; i < 8; i++) {
+ int attr = wp->w_hl_attrs[HLF_BORDER];
+ if (wp->w_float_config.border_hl_ids[i]) {
+ attr = hl_get_ui_attr(HLF_BORDER, wp->w_float_config.border_hl_ids[i],
+ false);
+ HlAttrs a = syn_attr2entry(attr);
+ if (a.hl_blend) {
+ wp->w_float_config.shadow = true;
+ }
+ }
+ wp->w_float_config.border_attr[i] = attr;
+ }
+ }
+
+ // shadow might cause blending
+ check_blending(wp);
}
/// Gets HL_UNDERLINE highlight.
@@ -517,6 +537,10 @@ static HlAttrs get_colors_force(int attr)
/// @return the resulting attributes.
int hl_blend_attrs(int back_attr, int front_attr, bool *through)
{
+ if (front_attr < 0 || back_attr < 0) {
+ return -1;
+ }
+
HlAttrs fattrs = get_colors_force(front_attr);
int ratio = fattrs.hl_blend;
if (ratio <= 0) {
diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h
index 2bda094d8e..ed4aefb577 100644
--- a/src/nvim/highlight_defs.h
+++ b/src/nvim/highlight_defs.h
@@ -101,6 +101,7 @@ typedef enum {
, HLF_MSGSEP // message separator line
, HLF_NFLOAT // Floating window
, HLF_MSG // Message area
+ , HLF_BORDER // Floating window border
, HLF_COUNT // MUST be the last one
} hlf_T;
@@ -155,6 +156,7 @@ EXTERN const char *hlf_names[] INIT(= {
[HLF_MSGSEP] = "MsgSeparator",
[HLF_NFLOAT] = "NormalFloat",
[HLF_MSG] = "MsgArea",
+ [HLF_BORDER] = "FloatBorder",
});
diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c
index 2dad8fb781..31615e744a 100644
--- a/src/nvim/if_cscope.c
+++ b/src/nvim/if_cscope.c
@@ -1865,7 +1865,7 @@ static void cs_release_csp(size_t i, bool freefnpp)
alive = false; // cscope process no longer exists
break;
}
- os_delay(50L, false); // sleep 50ms
+ os_delay(50L, false); // sleep 50 ms
}
}
if (alive)
diff --git a/src/nvim/indent.c b/src/nvim/indent.c
index fae971b3b3..8fa61515ef 100644
--- a/src/nvim/indent.c
+++ b/src/nvim/indent.c
@@ -34,14 +34,20 @@
// Count the size (in window cells) of the indent in the current line.
int get_indent(void)
{
- return get_indent_str(get_cursor_line_ptr(), (int)curbuf->b_p_ts, false);
+ return get_indent_str_vtab(get_cursor_line_ptr(),
+ curbuf->b_p_ts,
+ curbuf->b_p_vts_array,
+ false);
}
// Count the size (in window cells) of the indent in line "lnum".
int get_indent_lnum(linenr_T lnum)
{
- return get_indent_str(ml_get(lnum), (int)curbuf->b_p_ts, false);
+ return get_indent_str_vtab(ml_get(lnum),
+ curbuf->b_p_ts,
+ curbuf->b_p_vts_array,
+ false);
}
@@ -49,7 +55,10 @@ int get_indent_lnum(linenr_T lnum)
// "buf".
int get_indent_buf(buf_T *buf, linenr_T lnum)
{
- return get_indent_str(ml_get_buf(buf, lnum, false), (int)buf->b_p_ts, false);
+ return get_indent_str_vtab(ml_get_buf(buf, lnum, false),
+ curbuf->b_p_ts,
+ buf->b_p_vts_array,
+ false);
}
@@ -82,6 +91,30 @@ int get_indent_str(const char_u *ptr, int ts, int list)
return count;
}
+// Count the size (in window cells) of the indent in line "ptr", using
+// variable tabstops.
+// if "list" is true, count only screen size for tabs.
+int get_indent_str_vtab(const char_u *ptr, long ts, long *vts, bool list)
+{
+ int count = 0;
+
+ for (; *ptr; ptr++) {
+ if (*ptr == TAB) { // count a tab for what it is worth
+ if (!list || curwin->w_p_lcs_chars.tab1) {
+ count += tabstop_padding(count, ts, vts);
+ } else {
+ // In list mode, when tab is not set, count screen char width
+ // for Tab, displays: ^I
+ count += ptr2cells(ptr);
+ }
+ } else if (*ptr == ' ') {
+ count++; // count a space for one
+ } else {
+ break;
+ }
+ }
+ return count;
+}
// Set the indent of the current line.
// Leaves the cursor on the first non-blank in the line.
@@ -104,6 +137,7 @@ int set_indent(int size, int flags)
int line_len;
int doit = false;
int ind_done = 0; // Measured in spaces.
+ int ind_col = 0;
int tab_pad;
int retval = false;
@@ -130,7 +164,9 @@ int set_indent(int size, int flags)
// Count as many characters as we can use.
while (todo > 0 && ascii_iswhite(*p)) {
if (*p == TAB) {
- tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts);
+ tab_pad = tabstop_padding(ind_done,
+ curbuf->b_p_ts,
+ curbuf->b_p_vts_array);
// Stop if this tab will overshoot the target.
if (todo < tab_pad) {
@@ -147,35 +183,41 @@ int set_indent(int size, int flags)
p++;
}
+ // These diverge from this point.
+ ind_col = ind_done;
// Set initial number of whitespace chars to copy if we are
// preserving indent but expandtab is set.
if (curbuf->b_p_et) {
orig_char_len = ind_len;
}
-
// Fill to next tabstop with a tab, if possible.
- tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts);
-
+ tab_pad = tabstop_padding(ind_done,
+ curbuf->b_p_ts,
+ curbuf->b_p_vts_array);
if ((todo >= tab_pad) && (orig_char_len == -1)) {
doit = true;
todo -= tab_pad;
ind_len++;
// ind_done += tab_pad;
+ ind_col += tab_pad;
}
}
// Count tabs required for indent.
- while (todo >= (int)curbuf->b_p_ts) {
+ for (;;) {
+ tab_pad = tabstop_padding(ind_col, curbuf->b_p_ts, curbuf->b_p_vts_array);
+ if (todo < tab_pad) {
+ break;
+ }
if (*p != TAB) {
doit = true;
} else {
p++;
}
- todo -= (int)curbuf->b_p_ts;
+ todo -= tab_pad;
ind_len++;
-
- // ind_done += (int)curbuf->b_p_ts;
+ ind_col += tab_pad;
}
}
@@ -255,7 +297,9 @@ int set_indent(int size, int flags)
while (todo > 0 && ascii_iswhite(*p)) {
if (*p == TAB) {
- tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts);
+ tab_pad = tabstop_padding(ind_done,
+ curbuf->b_p_ts,
+ curbuf->b_p_vts_array);
// Stop if this tab will overshoot the target.
if (todo < tab_pad) {
@@ -272,18 +316,28 @@ int set_indent(int size, int flags)
}
// Fill to next tabstop with a tab, if possible.
- tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts);
+ tab_pad = tabstop_padding(ind_done,
+ curbuf->b_p_ts,
+ curbuf->b_p_vts_array);
if (todo >= tab_pad) {
*s++ = TAB;
todo -= tab_pad;
+ ind_done += tab_pad;
}
p = skipwhite(p);
}
- while (todo >= (int)curbuf->b_p_ts) {
+ for (;;) {
+ tab_pad = tabstop_padding(ind_done,
+ curbuf->b_p_ts,
+ curbuf->b_p_vts_array);
+ if (todo < tab_pad) {
+ break;
+ }
*s++ = TAB;
- todo -= (int)curbuf->b_p_ts;
+ todo -= tab_pad;
+ ind_done += tab_pad;
}
}
@@ -375,11 +429,9 @@ int get_number_indent(linenr_T lnum)
return (int)col;
}
-/*
- * Return appropriate space number for breakindent, taking influencing
- * parameters into account. Window must be specified, since it is not
- * necessarily always the current one.
- */
+// Return appropriate space number for breakindent, taking influencing
+// parameters into account. Window must be specified, since it is not
+// necessarily always the current one.
int get_breakindent_win(win_T *wp, const char_u *line)
FUNC_ATTR_NONNULL_ALL
{
@@ -387,6 +439,7 @@ int get_breakindent_win(win_T *wp, const char_u *line)
static long prev_ts = 0; // Cached tabstop value.
static const char_u *prev_line = NULL; // cached pointer to line.
static varnumber_T prev_tick = 0; // Changedtick of cached value.
+ static long *prev_vts = NULL; // Cached vartabs values.
int bri = 0;
// window width minus window margin space, i.e. what rests for text
const int eff_wwidth = wp->w_width_inner
@@ -396,11 +449,16 @@ int get_breakindent_win(win_T *wp, const char_u *line)
// used cached indent, unless pointer or 'tabstop' changed
if (prev_line != line || prev_ts != wp->w_buffer->b_p_ts
- || prev_tick != buf_get_changedtick(wp->w_buffer)) {
+ || prev_tick != buf_get_changedtick(wp->w_buffer)
+ || prev_vts != wp->w_buffer->b_p_vts_array) {
prev_line = line;
prev_ts = wp->w_buffer->b_p_ts;
prev_tick = buf_get_changedtick(wp->w_buffer);
- prev_indent = get_indent_str(line, (int)wp->w_buffer->b_p_ts, wp->w_p_list);
+ prev_vts = wp->w_buffer->b_p_vts_array;
+ prev_indent = get_indent_str_vtab(line,
+ wp->w_buffer->b_p_ts,
+ wp->w_buffer->b_p_vts_array,
+ wp->w_p_list);
}
bri = prev_indent + wp->w_briopt_shift;
diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c
index 9298e57411..771bf923b2 100644
--- a/src/nvim/indent_c.c
+++ b/src/nvim/indent_c.c
@@ -33,15 +33,12 @@ typedef struct {
* Search starts at w_cursor.lnum and goes backwards.
* Return NULL when not inside a comment.
*/
-static pos_T *ind_find_start_comment(void)
-{ /* XXX */
+static pos_T *ind_find_start_comment(void) // XXX
+{
return find_start_comment(curbuf->b_ind_maxcomment);
}
-pos_T *
-find_start_comment ( /* XXX */
- int ind_maxcomment
-)
+pos_T *find_start_comment(int ind_maxcomment) // XXX
{
pos_T *pos;
char_u *line;
@@ -109,8 +106,8 @@ static pos_T *ind_find_start_CORS(linenr_T *is_raw)
* Search starts at w_cursor.lnum and goes backwards.
* Return NULL when not inside a raw string.
*/
-static pos_T *find_start_rawstring(int ind_maxcomment)
-{ /* XXX */
+static pos_T *find_start_rawstring(int ind_maxcomment) // XXX
+{
pos_T *pos;
char_u *line;
char_u *p;
@@ -152,31 +149,35 @@ static char_u *skip_string(char_u *p)
/*
* We loop, because strings may be concatenated: "date""time".
*/
- for (;; ++p) {
- if (p[0] == '\'') { /* 'c' or '\n' or '\000' */
- if (!p[1]) /* ' at end of line */
+ for (;; p++) {
+ if (p[0] == '\'') { // 'c' or '\n' or '\000'
+ if (!p[1]) { // ' at end of line
break;
+ }
i = 2;
- if (p[1] == '\\') { /* '\n' or '\000' */
- ++i;
- while (ascii_isdigit(p[i - 1])) /* '\000' */
- ++i;
+ if (p[1] == '\\') { // '\n' or '\000'
+ i++;
+ while (ascii_isdigit(p[i - 1])) { // '\000'
+ i++;
+ }
}
- if (p[i] == '\'') { /* check for trailing ' */
+ if (p[i] == '\'') { // check for trailing '
p += i;
continue;
}
- } else if (p[0] == '"') { /* start of string */
- for (++p; p[0]; ++p) {
- if (p[0] == '\\' && p[1] != NUL)
- ++p;
- else if (p[0] == '"') /* end of string */
+ } else if (p[0] == '"') { // start of string
+ for (++p; p[0]; p++) {
+ if (p[0] == '\\' && p[1] != NUL) {
+ p++;
+ } else if (p[0] == '"') { // end of string
break;
+ }
+ }
+ if (p[0] == '"') {
+ continue; // continue for another string
}
- if (p[0] == '"')
- continue; /* continue for another string */
} else if (p[0] == 'R' && p[1] == '"') {
- /* Raw string: R"[delim](...)[delim]" */
+ // Raw string: R"[delim](...)[delim]"
char_u *delim = p + 2;
char_u *paren = vim_strchr(delim, '(');
@@ -190,14 +191,16 @@ static char_u *skip_string(char_u *p)
p += delim_len + 1;
break;
}
- if (p[0] == '"')
- continue; /* continue for another string */
+ if (p[0] == '"') {
+ continue; // continue for another string
+ }
}
}
- break; /* no string found */
+ break; // no string found
+ }
+ if (!*p) {
+ p--; // backup from NUL
}
- if (!*p)
- --p; /* backup from NUL */
return p;
}
@@ -255,20 +258,22 @@ static char_u *cin_skipcomment(char_u *s)
s += STRLEN(s);
break;
}
- if (*s != '/')
+ if (*s != '/') {
break;
- ++s;
- if (*s == '/') { /* slash-slash comment continues till eol */
+ }
+ s++;
+ if (*s == '/') { // slash-slash comment continues till eol
s += STRLEN(s);
break;
}
if (*s != '*')
break;
- for (++s; *s; ++s) /* skip slash-star comment */
+ for (++s; *s; s++) { // skip slash-star comment
if (s[0] == '*' && s[1] == '/') {
s += 2;
break;
}
+ }
}
return s;
}
@@ -285,7 +290,7 @@ static int cin_nocode(char_u *s)
/*
* Check previous lines for a "//" line comment, skipping over blank lines.
*/
-static pos_T *find_line_comment(void) /* XXX */
+static pos_T *find_line_comment(void) // XXX
{
static pos_T pos;
char_u *line;
@@ -335,39 +340,38 @@ 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 int cin_islabel_skip(char_u **s)
+static bool cin_islabel_skip(char_u **s)
+ FUNC_ATTR_NONNULL_ALL
{
- if (!vim_isIDc(**s)) /* need at least one ID character */
- return FALSE;
+ if (!vim_isIDc(**s)) { // need at least one ID character
+ return false;
+ }
while (vim_isIDc(**s))
(*s)++;
*s = cin_skipcomment(*s);
- /* "::" is not a label, it's C++ */
+ // "::" is not a label, it's C++
return **s == ':' && *++*s != ':';
}
-/*
- * Recognize a label: "label:".
- * Note: curwin->w_cursor must be where we are looking for the label.
- */
-int cin_islabel(void)
-{ /* XXX */
+// Recognize a label: "label:".
+// 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());
- /*
- * Exclude "default" from labels, since it should be indented
- * like a switch label. Same for C++ scope declarations.
- */
- if (cin_isdefault(s))
- return FALSE;
- if (cin_isscopedecl(s))
- return FALSE;
-
+ // Exclude "default" from labels, since it should be indented
+ // like a switch label. Same for C++ scope declarations.
+ if (cin_isdefault(s)) {
+ return false;
+ }
+ if (cin_isscopedecl(s)) {
+ return false;
+ }
if (!cin_islabel_skip(&s)) {
- return FALSE;
+ return false;
}
/*
@@ -392,21 +396,24 @@ int cin_islabel(void)
}
line = get_cursor_line_ptr();
- if (cin_ispreproc(line)) /* ignore #defines, #if, etc. */
+ if (cin_ispreproc(line)) { // ignore #defines, #if, etc.
continue;
- if (*(line = cin_skipcomment(line)) == NUL)
+ }
+ if (*(line = cin_skipcomment(line)) == NUL) {
continue;
+ }
curwin->w_cursor = cursor_save;
if (cin_isterminated(line, TRUE, FALSE)
|| cin_isscopedecl(line)
- || cin_iscase(line, TRUE)
- || (cin_islabel_skip(&line) && cin_nocode(line)))
- return TRUE;
- return FALSE;
+ || cin_iscase(line, true)
+ || (cin_islabel_skip(&line) && cin_nocode(line))) {
+ return true;
+ }
+ return false;
}
curwin->w_cursor = cursor_save;
- return TRUE; /* label at start of file??? */
+ return true; // label at start of file???
}
/*
@@ -451,10 +458,9 @@ static int cin_isinit(void)
/*
* Recognize a switch label: "case .*:" or "default:".
*/
-int
-cin_iscase (
+bool cin_iscase(
char_u *s,
- int strict /* Allow relaxed check of case statement for JS */
+ bool strict // Allow relaxed check of case statement for JS
)
{
s = cin_skipcomment(s);
@@ -465,29 +471,32 @@ cin_iscase (
break;
}
if (*s == ':') {
- if (s[1] == ':') /* skip over "::" for C++ */
- ++s;
- else
- return TRUE;
+ if (s[1] == ':') { // skip over "::" for C++
+ s++;
+ } else {
+ return true;
+ }
}
- if (*s == '\'' && s[1] && s[2] == '\'')
- s += 2; /* skip over ':' */
- else if (*s == '/' && (s[1] == '*' || s[1] == '/'))
- return FALSE; /* stop at comment */
- else if (*s == '"') {
- /* JS etc. */
- if (strict)
- return FALSE; /* stop at string */
- else
- return TRUE;
+ if (*s == '\'' && s[1] && s[2] == '\'') {
+ s += 2; // skip over ':'
+ } else if (*s == '/' && (s[1] == '*' || s[1] == '/')) {
+ return false; // stop at comment
+ } else if (*s == '"') {
+ // JS etc.
+ if (strict) {
+ return false; // stop at string
+ } else {
+ return true;
+ }
}
}
- return FALSE;
+ return false;
}
- if (cin_isdefault(s))
- return TRUE;
- return FALSE;
+ if (cin_isdefault(s)) {
+ return true;
+ }
+ return false;
}
/*
@@ -503,23 +512,24 @@ static int cin_isdefault(char_u *s)
/*
* Recognize a "public/private/protected" scope declaration label.
*/
-int cin_isscopedecl(char_u *s)
+bool cin_isscopedecl(char_u *s)
{
int i;
s = cin_skipcomment(s);
- if (STRNCMP(s, "public", 6) == 0)
+ if (STRNCMP(s, "public", 6) == 0) {
i = 6;
- else if (STRNCMP(s, "protected", 9) == 0)
+ } else if (STRNCMP(s, "protected", 9) == 0) {
i = 9;
- else if (STRNCMP(s, "private", 7) == 0)
+ } else if (STRNCMP(s, "private", 7) == 0) {
i = 7;
- else
- return FALSE;
+ } else {
+ return false;
+ }
return *(s = cin_skipcomment(s + i)) == ':' && s[1] != ':';
}
-/* Maximum number of lines to search back for a "namespace" line. */
+// Maximum number of lines to search back for a "namespace" line.
#define FIND_NAMESPACE_LIM 20
// Recognize a "namespace" scope declaration.
@@ -569,12 +579,14 @@ static char_u *after_label(char_u *l)
{
for (; *l; ++l) {
if (*l == ':') {
- if (l[1] == ':') /* skip over "::" for C++ */
- ++l;
- else if (!cin_iscase(l + 1, FALSE))
+ if (l[1] == ':') { // skip over "::" for C++
+ l++;
+ } else if (!cin_iscase(l + 1, false)) {
break;
- } else if (*l == '\'' && l[1] && l[2] == '\'')
- l += 2; /* skip over 'x' */
+ }
+ } else if (*l == '\'' && l[1] && l[2] == '\'') {
+ l += 2; // skip over 'x'
+ }
}
if (*l == NUL)
return NULL;
@@ -588,10 +600,7 @@ static char_u *after_label(char_u *l)
* Get indent of line "lnum", skipping a label.
* Return 0 if there is nothing after the label.
*/
-static int
-get_indent_nolabel ( /* XXX */
- linenr_T lnum
-)
+static int get_indent_nolabel(linenr_T lnum) // XXX
{
char_u *l;
pos_T fp;
@@ -624,12 +633,13 @@ static int skip_label(linenr_T lnum, char_u **pp)
cursor_save = curwin->w_cursor;
curwin->w_cursor.lnum = lnum;
l = get_cursor_line_ptr();
- /* XXX */
- if (cin_iscase(l, FALSE) || cin_isscopedecl(l) || cin_islabel()) {
+ // XXX
+ if (cin_iscase(l, false) || cin_isscopedecl(l) || cin_islabel()) {
amount = get_indent_nolabel(lnum);
l = after_label(get_cursor_line_ptr());
- if (l == NULL) /* just in case */
+ if (l == NULL) { // just in case
l = get_cursor_line_ptr();
+ }
} else {
amount = get_indent();
l = get_cursor_line_ptr();
@@ -710,10 +720,11 @@ static int cin_get_equal_amount(linenr_T lnum)
line = s = ml_get(lnum);
while (*s != NUL && vim_strchr((char_u *)"=;{}\"'", *s) == NULL) {
- if (cin_iscomment(s)) /* ignore comments */
+ if (cin_iscomment(s)) { // ignore comments
s = cin_skipcomment(s);
- else
- ++s;
+ } else {
+ s++;
+ }
}
if (*s != '=')
return 0;
@@ -722,8 +733,9 @@ static int cin_get_equal_amount(linenr_T lnum)
if (cin_nocode(s))
return 0;
- if (*s == '"') /* nice alignment for continued strings */
- ++s;
+ if (*s == '"') { // nice alignment for continued strings
+ s++;
+ }
fp.lnum = lnum;
fp.col = (colnr_T)(s - line);
@@ -806,8 +818,8 @@ static int cin_islinecomment(char_u *p)
static char_u
cin_isterminated (
char_u *s,
- int incl_open, /* include '{' at the end as terminator */
- int incl_comma /* recognize a trailing comma */
+ int incl_open, // include '{' at the end as terminator
+ int incl_comma // recognize a trailing comma
)
{
char_u found_start = 0;
@@ -823,7 +835,7 @@ cin_isterminated (
is_else = cin_iselse(s);
while (*s) {
- /* skip over comments, "" strings and 'c'haracters */
+ // skip over comments, "" strings and 'c'haracters
s = skip_string(cin_skipcomment(s));
if (*s == '}' && n_open > 0)
--n_open;
@@ -942,12 +954,12 @@ static int cin_isfuncdecl(char_u **sp, linenr_T first_lnum, linenr_T min_lnum)
s = skipwhite(s);
if (!just_started && (!comma && *s != ',' && *s != ')'))
break;
- just_started = FALSE;
- } else if (cin_iscomment(s)) /* ignore comments */
+ just_started = false;
+ } else if (cin_iscomment(s)) { // ignore comments
s = cin_skipcomment(s);
- else {
- ++s;
- just_started = FALSE;
+ } else {
+ s++;
+ just_started = false;
}
}
@@ -965,8 +977,9 @@ static int cin_isif(char_u *p)
static int cin_iselse(char_u *p)
{
- if (*p == '}') /* accept "} else" */
+ if (*p == '}') { // accept "} else"
p = cin_skipcomment(p + 1);
+ }
return STRNCMP(p, "else", 4) == 0 && !vim_isIDc(p[4]);
}
@@ -980,27 +993,24 @@ 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 ( /* XXX */
- char_u *p,
- linenr_T lnum
-)
+static int cin_iswhileofdo(char_u *p, linenr_T lnum) // XXX
{
pos_T cursor_save;
pos_T *trypos;
int retval = FALSE;
p = cin_skipcomment(p);
- if (*p == '}') /* accept "} while (cond);" */
+ if (*p == '}') { // accept "} while (cond);"
p = cin_skipcomment(p + 1);
+ }
if (cin_starts_with(p, "while")) {
cursor_save = curwin->w_cursor;
curwin->w_cursor.lnum = lnum;
curwin->w_cursor.col = 0;
p = get_cursor_line_ptr();
- while (*p && *p != 'w') { /* skip any '}', until the 'w' of the "while" */
- ++p;
- ++curwin->w_cursor.col;
+ while (*p && *p != 'w') { // skip any '}', until the 'w' of the "while"
+ p++;
+ curwin->w_cursor.col++;
}
if ((trypos = findmatchlimit(NULL, 0, 0,
curbuf->b_ind_maxparen)) != NULL
@@ -1067,8 +1077,9 @@ static int cin_iswhileofdo_end(int terminated)
pos_T *trypos;
int i;
- if (terminated != ';') /* there must be a ';' at the end */
- return FALSE;
+ if (terminated != ';') { // there must be a ';' at the end
+ return false;
+ }
p = line = get_cursor_line_ptr();
while (*p != NUL) {
@@ -1083,15 +1094,16 @@ static int cin_iswhileofdo_end(int terminated)
trypos = find_match_paren(curbuf->b_ind_maxparen);
if (trypos != NULL) {
s = cin_skipcomment(ml_get(trypos->lnum));
- if (*s == '}') /* accept "} while (cond);" */
+ if (*s == '}') { // accept "} while (cond);"
s = cin_skipcomment(s + 1);
+ }
if (cin_starts_with(s, "while")) {
curwin->w_cursor.lnum = trypos->lnum;
return TRUE;
}
}
- /* Searching may have made "line" invalid, get it again. */
+ // Searching may have made "line" invalid, get it again.
line = get_cursor_line_ptr();
p = line + i;
}
@@ -1134,8 +1146,9 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) {
pos->col = 0;
s = skipwhite(line);
- if (*s == '#') /* skip #define FOO x ? (x) : x */
- return FALSE;
+ if (*s == '#') { // skip #define FOO x ? (x) : x
+ return false;
+ }
s = cin_skipcomment(s);
if (*s == NUL)
return FALSE;
@@ -1230,23 +1243,23 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) {
class_or_struct = FALSE;
lookfor_ctor_init = TRUE;
} else if (s[0] == '?') {
- /* Avoid seeing '() :' after '?' as constructor init. */
- return FALSE;
+ // Avoid seeing '() :' after '?' as constructor init.
+ return false;
} else if (!vim_isIDc(s[0])) {
- /* if it is not an identifier, we are wrong */
+ // if it is not an identifier, we are wrong
class_or_struct = false;
lookfor_ctor_init = false;
} else if (pos->col == 0) {
- /* it can't be a constructor-initialization any more */
- lookfor_ctor_init = FALSE;
+ // it can't be a constructor-initialization any more
+ lookfor_ctor_init = false;
- /* the first statement starts here: lineup with this one... */
+ // the first statement starts here: lineup with this one...
if (cpp_base_class) {
pos->col = (colnr_T)(s - line);
}
}
- /* When the line ends in a comma don't align with it. */
+ // When the line ends in a comma don't align with it.
if (lnum == curwin->w_cursor.lnum && *s == ',' && cin_nocode(s + 1)) {
pos->col = 0;
}
@@ -1271,10 +1284,12 @@ static int get_baseclass_amount(int col)
if (col == 0) {
amount = get_indent();
if (find_last_paren(get_cursor_line_ptr(), '(', ')')
- && (trypos = find_match_paren(curbuf->b_ind_maxparen)) != NULL)
- amount = get_indent_lnum(trypos->lnum); /* XXX */
- if (!cin_ends_in(get_cursor_line_ptr(), (char_u *)",", NULL))
+ && (trypos = find_match_paren(curbuf->b_ind_maxparen)) != NULL) {
+ amount = get_indent_lnum(trypos->lnum); // XXX
+ }
+ if (!cin_ends_in(get_cursor_line_ptr(), (char_u *)",", NULL)) {
amount += curbuf->b_ind_cpp_baseclass;
+ }
} else {
curwin->w_cursor.col = col;
getvcol(curwin, &curwin->w_cursor, &vcol, NULL, NULL);
@@ -1389,12 +1404,12 @@ static int cin_skip2pos(pos_T *trypos)
* Return NULL if no match found.
* Ignore a '{' that is in a comment, makes indenting the next three lines
* work. */
-/* foo() */
-/* { */
-/* } */
+// foo()
+// {
+// }
-static pos_T *find_start_brace(void)
-{ /* XXX */
+static pos_T *find_start_brace(void) // XXX
+{
pos_T cursor_save;
pos_T *trypos;
pos_T *pos;
@@ -1402,11 +1417,11 @@ static pos_T *find_start_brace(void)
cursor_save = curwin->w_cursor;
while ((trypos = findmatchlimit(NULL, '{', FM_BLOCKSTOP, 0)) != NULL) {
- pos_copy = *trypos; /* copy pos_T, next findmatch will change it */
+ pos_copy = *trypos; // copy pos_T, next findmatch will change it
trypos = &pos_copy;
curwin->w_cursor = *trypos;
pos = NULL;
- /* ignore the { if it's in a // or / * * / comment */
+ // ignore the { if it's in a // or / * * / comment
if ((colnr_T)cin_skip2pos(trypos) == trypos->col
&& (pos = ind_find_start_CORS(NULL)) == NULL) { // XXX
break;
@@ -1449,7 +1464,7 @@ retry:
} else {
pos_T *trypos_wk;
- pos_copy = *trypos; /* copy trypos, findmatch will change it */
+ pos_copy = *trypos; // copy trypos, findmatch will change it
trypos = &pos_copy;
curwin->w_cursor = *trypos;
if ((trypos_wk = ind_find_start_CORS(NULL)) != NULL) { // XXX
@@ -1515,17 +1530,17 @@ static int find_last_paren(char_u *l, int start, int end)
int retval = FALSE;
int open_count = 0;
- curwin->w_cursor.col = 0; /* default is start of line */
+ curwin->w_cursor.col = 0; // default is start of line
for (i = 0; l[i] != NUL; i++) {
- i = (int)(cin_skipcomment(l + i) - l); /* ignore parens in comments */
- i = (int)(skip_string(l + i) - l); /* ignore parens in quotes */
- if (l[i] == start)
- ++open_count;
- else if (l[i] == end) {
- if (open_count > 0)
- --open_count;
- else {
+ i = (int)(cin_skipcomment(l + i) - l); // ignore parens in comments
+ i = (int)(skip_string(l + i) - l); // ignore parens in quotes
+ if (l[i] == start) {
+ open_count++;
+ } else if (l[i] == end) {
+ if (open_count > 0) {
+ open_count--;
+ } else {
curwin->w_cursor.col = i;
retval = TRUE;
}
@@ -1561,7 +1576,7 @@ void parse_cino(buf_T *buf)
* an opening brace. */
buf->b_ind_no_brace = 0;
- /* Column where the first { of a function should be located }. */
+ // Column where the first { of a function should be located }.
buf->b_ind_first_open = 0;
/* Spaces from the prevailing indent a leftmost open brace should be
@@ -1581,26 +1596,26 @@ void parse_cino(buf_T *buf)
* otherwise the jump label will be put to column 1. */
buf->b_ind_jump_label = -1;
- /* Spaces from the switch() indent a "case xx" label should be located. */
+ // Spaces from the switch() indent a "case xx" label should be located.
buf->b_ind_case = sw;
- /* Spaces from the "case xx:" code after a switch() should be located. */
+ // Spaces from the "case xx:" code after a switch() should be located.
buf->b_ind_case_code = sw;
- /* Lineup break at end of case in switch() with case label. */
+ // Lineup break at end of case in switch() with case label.
buf->b_ind_case_break = 0;
/* Spaces from the class declaration indent a scope declaration label
* should be located. */
buf->b_ind_scopedecl = sw;
- /* Spaces from the scope declaration label code should be located. */
+ // Spaces from the scope declaration label code should be located.
buf->b_ind_scopedecl_code = sw;
- /* Amount K&R-style parameters should be indented. */
+ // Amount K&R-style parameters should be indented.
buf->b_ind_param = sw;
- /* Amount a function type spec should be indented. */
+ // Amount a function type spec should be indented.
buf->b_ind_func_type = sw;
/* Amount a cpp base class declaration or constructor initialization
@@ -1611,7 +1626,7 @@ void parse_cino(buf_T *buf)
* should be located. */
buf->b_ind_continuation = sw;
- /* Spaces from the indent of the line with an unclosed parentheses. */
+ // Spaces from the indent of the line with an unclosed parentheses.
buf->b_ind_unclosed = sw * 2;
/* Spaces from the indent of the line with an unclosed parentheses, which
@@ -1635,35 +1650,35 @@ void parse_cino(buf_T *buf)
* opening parentheses. */
buf->b_ind_matching_paren = 0;
- /* Indent a closing parentheses under the previous line. */
+ // Indent a closing parentheses under the previous line.
buf->b_ind_paren_prev = 0;
- /* Extra indent for comments. */
+ // Extra indent for comments.
buf->b_ind_comment = 0;
- /* Spaces from the comment opener when there is nothing after it. */
+ // Spaces from the comment opener when there is nothing after it.
buf->b_ind_in_comment = 3;
/* Boolean: if non-zero, use b_ind_in_comment even if there is something
* after the comment opener. */
buf->b_ind_in_comment2 = 0;
- /* Max lines to search for an open paren. */
+ // Max lines to search for an open paren.
buf->b_ind_maxparen = 20;
- /* Max lines to search for an open comment. */
+ // Max lines to search for an open comment.
buf->b_ind_maxcomment = 70;
- /* Handle braces for java code. */
+ // Handle braces for java code.
buf->b_ind_java = 0;
- /* Not to confuse JS object properties with labels. */
+ // Not to confuse JS object properties with labels.
buf->b_ind_js = 0;
- /* Handle blocked cases correctly. */
+ // Handle blocked cases correctly.
buf->b_ind_keep_case_label = 0;
- /* Handle C++ namespace. */
+ // Handle C++ namespace.
buf->b_ind_cpp_namespace = 0;
/* Handle continuation lines containing conditions of if(), for() and
@@ -1777,9 +1792,9 @@ int get_c_indent(void)
pos_T our_paren_pos;
char_u *start;
int start_brace;
-#define BRACE_IN_COL0 1 /* '{' is in column 0 */
-#define BRACE_AT_START 2 /* '{' is at start of line */
-#define BRACE_AT_END 3 /* '{' is at end of line */
+#define BRACE_IN_COL0 1 // '{' is in column 0
+#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;
@@ -1802,24 +1817,24 @@ int get_c_indent(void)
int whilelevel;
linenr_T lnum;
int n;
- int iscase;
int lookfor_break;
- int lookfor_cpp_namespace = FALSE;
- int cont_amount = 0; /* amount for continuation line */
+ bool lookfor_cpp_namespace = false;
+ int cont_amount = 0; // amount for continuation line
int original_line_islabel;
int added_to_amount = 0;
linenr_T raw_string_start = 0;
cpp_baseclass_cache_T cache_cpp_baseclass = { false, { MAXLNUM, 0 } };
- /* make a copy, value is changed below */
+ // make a copy, value is changed below
int ind_continuation = curbuf->b_ind_continuation;
- /* remember where the cursor was when we started */
+ // remember where the cursor was when we started
cur_curpos = curwin->w_cursor;
- /* if we are at line 1 zero indent is fine, right? */
- if (cur_curpos.lnum == 1)
+ // if we are at line 1 zero indent is fine, right?
+ if (cur_curpos.lnum == 1) {
return 0;
+ }
/* Get a copy of the current contents of the line.
* This is required, because only the most recent line obtained with
@@ -1840,11 +1855,11 @@ int get_c_indent(void)
theline = skipwhite(linecopy);
- /* move the cursor to the start of the line */
+ // move the cursor to the start of the line
curwin->w_cursor.col = 0;
- original_line_islabel = cin_islabel(); /* XXX */
+ original_line_islabel = cin_islabel(); // XXX
/*
* If we are inside a raw string don't change the indent.
@@ -1852,7 +1867,7 @@ int get_c_indent(void)
*/
comment_pos = ind_find_start_comment();
if (comment_pos != NULL) {
- /* findmatchlimit() static pos is overwritten, make a copy */
+ // findmatchlimit() static pos is overwritten, make a copy
tryposCopy = *comment_pos;
comment_pos = &tryposCopy;
}
@@ -1887,8 +1902,8 @@ int get_c_indent(void)
* 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 */
+ && (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;
@@ -1897,18 +1912,18 @@ int get_c_indent(void)
* If we're inside a comment and not looking at the start of the
* comment, try using the 'comments' option.
*/
- if (!cin_iscomment(theline) && comment_pos != NULL) { /* XXX */
+ if (!cin_iscomment(theline) && comment_pos != NULL) { // XXX
int lead_start_len = 2;
int lead_middle_len = 1;
- char_u lead_start[COM_MAX_LEN]; /* start-comment string */
- char_u lead_middle[COM_MAX_LEN]; /* middle-comment string */
- char_u lead_end[COM_MAX_LEN]; /* end-comment string */
+ char_u lead_start[COM_MAX_LEN]; // start-comment string
+ char_u lead_middle[COM_MAX_LEN]; // middle-comment string
+ char_u lead_end[COM_MAX_LEN]; // end-comment string
char_u *p;
int start_align = 0;
int start_off = 0;
int done = FALSE;
- /* find how indented the line beginning the comment is */
+ // find how indented the line beginning the comment is
getvcol(curwin, comment_pos, &col, NULL, NULL);
amount = col;
*lead_start = NUL;
@@ -1981,13 +1996,13 @@ int get_c_indent(void)
if (STRNCMP(theline, lead_middle, lead_middle_len) != 0
&& STRNCMP(theline, lead_end, STRLEN(lead_end)) == 0) {
amount = get_indent_lnum(curwin->w_cursor.lnum - 1);
- /* XXX */
- if (off != 0)
+ // XXX
+ if (off != 0) {
amount += off;
- else if (align == COM_RIGHT)
- amount += vim_strsize(lead_start)
- - vim_strsize(lead_middle);
- done = TRUE;
+ } else if (align == COM_RIGHT) {
+ amount += vim_strsize(lead_start) - vim_strsize(lead_middle);
+ }
+ done = true;
break;
}
}
@@ -2010,18 +2025,20 @@ int get_c_indent(void)
* otherwise, add the amount specified by "c" in 'cino'
*/
amount = -1;
- for (lnum = cur_curpos.lnum - 1; lnum > comment_pos->lnum; --lnum) {
- if (linewhite(lnum)) /* skip blank lines */
+ for (lnum = cur_curpos.lnum - 1; lnum > comment_pos->lnum; lnum--) {
+ if (linewhite(lnum)) { // skip blank lines
continue;
- amount = get_indent_lnum(lnum); /* XXX */
+ }
+ amount = get_indent_lnum(lnum); // XXX
break;
}
- if (amount == -1) { /* use the comment opener */
+ if (amount == -1) { // use the comment opener
if (!curbuf->b_ind_in_comment2) {
- start = ml_get(comment_pos->lnum);
- look = start + comment_pos->col + 2; /* skip / and * */
- if (*look != NUL) /* if something after it */
- comment_pos->col = (colnr_T)(skipwhite(look) - start);
+ start = ml_get(comment_pos->lnum);
+ look = start + comment_pos->col + 2; // skip / and *
+ if (*look != NUL) { // if something after it
+ comment_pos->col = (colnr_T)(skipwhite(look) - start);
+ }
}
getvcol(curwin, comment_pos, &col, NULL, NULL);
amount = col;
@@ -2038,9 +2055,8 @@ int get_c_indent(void)
amount = get_indent_lnum(trypos->lnum);
goto theend;
}
- /*
- * Are we inside parentheses or braces?
- */ /* XXX */
+ // Are we inside parentheses or braces?
+ // XXX
if (((trypos = find_match_paren(curbuf->b_ind_maxparen)) != NULL
&& curbuf->b_ind_java == 0)
|| (tryposBrace = find_start_brace()) != NULL
@@ -2063,8 +2079,8 @@ int get_c_indent(void)
* a previous non-empty line that matches the same paren.
*/
if (theline[0] == ')' && curbuf->b_ind_paren_prev) {
- /* Line up with the start of the matching paren line. */
- amount = get_indent_lnum(curwin->w_cursor.lnum - 1); /* XXX */
+ // Line up with the start of the matching paren line.
+ amount = get_indent_lnum(curwin->w_cursor.lnum - 1); // XXX
} else {
amount = -1;
for (lnum = cur_curpos.lnum - 1; lnum > our_paren_pos.lnum; --lnum) {
@@ -2083,12 +2099,12 @@ int get_c_indent(void)
continue;
}
- /* XXX */
+ // XXX
if ((trypos = find_match_paren(
corr_ind_maxparen(&cur_curpos))) != NULL
&& trypos->lnum == our_paren_pos.lnum
&& trypos->col == our_paren_pos.col) {
- amount = get_indent_lnum(lnum); /* XXX */
+ amount = get_indent_lnum(lnum); // XXX
if (theline[0] == ')') {
if (our_paren_pos.lnum != lnum
@@ -2200,10 +2216,11 @@ int get_c_indent(void)
col = our_paren_pos.col + 1;
while (ascii_iswhite(l[col]))
col++;
- if (l[col] != NUL) /* In case of trailing space */
+ if (l[col] != NUL) { // In case of trailing space
our_paren_pos.col = col;
- else
+ } else {
our_paren_pos.col++;
+ }
}
}
@@ -2219,7 +2236,7 @@ int get_c_indent(void)
}
if (theline[0] == ')' && curbuf->b_ind_matching_paren) {
- /* Line up with the start of the matching paren line. */
+ // Line up with the start of the matching paren line.
} else if ((curbuf->b_ind_unclosed == 0 && is_if_for_while == 0)
|| (!curbuf->b_ind_unclosed_noignore
&& *look == '(' && ignore_paren_col == 0)) {
@@ -2271,9 +2288,10 @@ int get_c_indent(void)
}
}
- /* add extra indent for a comment */
- if (cin_iscomment(theline))
+ // add extra indent for a comment
+ if (cin_iscomment(theline)) {
amount += curbuf->b_ind_comment;
+ }
} else {
// We are inside braces, there is a { before this line at the position
// stored in tryposBrace.
@@ -2317,7 +2335,7 @@ int get_c_indent(void)
// ldfd) {
// }
if ((curbuf->b_ind_js || curbuf->b_ind_keep_case_label)
- && cin_iscase(skipwhite(get_cursor_line_ptr()), FALSE)) {
+ && cin_iscase(skipwhite(get_cursor_line_ptr()), false)) {
amount = get_indent();
} else if (curbuf->b_ind_js) {
amount = get_indent_lnum(lnum);
@@ -2348,14 +2366,15 @@ int get_c_indent(void)
* to match it with.
*/
lookfor = LOOKFOR_INITIAL;
- if (cin_iselse(theline))
+ if (cin_iselse(theline)) {
lookfor = LOOKFOR_IF;
- else if (cin_iswhileofdo(theline, cur_curpos.lnum)) /* XXX */
+ } else if (cin_iswhileofdo(theline, cur_curpos.lnum)) { // XXX
lookfor = LOOKFOR_DO;
+ }
if (lookfor != LOOKFOR_INITIAL) {
curwin->w_cursor.lnum = cur_curpos.lnum;
if (find_match(lookfor, ourscope) == OK) {
- amount = get_indent(); /* XXX */
+ amount = get_indent(); // XXX
goto theend;
}
}
@@ -2390,7 +2409,7 @@ int get_c_indent(void)
amount += curbuf->b_ind_cpp_extern_c;
}
} else {
- /* Compensate for adding b_ind_open_extra later. */
+ // Compensate for adding b_ind_open_extra later.
amount -= curbuf->b_ind_open_extra;
if (amount < 0)
amount = 0;
@@ -2399,19 +2418,20 @@ int get_c_indent(void)
lookfor_break = FALSE;
- if (cin_iscase(theline, FALSE)) { /* it's a switch() label */
- lookfor = LOOKFOR_CASE; /* find a previous switch() label */
+ if (cin_iscase(theline, false)) { // it's a switch() label
+ lookfor = LOOKFOR_CASE; // find a previous switch() label
amount += curbuf->b_ind_case;
- } else if (cin_isscopedecl(theline)) { /* private:, ... */
- lookfor = LOOKFOR_SCOPEDECL; /* class decl is this block */
+ } else if (cin_isscopedecl(theline)) { // private:, ...
+ lookfor = LOOKFOR_SCOPEDECL; // class decl is this block
amount += curbuf->b_ind_scopedecl;
} else {
- if (curbuf->b_ind_case_break && cin_isbreak(theline))
- /* break; ... */
- lookfor_break = TRUE;
+ if (curbuf->b_ind_case_break && cin_isbreak(theline)) {
+ // break; ...
+ lookfor_break = true;
+ }
lookfor = LOOKFOR_INITIAL;
- /* b_ind_level from start of block */
+ // b_ind_level from start of block
amount += curbuf->b_ind_level;
}
scope_amount = amount;
@@ -2503,16 +2523,17 @@ int get_c_indent(void)
if (terminated != ';' && cin_isinit())
break;
- /* nothing useful found */
- if (terminated == 0 || terminated == '{')
+ // nothing useful found
+ if (terminated == 0 || terminated == '{') {
continue;
+ }
}
if (terminated != ';') {
- /* Skip parens and braces. Position the cursor
- * over the rightmost paren, so that matching it
- * will take us back to the start of the line.
- */ /* XXX */
+ // Skip parens and braces. Position the cursor
+ // over the rightmost paren, so that matching it
+ // will take us back to the start of the line.
+ // XXX
trypos = NULL;
if (find_last_paren(l, '(', ')'))
trypos = find_match_paren(
@@ -2582,7 +2603,7 @@ int get_c_indent(void)
continue;
}
- /* Finally the actual check for "namespace". */
+ // Finally the actual check for "namespace".
if (cin_is_cpp_namespace(l)) {
amount += curbuf->b_ind_cpp_namespace
- added_to_amount;
@@ -2614,7 +2635,7 @@ int get_c_indent(void)
* If this is a switch() label, may line up relative to that.
* If this is a C++ scope declaration, do the same.
*/
- iscase = cin_iscase(l, FALSE);
+ bool iscase = cin_iscase(l, false);
if (iscase || cin_isscopedecl(l)) {
/* we are only looking for cpp base class
* declaration/initialization any longer */
@@ -2640,27 +2661,24 @@ int get_c_indent(void)
break;
}
- /*
- * case xx: <- line up with this case
- * x = 333;
- * case yy:
- */
- if ( (iscase && lookfor == LOOKFOR_CASE)
- || (iscase && lookfor_break)
- || (!iscase && lookfor == LOOKFOR_SCOPEDECL)) {
- /*
- * Check that this case label is not for another
- * switch()
- */ /* XXX */
+ // case xx: <- line up with this case
+ // x = 333;
+ // case yy:
+ if ((iscase && lookfor == LOOKFOR_CASE)
+ || (iscase && lookfor_break)
+ || (!iscase && lookfor == LOOKFOR_SCOPEDECL)) {
+ // Check that this case label is not for another
+ // switch()
+ // XXX
if ((trypos = find_start_brace()) == NULL
|| trypos->lnum == ourscope) {
- amount = get_indent(); /* XXX */
+ amount = get_indent(); // XXX
break;
}
continue;
}
- n = get_indent_nolabel(curwin->w_cursor.lnum); /* XXX */
+ n = get_indent_nolabel(curwin->w_cursor.lnum); // XXX
/*
* case xx: if (cond) <- line up with this if
@@ -2708,7 +2726,7 @@ int get_c_indent(void)
* case xx:
* -> y = 1;
*/
- scope_amount = get_indent() + (iscase /* XXX */
+ scope_amount = get_indent() + (iscase // XXX
? curbuf->b_ind_case_code
: curbuf->b_ind_scopedecl_code);
lookfor = curbuf->b_ind_case_break
@@ -2750,11 +2768,10 @@ int get_c_indent(void)
continue;
}
- /*
- * Are we at the start of a cpp base class declaration or
- * constructor initialization?
- */ /* XXX */
- n = FALSE;
+ // Are we at the start of a cpp base class declaration or
+ // constructor initialization?
+ // XXX
+ n = 0;
if (lookfor != LOOKFOR_TERM && curbuf->b_ind_cpp_baseclass > 0) {
n = cin_is_cpp_baseclass(&cache_cpp_baseclass);
l = get_cursor_line_ptr();
@@ -2766,13 +2783,14 @@ int get_c_indent(void)
else
amount += ind_continuation;
} else if (theline[0] == '{') {
- /* Need to find start of the declaration. */
+ // Need to find start of the declaration.
lookfor = LOOKFOR_UNTERM;
ind_continuation = 0;
continue;
- } else
- /* XXX */
+ } else {
+ // XXX
amount = get_baseclass_amount(cache_cpp_baseclass.lpos.col);
+ }
break;
} else if (lookfor == LOOKFOR_CPP_BASECLASS) {
/* only look, whether there is a cpp base class
@@ -2871,8 +2889,8 @@ int get_c_indent(void)
*/
curwin->w_cursor = *trypos;
l = get_cursor_line_ptr();
- if (cin_iscase(l, FALSE) || cin_isscopedecl(l)) {
- ++curwin->w_cursor.lnum;
+ if (cin_iscase(l, false) || cin_isscopedecl(l)) {
+ curwin->w_cursor.lnum++;
curwin->w_cursor.col = 0;
continue;
}
@@ -3025,9 +3043,10 @@ int get_c_indent(void)
* -> here;
*/
if (lookfor == LOOKFOR_UNTERM) {
- /* When line ends in a comma add extra indent */
- if (terminated == ',')
+ // When line ends in a comma add extra indent
+ if (terminated == ',') {
amount += ind_continuation;
+ }
break;
}
@@ -3144,9 +3163,10 @@ int get_c_indent(void)
if (whilelevel == 0) {
lookfor = LOOKFOR_TERM;
- amount = get_indent(); /* XXX */
- if (theline[0] == '{')
+ amount = get_indent(); // XXX
+ if (theline[0] == '{') {
amount += curbuf->b_ind_open_extra;
+ }
}
++whilelevel;
}
@@ -3174,8 +3194,8 @@ int get_c_indent(void)
if (whilelevel > 0) {
l = cin_skipcomment(get_cursor_line_ptr());
if (cin_isdo(l)) {
- amount = get_indent(); /* XXX */
- --whilelevel;
+ amount = get_indent(); // XXX
+ whilelevel--;
continue;
}
}
@@ -3240,8 +3260,8 @@ term_again:
*/
curwin->w_cursor = *trypos;
l = get_cursor_line_ptr();
- if (cin_iscase(l, FALSE) || cin_isscopedecl(l)) {
- ++curwin->w_cursor.lnum;
+ if (cin_iscase(l, false) || cin_isscopedecl(l)) {
+ curwin->w_cursor.lnum++;
curwin->w_cursor.col = 0;
continue;
}
@@ -3256,8 +3276,7 @@ term_again:
* stat;
* }
*/
- iscase = (curbuf->b_ind_keep_case_label
- && cin_iscase(l, FALSE));
+ iscase = curbuf->b_ind_keep_case_label && cin_iscase(l, false);
/*
* Get indent and pointer to text for current line,
@@ -3267,7 +3286,7 @@ term_again:
if (theline[0] == '{')
amount += curbuf->b_ind_open_extra;
- /* See remark above: "Only add b_ind_open_extra.." */
+ // See remark above: "Only add b_ind_open_extra.."
l = skipwhite(l);
if (*l == '{')
amount -= curbuf->b_ind_open_extra;
@@ -3297,11 +3316,11 @@ term_again:
* that block.
*/
l = get_cursor_line_ptr();
- if (find_last_paren(l, '{', '}') /* XXX */
+ if (find_last_paren(l, '{', '}') // XXX
&& (trypos = find_start_brace()) != NULL) {
curwin->w_cursor = *trypos;
- /* if not "else {" check for terminated again */
- /* but skip block for "} else {" */
+ // if not "else {" check for terminated again
+ // but skip block for "} else {"
l = cin_skipcomment(get_cursor_line_ptr());
if (*l == '}' || !cin_iselse(l))
goto term_again;
@@ -3314,13 +3333,14 @@ term_again:
}
}
- /* add extra indent for a comment */
- if (cin_iscomment(theline))
+ // add extra indent for a comment
+ if (cin_iscomment(theline)) {
amount += curbuf->b_ind_comment;
-
- /* subtract extra left-shift for jump labels */
- if (curbuf->b_ind_jump_label > 0 && original_line_islabel)
+ }
+ // subtract extra left-shift for jump labels
+ if (curbuf->b_ind_jump_label > 0 && original_line_islabel) {
amount -= curbuf->b_ind_jump_label;
+ }
goto theend;
}
@@ -3360,7 +3380,7 @@ term_again:
goto theend;
}
- /* search backwards until we find something we recognize */
+ // search backwards until we find something we recognize
amount = 0;
curwin->w_cursor = cur_curpos;
while (curwin->w_cursor.lnum > 1) {
@@ -3386,7 +3406,7 @@ term_again:
l = get_cursor_line_ptr();
}
if (n) {
- /* XXX */
+ // XXX
amount = get_baseclass_amount(cache_cpp_baseclass.lpos.col);
break;
}
@@ -3415,11 +3435,11 @@ term_again:
*/
if (cin_ends_in(l, (char_u *)",", NULL)
|| (*l != NUL && (n = l[STRLEN(l) - 1]) == '\\')) {
- /* take us back to opening paren */
+ // take us back to opening paren
if (find_last_paren(l, '(', ')')
- && (trypos = find_match_paren(
- curbuf->b_ind_maxparen)) != NULL)
+ && (trypos = find_match_paren(curbuf->b_ind_maxparen)) != NULL) {
curwin->w_cursor = *trypos;
+ }
/* For a line ending in ',' that is a continuation line go
* back to the first line with a backslash:
@@ -3435,7 +3455,7 @@ term_again:
curwin->w_cursor.col = 0;
}
- amount = get_indent(); /* XXX */
+ amount = get_indent(); // XXX
if (amount == 0)
amount = cin_first_id_amount();
@@ -3448,8 +3468,9 @@ term_again:
* If the line looks like a function declaration, and we're
* not in a comment, put it the left margin.
*/
- if (cin_isfuncdecl(NULL, cur_curpos.lnum, 0)) /* XXX */
+ if (cin_isfuncdecl(NULL, cur_curpos.lnum, 0)) { // XXX
break;
+ }
l = get_cursor_line_ptr();
/*
@@ -3535,13 +3556,14 @@ term_again:
if ((trypos = find_match_paren(curbuf->b_ind_maxparen)) != NULL)
curwin->w_cursor = *trypos;
- amount = get_indent(); /* XXX */
+ amount = get_indent(); // XXX
break;
}
- /* add extra indent for a comment */
- if (cin_iscomment(theline))
+ // add extra indent for a comment
+ if (cin_iscomment(theline)) {
amount += curbuf->b_ind_comment;
+ }
/* add extra indent if the previous line ended in a backslash:
* "asdfasdf\
@@ -3565,7 +3587,7 @@ theend:
amount = 0;
laterend:
- /* put the cursor back where it belongs */
+ // put the cursor back where it belongs
curwin->w_cursor = cur_curpos;
xfree(linecopy);
@@ -3598,7 +3620,7 @@ static int find_match(int lookfor, linenr_T ourscope)
look = cin_skipcomment(get_cursor_line_ptr());
if (!cin_iselse(look)
&& !cin_isif(look)
- && !cin_isdo(look) /* XXX */
+ && !cin_isdo(look) // XXX
&& !cin_iswhileofdo(look, curwin->w_cursor.lnum)) {
continue;
}
@@ -3607,9 +3629,10 @@ static int find_match(int lookfor, linenr_T ourscope)
* if we've gone outside the braces entirely,
* we must be out of scope...
*/
- theirscope = find_start_brace(); /* XXX */
- if (theirscope == NULL)
+ theirscope = find_start_brace(); // XXX
+ if (theirscope == NULL) {
break;
+ }
/*
* and if the brace enclosing this is further
@@ -3649,7 +3672,7 @@ static int find_match(int lookfor, linenr_T ourscope)
continue;
}
- /* If it's an "if" decrement elselevel */
+ // If it's an "if" decrement elselevel
look = cin_skipcomment(get_cursor_line_ptr());
if (cin_isif(look)) {
elselevel--;
@@ -3661,9 +3684,10 @@ static int find_match(int lookfor, linenr_T ourscope)
whilelevel = 0;
}
- /* If it's a "do" decrement whilelevel */
- if (cin_isdo(look))
+ // If it's a "do" decrement whilelevel
+ if (cin_isdo(look)) {
whilelevel--;
+ }
/*
* if we've used up all the elses, then
diff --git a/src/nvim/lib/queue.h b/src/nvim/lib/queue.h
index ab9270081e..452998a5a4 100644
--- a/src/nvim/lib/queue.h
+++ b/src/nvim/lib/queue.h
@@ -33,11 +33,17 @@ typedef struct _queue {
#define QUEUE_DATA(ptr, type, field) \
((type *)((char *)(ptr) - offsetof(type, field)))
-// Important note: mutating the list while QUEUE_FOREACH is
-// iterating over its elements results in undefined behavior.
-#define QUEUE_FOREACH(q, h) \
- for ( /* NOLINT(readability/braces) */ \
- (q) = (h)->next; (q) != (h); (q) = (q)->next)
+// Important note: the node currently being processed can be safely deleted.
+// otherwise, mutating the list while QUEUE_FOREACH is iterating over its
+// elements results in undefined behavior.
+#define QUEUE_FOREACH(q, h, code) \
+ (q) = (h)->next; \
+ while((q) != (h)) { \
+ QUEUE *next = q->next; \
+ code \
+ (q) = next; \
+ }
+
// ffi.cdef is unable to swallow `bool` in place of `int` here.
static inline int QUEUE_EMPTY(const QUEUE *const q)
diff --git a/src/nvim/log.h b/src/nvim/log.h
index f2e74df031..17d754c033 100644
--- a/src/nvim/log.h
+++ b/src/nvim/log.h
@@ -17,10 +17,11 @@
#endif
-#define DEBUG_LOG_LEVEL 0
-#define INFO_LOG_LEVEL 1
-#define WARN_LOG_LEVEL 2
-#define ERROR_LOG_LEVEL 3
+#define TRACE_LOG_LEVEL 0
+#define DEBUG_LOG_LEVEL 1
+#define INFO_LOG_LEVEL 2
+#define WARN_LOG_LEVEL 3
+#define ERROR_LOG_LEVEL 4
#define DLOG(...)
#define DLOGN(...)
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
index 030df69caa..ce8c9b0d06 100644
--- a/src/nvim/lua/converter.c
+++ b/src/nvim/lua/converter.c
@@ -245,7 +245,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
} else {
dictitem_T *const di = tv_dict_item_alloc_len(s, len);
if (tv_dict_add(cur.tv->vval.v_dict, di) == FAIL) {
- assert(false);
+ abort();
}
kv_push(stack, cur);
cur = (TVPopStackItem) { &di->di_tv, false, false, 0 };
@@ -391,7 +391,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
break;
}
default: {
- assert(false);
+ abort();
}
}
nlua_pop_typval_table_processing_end:
@@ -400,7 +400,6 @@ nlua_pop_typval_table_processing_end:
case LUA_TFUNCTION: {
LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState));
state->lua_callable.func_ref = nlua_ref(lstate, -1);
- state->lua_callable.table_ref = LUA_NOREF;
char_u *name = register_cfunc(
&nlua_CFunction_func_call,
@@ -412,6 +411,7 @@ nlua_pop_typval_table_processing_end:
break;
}
case LUA_TUSERDATA: {
+ // TODO(bfredl): check mt.__call and convert to function?
nlua_pushref(lstate, nlua_nil_ref);
bool is_nil = lua_rawequal(lstate, -2, -1);
lua_pop(lstate, 1);
@@ -1200,7 +1200,7 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err)
break;
}
default: {
- assert(false);
+ abort();
}
}
break;
diff --git a/src/nvim/lua/converter.h b/src/nvim/lua/converter.h
index 8601a32418..43a7e06019 100644
--- a/src/nvim/lua/converter.h
+++ b/src/nvim/lua/converter.h
@@ -11,7 +11,6 @@
typedef struct {
LuaRef func_ref;
- LuaRef table_ref;
} LuaCallable;
typedef struct {
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index 3219c02068..f99a2dd0fe 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -5,6 +5,7 @@
#include <lualib.h>
#include <lauxlib.h>
+#include "nvim/assert.h"
#include "nvim/version.h"
#include "nvim/misc1.h"
#include "nvim/getchar.h"
@@ -16,8 +17,10 @@
#include "nvim/api/vim.h"
#include "nvim/msgpack_rpc/channel.h"
#include "nvim/vim.h"
+#include "nvim/extmark.h"
#include "nvim/ex_getln.h"
#include "nvim/ex_cmds2.h"
+#include "nvim/map.h"
#include "nvim/message.h"
#include "nvim/memline.h"
#include "nvim/buffer_defs.h"
@@ -32,9 +35,7 @@
#include "nvim/event/time.h"
#include "nvim/event/loop.h"
-#ifdef WIN32
#include "nvim/os/os.h"
-#endif
#include "nvim/lua/converter.h"
#include "nvim/lua/executor.h"
@@ -63,6 +64,11 @@ typedef struct {
} \
}
+#if __has_feature(address_sanitizer)
+ PMap(handle_T) *nlua_ref_markers = NULL;
+# define NLUA_TRACK_REFS
+#endif
+
/// Convert lua error into a Vim error message
///
/// @param lstate Lua interpreter state.
@@ -547,6 +553,13 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
static lua_State *nlua_init(void)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
{
+#ifdef NLUA_TRACK_REFS
+ const char *env = os_getenv("NVIM_LUA_NOTRACK");
+ if (!env || !*env) {
+ nlua_ref_markers = pmap_new(handle_T)();
+ }
+#endif
+
lua_State *lstate = luaL_newstate();
if (lstate == NULL) {
EMSG(_("E970: Failed to initialize lua interpreter"));
@@ -554,9 +567,13 @@ static lua_State *nlua_init(void)
}
luaL_openlibs(lstate);
nlua_state_init(lstate);
+
return lstate;
}
+// only to be used by nlua_enter and nlua_free_all_mem!
+static lua_State *global_lstate = NULL;
+
/// Enter lua interpreter
///
/// Calls nlua_init() if needed. Is responsible for pre-lua call initalization
@@ -567,26 +584,39 @@ static lua_State *nlua_init(void)
static lua_State *nlua_enter(void)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
{
- static lua_State *global_lstate = NULL;
if (global_lstate == NULL) {
global_lstate = nlua_init();
}
lua_State *const lstate = global_lstate;
- // Last used p_rtp value. Must not be dereferenced because value pointed to
- // may already be freed. Used to check whether &runtimepath option value
- // changed.
- static const void *last_p_rtp = NULL;
- if (last_p_rtp != (const void *)p_rtp) {
- // stack: (empty)
- lua_getglobal(lstate, "vim");
- // stack: vim
- lua_pop(lstate, 1);
- // stack: (empty)
- last_p_rtp = (const void *)p_rtp;
- }
return lstate;
}
+void nlua_free_all_mem(void)
+{
+ if (!global_lstate) {
+ return;
+ }
+ lua_State *lstate = global_lstate;
+
+ nlua_unref(lstate, nlua_nil_ref);
+ nlua_unref(lstate, nlua_empty_dict_ref);
+
+#ifdef NLUA_TRACK_REFS
+ if (nlua_refcount) {
+ fprintf(stderr, "%d lua references were leaked!", nlua_refcount);
+ }
+
+ if (nlua_ref_markers) {
+ // in case there are leaked luarefs, leak the associated memory
+ // to get LeakSanitizer stacktraces on exit
+ pmap_free(handle_T)(nlua_ref_markers);
+ }
+#endif
+
+ nlua_refcount = 0;
+ lua_close(lstate);
+}
+
static void nlua_print_event(void **argv)
{
char *str = argv[0];
@@ -866,17 +896,35 @@ static int nlua_getenv(lua_State *lstate)
}
#endif
+
/// add the value to the registry
LuaRef nlua_ref(lua_State *lstate, int index)
{
lua_pushvalue(lstate, index);
- return luaL_ref(lstate, LUA_REGISTRYINDEX);
+ LuaRef ref = luaL_ref(lstate, LUA_REGISTRYINDEX);
+ if (ref > 0) {
+ nlua_refcount++;
+#ifdef NLUA_TRACK_REFS
+ if (nlua_ref_markers) {
+ // dummy allocation to make LeakSanitizer track our luarefs
+ pmap_put(handle_T)(nlua_ref_markers, ref, xmalloc(3));
+ }
+#endif
+ }
+ return ref;
}
/// remove the value from the registry
void nlua_unref(lua_State *lstate, LuaRef ref)
{
if (ref > 0) {
+ nlua_refcount--;
+#ifdef NLUA_TRACK_REFS
+ // NB: don't remove entry from map to track double-unref
+ if (nlua_ref_markers) {
+ xfree(pmap_get(handle_T)(nlua_ref_markers, ref));
+ }
+#endif
luaL_unref(lstate, LUA_REGISTRYINDEX, ref);
}
}
@@ -893,19 +941,11 @@ void nlua_pushref(lua_State *lstate, LuaRef ref)
lua_rawgeti(lstate, LUA_REGISTRYINDEX, ref);
}
+
/// Gets a new reference to an object stored at original_ref
///
/// NOTE: It does not copy the value, it creates a new ref to the lua object.
/// Leaves the stack unchanged.
-LuaRef nlua_newref(lua_State *lstate, LuaRef original_ref)
-{
- nlua_pushref(lstate, original_ref);
- LuaRef new_ref = nlua_ref(lstate, -1);
- lua_pop(lstate, 1);
-
- return new_ref;
-}
-
LuaRef api_new_luaref(LuaRef original_ref)
{
if (original_ref == LUA_NOREF) {
@@ -913,7 +953,10 @@ LuaRef api_new_luaref(LuaRef original_ref)
}
lua_State *const lstate = nlua_enter();
- return nlua_newref(lstate, original_ref);
+ nlua_pushref(lstate, original_ref);
+ LuaRef new_ref = nlua_ref(lstate, -1);
+ lua_pop(lstate, 1);
+ return new_ref;
}
@@ -1023,25 +1066,13 @@ int typval_exec_lua_callable(
typval_T *rettv
)
{
- int offset = 0;
LuaRef cb = lua_cb.func_ref;
- if (cb == LUA_NOREF) {
- // This shouldn't happen.
- luaL_error(lstate, "Invalid function passed to VimL");
- return ERROR_OTHER;
- }
-
nlua_pushref(lstate, cb);
- if (lua_cb.table_ref != LUA_NOREF) {
- offset += 1;
- nlua_pushref(lstate, lua_cb.table_ref);
- }
-
PUSH_ALL_TYPVALS(lstate, argvars, argcount, false);
- if (lua_pcall(lstate, argcount + offset, 1, 0)) {
+ if (lua_pcall(lstate, argcount, 1, 0)) {
nlua_print(lstate);
return ERROR_OTHER;
}
@@ -1213,13 +1244,16 @@ void ex_luado(exarg_T *const eap)
break;
}
lua_pushvalue(lstate, -1);
- lua_pushstring(lstate, (const char *)ml_get_buf(curbuf, l, false));
+ const char *old_line = (const char *)ml_get_buf(curbuf, l, false);
+ lua_pushstring(lstate, old_line);
lua_pushnumber(lstate, (lua_Number)l);
if (lua_pcall(lstate, 2, 1, 0)) {
nlua_error(lstate, _("E5111: Error calling lua: %.*s"));
break;
}
if (lua_isstring(lstate, -1)) {
+ size_t old_line_len = STRLEN(old_line);
+
size_t new_line_len;
const char *const new_line = lua_tolstring(lstate, -1, &new_line_len);
char *const new_line_transformed = xmemdupz(new_line, new_line_len);
@@ -1229,7 +1263,7 @@ void ex_luado(exarg_T *const eap)
}
}
ml_replace(l, (char_u *)new_line_transformed, false);
- changed_bytes(l, 0);
+ inserted_bytes(l, 0, (int)old_line_len, (int)new_line_len);
}
lua_pop(lstate, 1);
}
@@ -1272,6 +1306,12 @@ bool nlua_exec_file(const char *path)
return true;
}
+int tslua_get_language_version(lua_State *L)
+{
+ lua_pushnumber(L, TREE_SITTER_LANGUAGE_VERSION);
+ return 1;
+}
+
static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
{
tslua_init(lstate);
@@ -1288,8 +1328,85 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
lua_pushcfunction(lstate, tslua_inspect_lang);
lua_setfield(lstate, -2, "_ts_inspect_language");
- lua_pushcfunction(lstate, ts_lua_parse_query);
+ lua_pushcfunction(lstate, tslua_parse_query);
lua_setfield(lstate, -2, "_ts_parse_query");
+
+ lua_pushcfunction(lstate, tslua_get_language_version);
+ lua_setfield(lstate, -2, "_ts_get_language_version");
+}
+
+int nlua_expand_pat(expand_T *xp,
+ char_u *pat,
+ int *num_results,
+ char_u ***results)
+{
+ lua_State *const lstate = nlua_enter();
+ int ret = OK;
+
+ // [ vim ]
+ lua_getglobal(lstate, "vim");
+
+ // [ vim, vim._expand_pat ]
+ lua_getfield(lstate, -1, "_expand_pat");
+ luaL_checktype(lstate, -1, LUA_TFUNCTION);
+
+ // [ vim, vim._log_keystroke, buf ]
+ lua_pushlstring(lstate, (const char *)pat, STRLEN(pat));
+
+ if (lua_pcall(lstate, 1, 2, 0) != 0) {
+ nlua_error(
+ lstate,
+ _("Error executing vim._expand_pat: %.*s"));
+ return FAIL;
+ }
+
+ Error err = ERROR_INIT;
+
+ *num_results = 0;
+ *results = NULL;
+
+ int prefix_len = (int)nlua_pop_Integer(lstate, &err);
+ if (ERROR_SET(&err)) {
+ ret = FAIL;
+ goto cleanup;
+ }
+
+ Array completions = nlua_pop_Array(lstate, &err);
+ if (ERROR_SET(&err)) {
+ ret = FAIL;
+ goto cleanup_array;
+ }
+
+ garray_T result_array;
+ ga_init(&result_array, (int)sizeof(char *), 80);
+ for (size_t i = 0; i < completions.size; i++) {
+ Object v = completions.items[i];
+
+ if (v.type != kObjectTypeString) {
+ ret = FAIL;
+ goto cleanup_array;
+ }
+
+ GA_APPEND(
+ char_u *,
+ &result_array,
+ vim_strsave((char_u *)v.data.string.data));
+ }
+
+ xp->xp_pattern += prefix_len;
+ *results = result_array.ga_data;
+ *num_results = result_array.ga_len;
+
+cleanup_array:
+ api_free_array(completions);
+
+cleanup:
+
+ if (ret == FAIL) {
+ ga_clear(&result_array);
+ }
+
+ return ret;
}
static int nlua_regex(lua_State *lstate)
@@ -1425,6 +1542,8 @@ static int regex_match_line(lua_State *lstate)
return nret;
}
+// Required functions for lua c functions as VimL callbacks
+
int nlua_CFunction_func_call(
int argcount,
typval_T *argvars,
@@ -1434,53 +1553,40 @@ int nlua_CFunction_func_call(
lua_State *const lstate = nlua_enter();
LuaCFunctionState *funcstate = (LuaCFunctionState *)state;
- return typval_exec_lua_callable(
- lstate,
- funcstate->lua_callable,
- argcount,
- argvars,
- rettv);
+ return typval_exec_lua_callable(lstate, funcstate->lua_callable,
+ argcount, argvars, rettv);
}
-/// Required functions for lua c functions as VimL callbacks
+
void nlua_CFunction_func_free(void *state)
{
lua_State *const lstate = nlua_enter();
LuaCFunctionState *funcstate = (LuaCFunctionState *)state;
nlua_unref(lstate, funcstate->lua_callable.func_ref);
- nlua_unref(lstate, funcstate->lua_callable.table_ref);
xfree(funcstate);
}
bool nlua_is_table_from_lua(typval_T *const arg)
{
- if (arg->v_type != VAR_DICT && arg->v_type != VAR_LIST) {
- return false;
- }
-
if (arg->v_type == VAR_DICT) {
- return arg->vval.v_dict->lua_table_ref > 0
- && arg->vval.v_dict->lua_table_ref != LUA_NOREF;
+ return arg->vval.v_dict->lua_table_ref != LUA_NOREF;
} else if (arg->v_type == VAR_LIST) {
- return arg->vval.v_list->lua_table_ref > 0
- && arg->vval.v_list->lua_table_ref != LUA_NOREF;
+ return arg->vval.v_list->lua_table_ref != LUA_NOREF;
+ } else {
+ return false;
}
-
- return false;
}
char_u *nlua_register_table_as_callable(typval_T *const arg)
{
- if (!nlua_is_table_from_lua(arg)) {
- return NULL;
- }
-
- LuaRef table_ref;
+ LuaRef table_ref = LUA_NOREF;
if (arg->v_type == VAR_DICT) {
table_ref = arg->vval.v_dict->lua_table_ref;
} else if (arg->v_type == VAR_LIST) {
table_ref = arg->vval.v_list->lua_table_ref;
- } else {
+ }
+
+ if (table_ref == LUA_NOREF) {
return NULL;
}
@@ -1490,55 +1596,34 @@ char_u *nlua_register_table_as_callable(typval_T *const arg)
int top = lua_gettop(lstate);
#endif
- nlua_pushref(lstate, table_ref);
+ nlua_pushref(lstate, table_ref); // [table]
if (!lua_getmetatable(lstate, -1)) {
+ lua_pop(lstate, 1);
+ assert(top == lua_gettop(lstate));
return NULL;
- }
+ } // [table, mt]
- lua_getfield(lstate, -1, "__call");
+ lua_getfield(lstate, -1, "__call"); // [table, mt, mt.__call]
if (!lua_isfunction(lstate, -1)) {
+ lua_pop(lstate, 3);
+ assert(top == lua_gettop(lstate));
return NULL;
}
-
- LuaRef new_table_ref = nlua_newref(lstate, table_ref);
+ lua_pop(lstate, 2); // [table]
LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState));
state->lua_callable.func_ref = nlua_ref(lstate, -1);
- state->lua_callable.table_ref = new_table_ref;
- char_u *name = register_cfunc(
- &nlua_CFunction_func_call,
- &nlua_CFunction_func_free,
- state);
+ char_u *name = register_cfunc(&nlua_CFunction_func_call,
+ &nlua_CFunction_func_free, state);
- lua_pop(lstate, 3);
+ lua_pop(lstate, 1); // []
assert(top == lua_gettop(lstate));
return name;
}
-/// Helper function to free a list_T
-void nlua_free_typval_list(list_T *const l)
-{
- if (l->lua_table_ref != LUA_NOREF && l->lua_table_ref > 0) {
- lua_State *const lstate = nlua_enter();
- nlua_unref(lstate, l->lua_table_ref);
- l->lua_table_ref = LUA_NOREF;
- }
-}
-
-
-/// Helper function to free a dict_T
-void nlua_free_typval_dict(dict_T *const d)
-{
- if (d->lua_table_ref != LUA_NOREF && d->lua_table_ref > 0) {
- lua_State *const lstate = nlua_enter();
- nlua_unref(lstate, d->lua_table_ref);
- d->lua_table_ref = LUA_NOREF;
- }
-}
-
void nlua_execute_log_keystroke(int c)
{
char_u buf[NUMBUFLEN];
diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h
index 1d7a15d9aa..ea774ac2e3 100644
--- a/src/nvim/lua/executor.h
+++ b/src/nvim/lua/executor.h
@@ -16,6 +16,8 @@ void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL;
EXTERN LuaRef nlua_nil_ref INIT(= LUA_NOREF);
EXTERN LuaRef nlua_empty_dict_ref INIT(= LUA_NOREF);
+EXTERN int nlua_refcount INIT(= 0);
+
#define set_api_error(s, err) \
do { \
Error *err_ = (err); \
diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c
index a640b97d3b..c186928ae2 100644
--- a/src/nvim/lua/treesitter.c
+++ b/src/nvim/lua/treesitter.c
@@ -171,7 +171,7 @@ int tslua_add_language(lua_State *L)
TSLanguage *lang = lang_parser();
if (lang == NULL) {
- return luaL_error(L, "Failed to load parser: internal error");
+ return luaL_error(L, "Failed to load parser %s: internal error", path);
}
uint32_t lang_version = ts_language_version(lang);
@@ -179,7 +179,8 @@ int tslua_add_language(lua_State *L)
|| lang_version > TREE_SITTER_LANGUAGE_VERSION) {
return luaL_error(
L,
- "ABI version mismatch : supported between %d and %d, found %d",
+ "ABI version mismatch for %s: supported between %d and %d, found %d",
+ path,
TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION,
TREE_SITTER_LANGUAGE_VERSION, lang_version);
}
@@ -221,13 +222,19 @@ int tslua_inspect_lang(lua_State *L)
lua_setfield(L, -2, "symbols"); // [retval]
size_t nfields = (size_t)ts_language_field_count(lang);
- lua_createtable(L, nfields-1, 1); // [retval, fields]
- for (size_t i = 0; i < nfields; i++) {
+ lua_createtable(L, nfields, 1); // [retval, fields]
+ // Field IDs go from 1 to nfields inclusive (extra index 0 maps to NULL)
+ for (size_t i = 1; i <= nfields; i++) {
lua_pushstring(L, ts_language_field_name_for_id(lang, i));
lua_rawseti(L, -2, i); // [retval, fields]
}
lua_setfield(L, -2, "fields"); // [retval]
+
+ uint32_t lang_version = ts_language_version(lang);
+ lua_pushinteger(L, lang_version); // [retval, version]
+ lua_setfield(L, -2, "_abi_version");
+
return 1;
}
@@ -1108,7 +1115,7 @@ static int querycursor_gc(lua_State *L)
// Query methods
-int ts_lua_parse_query(lua_State *L)
+int tslua_parse_query(lua_State *L)
{
if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) {
return luaL_error(L, "string expected");
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
index 80b311de2c..eb54ff28ee 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -39,6 +39,16 @@ assert(vim)
vim.inspect = package.loaded['vim.inspect']
assert(vim.inspect)
+vim.log = {
+ levels = {
+ TRACE = 0;
+ DEBUG = 1;
+ INFO = 2;
+ WARN = 3;
+ ERROR = 4;
+ }
+}
+
-- Internal-only until comments in #8107 are addressed.
-- Returns:
-- {errcode}, {output}
@@ -253,8 +263,15 @@ end
-- vim.fn.{func}(...)
vim.fn = setmetatable({}, {
__index = function(t, key)
- local function _fn(...)
- return vim.call(key, ...)
+ local _fn
+ if vim.api[key] ~= nil then
+ _fn = function()
+ error(string.format("Tried to call API function with vim.fn: use vim.api.%s instead", key))
+ end
+ else
+ _fn = function(...)
+ return vim.call(key, ...)
+ end
end
t[key] = _fn
return _fn
@@ -478,6 +495,23 @@ function vim.defer_fn(fn, timeout)
return timer
end
+
+--- Notification provider
+--- without a runtime, writes to :Messages
+-- see :help nvim_notify
+--@param msg Content of the notification to show to the user
+--@param log_level Optional log level
+--@param opts Dictionary with optional options (timeout, etc)
+function vim.notify(msg, log_level, _opts)
+
+ if log_level == vim.log.levels.ERROR then
+ vim.api.nvim_err_writeln(msg)
+ else
+ vim.api.nvim_echo({{msg}}, true, {})
+ end
+end
+
+
local on_keystroke_callbacks = {}
--- Register a lua {fn} with an {id} to be run after every keystroke.
@@ -534,4 +568,164 @@ function vim._log_keystroke(char)
end
end
+--- Generate a list of possible completions for the string.
+--- String starts with ^ and then has the pattern.
+---
+--- 1. Can we get it to just return things in the global namespace with that name prefix
+--- 2. Can we get it to return things from global namespace even with `print(` in front.
+function vim._expand_pat(pat, env)
+ env = env or _G
+
+ pat = string.sub(pat, 2, #pat)
+
+ if pat == '' then
+ local result = vim.tbl_keys(env)
+ table.sort(result)
+ return result, 0
+ end
+
+ -- TODO: We can handle spaces in [] ONLY.
+ -- We should probably do that at some point, just for cooler completion.
+ -- TODO: We can suggest the variable names to go in []
+ -- This would be difficult as well.
+ -- Probably just need to do a smarter match than just `:match`
+
+ -- Get the last part of the pattern
+ local last_part = pat:match("[%w.:_%[%]'\"]+$")
+ if not last_part then return {}, 0 end
+
+ local parts, search_index = vim._expand_pat_get_parts(last_part)
+
+ local match_part = string.sub(last_part, search_index, #last_part)
+ local prefix_match_pat = string.sub(pat, 1, #pat - #match_part) or ''
+
+ local final_env = env
+
+ for _, part in ipairs(parts) do
+ if type(final_env) ~= 'table' then
+ return {}, 0
+ end
+ local key
+
+ -- Normally, we just have a string
+ -- Just attempt to get the string directly from the environment
+ if type(part) == "string" then
+ key = part
+ else
+ -- However, sometimes you want to use a variable, and complete on it
+ -- With this, you have the power.
+
+ -- MY_VAR = "api"
+ -- vim[MY_VAR]
+ -- -> _G[MY_VAR] -> "api"
+ local result_key = part[1]
+ if not result_key then
+ return {}, 0
+ end
+
+ local result = rawget(env, result_key)
+
+ if result == nil then
+ return {}, 0
+ end
+
+ key = result
+ end
+ local field = rawget(final_env, key)
+ if field == nil then
+ local mt = getmetatable(final_env)
+ if mt and type(mt.__index) == "table" then
+ field = rawget(mt.__index, key)
+ end
+ end
+ final_env = field
+
+ if not final_env then
+ return {}, 0
+ end
+ end
+
+ local keys = {}
+ local function insert_keys(obj)
+ for k,_ in pairs(obj) do
+ if type(k) == "string" and string.sub(k,1,string.len(match_part)) == match_part then
+ table.insert(keys,k)
+ end
+ end
+ end
+
+ if type(final_env) == "table" then
+ insert_keys(final_env)
+ end
+ local mt = getmetatable(final_env)
+ if mt and type(mt.__index) == "table" then
+ insert_keys(mt.__index)
+ end
+
+ table.sort(keys)
+
+ return keys, #prefix_match_pat
+end
+
+vim._expand_pat_get_parts = function(lua_string)
+ local parts = {}
+
+ local accumulator, search_index = '', 1
+ local in_brackets, bracket_end = false, -1
+ local string_char = nil
+ for idx = 1, #lua_string do
+ local s = lua_string:sub(idx, idx)
+
+ if not in_brackets and (s == "." or s == ":") then
+ table.insert(parts, accumulator)
+ accumulator = ''
+
+ search_index = idx + 1
+ elseif s == "[" then
+ in_brackets = true
+
+ table.insert(parts, accumulator)
+ accumulator = ''
+
+ search_index = idx + 1
+ elseif in_brackets then
+ if idx == bracket_end then
+ in_brackets = false
+ search_index = idx + 1
+
+ if string_char == "VAR" then
+ table.insert(parts, { accumulator })
+ accumulator = ''
+
+ string_char = nil
+ end
+ elseif not string_char then
+ bracket_end = string.find(lua_string, ']', idx, true)
+
+ if s == '"' or s == "'" then
+ string_char = s
+ elseif s ~= ' ' then
+ string_char = "VAR"
+ accumulator = s
+ end
+ elseif string_char then
+ if string_char ~= s then
+ accumulator = accumulator .. s
+ else
+ table.insert(parts, accumulator)
+ accumulator = ''
+
+ string_char = nil
+ end
+ end
+ else
+ accumulator = accumulator .. s
+ end
+ end
+
+ parts = vim.tbl_filter(function(val) return #val > 0 end, parts)
+
+ return parts, search_index
+end
+
return module
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 9f71df3a46..7064f2a068 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -1404,9 +1404,9 @@ static void load_plugins(void)
static void handle_quickfix(mparm_T *paramp)
{
if (paramp->edit_type == EDIT_QF) {
- if (paramp->use_ef != NULL)
- set_string_option_direct((char_u *)"ef", -1,
- paramp->use_ef, OPT_FREE, SID_CARG);
+ if (paramp->use_ef != NULL) {
+ set_string_option_direct("ef", -1, paramp->use_ef, OPT_FREE, SID_CARG);
+ }
vim_snprintf((char *)IObuff, IOSIZE, "cfile %s", p_ef);
if (qf_init(NULL, p_ef, p_efm, true, IObuff, p_menc) < 0) {
msg_putchar('\n');
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index ec4f4cbc21..73e3ba53a5 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -571,11 +571,12 @@ size_t mb_string2cells(const char_u *str)
/// @param size maximum length of string. It will terminate on earlier NUL.
/// @return The number of cells occupied by string `str`
size_t mb_string2cells_len(const char_u *str, size_t size)
+ FUNC_ATTR_NONNULL_ARG(1)
{
size_t clen = 0;
for (const char_u *p = str; *p != NUL && p < str+size;
- p += utf_ptr2len_len(p, size+(p-str))) {
+ p += utfc_ptr2len_len(p, size+(p-str))) {
clen += utf_ptr2cells(p);
}
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index 31dc6b3649..34d8eb0ffe 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -3859,8 +3859,8 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype)
/* May resize here so we don't have to do it in both cases below */
if (buf->b_ml.ml_usedchunks + 1 >= buf->b_ml.ml_numchunks) {
buf->b_ml.ml_numchunks = buf->b_ml.ml_numchunks * 3 / 2;
- buf->b_ml.ml_chunksize = (chunksize_T *)
- xrealloc(buf->b_ml.ml_chunksize,
+ buf->b_ml.ml_chunksize = xrealloc(
+ buf->b_ml.ml_chunksize,
sizeof(chunksize_T) * buf->b_ml.ml_numchunks);
}
@@ -4142,7 +4142,7 @@ void goto_byte(long cnt)
if (lnum < 1) { // past the end
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
curwin->w_curswant = MAXCOL;
- coladvance((colnr_T)MAXCOL);
+ coladvance(MAXCOL);
} else {
curwin->w_cursor.lnum = lnum;
curwin->w_cursor.col = (colnr_T)boff;
diff --git a/src/nvim/memline_defs.h b/src/nvim/memline_defs.h
index 9a6f29a908..dc4755f83d 100644
--- a/src/nvim/memline_defs.h
+++ b/src/nvim/memline_defs.h
@@ -45,16 +45,16 @@ typedef struct memline {
memfile_T *ml_mfp; // pointer to associated memfile
+ infoptr_T *ml_stack; // stack of pointer blocks (array of IPTRs)
+ int ml_stack_top; // current top of ml_stack
+ int ml_stack_size; // total number of entries in ml_stack
+
#define ML_EMPTY 1 // empty buffer
#define ML_LINE_DIRTY 2 // cached line was changed and allocated
#define ML_LOCKED_DIRTY 4 // ml_locked was changed
#define ML_LOCKED_POS 8 // ml_locked needs positive block number
int ml_flags;
- infoptr_T *ml_stack; // stack of pointer blocks (array of IPTRs)
- int ml_stack_top; // current top of ml_stack
- int ml_stack_size; // total number of entries in ml_stack
-
linenr_T ml_line_lnum; // line number of cached line, 0 if not valid
char_u *ml_line_ptr; // pointer to cached line
size_t ml_line_offset; // cached byte offset of ml_line_lnum
diff --git a/src/nvim/memory.c b/src/nvim/memory.c
index 9bc6b23ce3..7a8fc4da75 100644
--- a/src/nvim/memory.c
+++ b/src/nvim/memory.c
@@ -19,6 +19,8 @@
#include "nvim/ui.h"
#include "nvim/sign.h"
#include "nvim/api/vim.h"
+#include "nvim/lua/executor.h"
+#include "nvim/decoration.h"
#ifdef UNIT_TESTING
# define malloc(size) mem_malloc(size)
@@ -695,6 +697,10 @@ void free_all_mem(void)
list_free_log();
check_quickfix_busy();
+
+ decor_free_all_mem();
+
+ nlua_free_all_mem();
}
#endif
diff --git a/src/nvim/menu.c b/src/nvim/menu.c
index 7094d3be90..ac3b7768e6 100644
--- a/src/nvim/menu.c
+++ b/src/nvim/menu.c
@@ -81,7 +81,7 @@ ex_menu(exarg_T *eap)
// kFalse for "menu disable
vimmenu_T menuarg;
- modes = get_menu_cmd_modes(eap->cmd, eap->forceit, &noremap, &unmenu);
+ modes = get_menu_cmd_modes((char *)eap->cmd, eap->forceit, &noremap, &unmenu);
arg = eap->arg;
for (;; ) {
@@ -912,7 +912,9 @@ static int expand_emenu; /* TRUE for ":emenu" command */
/*
* Work out what to complete when doing command line completion of menu names.
*/
-char_u *set_context_in_menu_cmd(expand_T *xp, char_u *cmd, char_u *arg, int forceit)
+char_u *set_context_in_menu_cmd(expand_T *xp, const char *cmd, char_u *arg,
+ bool forceit)
+ FUNC_ATTR_NONNULL_ALL
{
char_u *after_dot;
char_u *p;
@@ -1178,7 +1180,7 @@ static bool menu_namecmp(const char_u *const name, const char_u *const mname)
/// to whether the command is an "unmenu" command.
int
get_menu_cmd_modes(
- const char_u * cmd,
+ const char *cmd,
bool forceit,
int *noremap,
int *unmenu
diff --git a/src/nvim/message.c b/src/nvim/message.c
index ba7a667a60..7c98d3c6b5 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -178,6 +178,7 @@ void msg_grid_validate(void)
msg_grid.throttled = false; // don't throttle in 'cmdheight' area
msg_scrolled_at_flush = msg_scrolled;
msg_grid.focusable = false;
+ msg_grid_adj.target = &msg_grid;
if (!msg_scrolled) {
msg_grid_set_pos(Rows - p_ch, false);
}
@@ -188,6 +189,7 @@ void msg_grid_validate(void)
ui_call_grid_destroy(msg_grid.handle);
msg_grid.throttled = false;
msg_grid_adj.row_offset = 0;
+ msg_grid_adj.target = &default_grid;
redraw_cmdline = true;
} else if (msg_grid.chars && !msg_scrolled && msg_grid_pos != Rows - p_ch) {
msg_grid_set_pos(Rows - p_ch, false);
@@ -867,18 +869,18 @@ char_u *msg_trunc_attr(char_u *s, int force, int attr)
*/
char_u *msg_may_trunc(int force, char_u *s)
{
- int n;
int room;
room = (int)(Rows - cmdline_row - 1) * Columns + sc_col - 1;
if ((force || (shortmess(SHM_TRUNC) && !exmode_active))
- && (n = (int)STRLEN(s) - room) > 0) {
+ && (int)STRLEN(s) - room > 0) {
int size = vim_strsize(s);
// There may be room anyway when there are multibyte chars.
if (size <= room) {
return s;
}
+ int n;
for (n = 0; size >= room; ) {
size -= utf_ptr2cells(s + n);
n += utfc_ptr2len(s + n);
@@ -1703,6 +1705,7 @@ void msg_prt_line(char_u *s, int list)
char_u *p_extra = NULL; // init to make SASC shut up
int n;
int attr = 0;
+ char_u *lead = NULL;
char_u *trail = NULL;
int l;
@@ -1710,11 +1713,24 @@ void msg_prt_line(char_u *s, int list)
list = true;
}
- // find start of trailing whitespace
- if (list && curwin->w_p_lcs_chars.trail) {
- trail = s + STRLEN(s);
- while (trail > s && ascii_iswhite(trail[-1])) {
- trail--;
+ if (list) {
+ // find start of trailing whitespace
+ if (curwin->w_p_lcs_chars.trail) {
+ trail = s + STRLEN(s);
+ while (trail > s && ascii_iswhite(trail[-1])) {
+ trail--;
+ }
+ }
+ // find end of leading whitespace
+ if (curwin->w_p_lcs_chars.lead) {
+ lead = s;
+ while (ascii_iswhite(lead[0])) {
+ lead++;
+ }
+ // in a line full of spaces all of them are treated as trailing
+ if (*lead == NUL) {
+ lead = NULL;
+ }
}
}
@@ -1756,7 +1772,9 @@ void msg_prt_line(char_u *s, int list)
c = *s++;
if (c == TAB && (!list || curwin->w_p_lcs_chars.tab1)) {
// tab amount depends on current column
- n_extra = curbuf->b_p_ts - col % curbuf->b_p_ts - 1;
+ n_extra = tabstop_padding(col,
+ curbuf->b_p_ts,
+ curbuf->b_p_vts_array) - 1;
if (!list) {
c = ' ';
c_extra = ' ';
@@ -1789,6 +1807,9 @@ void msg_prt_line(char_u *s, int list)
/* Use special coloring to be able to distinguish <hex> from
* the same in plain text. */
attr = HL_ATTR(HLF_8);
+ } else if (c == ' ' && lead != NULL && s <= lead) {
+ c = curwin->w_p_lcs_chars.lead;
+ attr = HL_ATTR(HLF_8);
} else if (c == ' ' && trail != NULL && s > trail) {
c = curwin->w_p_lcs_chars.trail;
attr = HL_ATTR(HLF_8);
diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c
index fcffe64595..34c43da0f7 100644
--- a/src/nvim/misc1.c
+++ b/src/nvim/misc1.c
@@ -412,7 +412,7 @@ int plines_win_nofold(win_T *wp, linenr_T lnum)
s = ml_get_buf(wp->w_buffer, lnum, FALSE);
if (*s == NUL) /* empty line */
return 1;
- col = win_linetabsize(wp, s, (colnr_T)MAXCOL);
+ col = win_linetabsize(wp, s, MAXCOL);
// If list mode is on, then the '$' at the end of the line may take up one
// extra column.
diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c
index ff471ea978..4c0339e5f4 100644
--- a/src/nvim/mouse.c
+++ b/src/nvim/mouse.c
@@ -72,9 +72,7 @@ int jump_to_mouse(int flags,
int row = mouse_row;
int col = mouse_col;
int grid = mouse_grid;
- int mouse_char;
int fdc = 0;
- ScreenGrid *gp = &default_grid;
mouse_past_bottom = false;
mouse_past_eol = false;
@@ -303,25 +301,6 @@ retnomove:
}
}
- // Remember the character under the mouse, might be one of foldclose or
- // foldopen fillchars in the fold column.
- if (ui_has(kUIMultigrid)) {
- gp = &curwin->w_grid;
- }
- if (row >= 0 && row < Rows && col >= 0 && col <= Columns
- && gp->chars != NULL) {
- mouse_char = utf_ptr2char(gp->chars[gp->line_offset[row]
- + (unsigned)col]);
- } else {
- mouse_char = ' ';
- }
-
- // Check for position outside of the fold column.
- if (curwin->w_p_rl ? col < curwin->w_width_inner - fdc :
- col >= fdc + (cmdwin_type == 0 ? 0 : 1)) {
- mouse_char = ' ';
- }
-
// compute the position in the buffer line from the posn on the screen
if (mouse_comp_pos(curwin, &row, &col, &curwin->w_cursor.lnum)) {
mouse_past_bottom = true;
@@ -362,11 +341,7 @@ retnomove:
count |= CURSOR_MOVED; // Cursor has moved
}
- if (mouse_char == curwin->w_p_fcs_chars.foldclosed) {
- count |= MOUSE_FOLD_OPEN;
- } else if (mouse_char != ' ') {
- count |= MOUSE_FOLD_CLOSE;
- }
+ count |= mouse_check_fold();
return count;
}
@@ -495,21 +470,21 @@ static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp)
*gridp = DEFAULT_GRID_HANDLE;
} else if (*gridp > 1) {
win_T *wp = get_win_by_grid_handle(*gridp);
- if (wp && wp->w_grid.chars
+ if (wp && wp->w_grid_alloc.chars
&& !(wp->w_floating && !wp->w_float_config.focusable)) {
- *rowp = MIN(*rowp, wp->w_grid.Rows-1);
- *colp = MIN(*colp, wp->w_grid.Columns-1);
+ *rowp = MIN(*rowp-wp->w_grid.row_offset, wp->w_grid.Rows-1);
+ *colp = MIN(*colp-wp->w_grid.col_offset, wp->w_grid.Columns-1);
return wp;
}
} else if (*gridp == 0) {
ScreenGrid *grid = ui_comp_mouse_focus(*rowp, *colp);
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (&wp->w_grid != grid) {
+ if (&wp->w_grid_alloc != grid) {
continue;
}
*gridp = grid->handle;
- *rowp -= grid->comp_row;
- *colp -= grid->comp_col;
+ *rowp -= grid->comp_row+wp->w_grid.row_offset;
+ *colp -= grid->comp_col+wp->w_grid.col_offset;
return wp;
}
@@ -738,3 +713,46 @@ static int mouse_adjust_click(win_T *wp, int row, int col)
return col + nudge;
}
+
+// Check clicked cell is foldcolumn
+int mouse_check_fold(void)
+{
+ int click_grid = mouse_grid;
+ int click_row = mouse_row;
+ int click_col = mouse_col;
+ int mouse_char = ' ';
+
+ win_T *wp;
+
+ wp = mouse_find_win(&click_grid, &click_row, &click_col);
+
+ if (wp && mouse_row >= 0 && mouse_row < Rows
+ && mouse_col >= 0 && mouse_col <= Columns) {
+ int multigrid = ui_has(kUIMultigrid);
+ ScreenGrid *gp = multigrid ? &wp->w_grid_alloc : &default_grid;
+ int fdc = win_fdccol_count(wp);
+ int row = multigrid && mouse_grid == 0 ? click_row : mouse_row;
+ int col = multigrid && mouse_grid == 0 ? click_col : mouse_col;
+
+ // Remember the character under the mouse, might be one of foldclose or
+ // foldopen fillchars in the fold column.
+ if (gp->chars != NULL) {
+ mouse_char = utf_ptr2char(gp->chars[gp->line_offset[row]
+ + (unsigned)col]);
+ }
+
+ // Check for position outside of the fold column.
+ if (wp->w_p_rl ? click_col < wp->w_width_inner - fdc :
+ click_col >= fdc + (cmdwin_type == 0 ? 0 : 1)) {
+ mouse_char = ' ';
+ }
+ }
+
+ if (wp && mouse_char == wp->w_p_fcs_chars.foldclosed) {
+ return MOUSE_FOLD_OPEN;
+ } else if (mouse_char != ' ') {
+ return MOUSE_FOLD_CLOSE;
+ }
+
+ return 0;
+}
diff --git a/src/nvim/move.c b/src/nvim/move.c
index a6afdc27d9..1210a3365a 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -888,11 +888,11 @@ void curs_columns(
} else {
n = plines;
}
- if ((colnr_T)n >= wp->w_height_inner + wp->w_skipcol / width) {
+ if ((colnr_T)n >= wp->w_height_inner + wp->w_skipcol / width - so) {
extra += 2;
}
- if (extra == 3 || plines < so * 2) {
+ if (extra == 3 || plines <= so * 2) {
// not enough room for 'scrolloff', put cursor in the middle
n = wp->w_virtcol / width;
if (n > wp->w_height_inner / 2) {
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
index a0b439ac45..a2d8859c68 100644
--- a/src/nvim/msgpack_rpc/channel.c
+++ b/src/nvim/msgpack_rpc/channel.c
@@ -219,7 +219,7 @@ static void receive_msgpack(Stream *stream, RBuffer *rbuf, size_t c,
char buf[256];
snprintf(buf, sizeof(buf), "ch %" PRIu64 " was closed by the client",
channel->id);
- call_set_error(channel, buf, WARN_LOG_LEVEL);
+ call_set_error(channel, buf, INFO_LOG_LEVEL);
goto end;
}
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 8f22243348..c948881eca 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -630,9 +630,9 @@ static void normal_redraw_mode_message(NormalState *s)
ui_cursor_shape(); // show different cursor shape
ui_flush();
if (msg_scroll || emsg_on_display) {
- os_delay(1000L, true); // wait at least one second
+ os_delay(1003L, true); // wait at least one second
}
- os_delay(3000L, false); // wait up to three seconds
+ os_delay(3003L, false); // wait up to three seconds
State = save_State;
msg_scroll = false;
@@ -2404,8 +2404,8 @@ do_mouse (
start_visual.lnum = 0;
- /* Check for clicking in the tab page line. */
- if (mouse_row == 0 && firstwin->w_winrow > 0) {
+ // Check for clicking in the tab page line.
+ if (mouse_grid <= 1 && mouse_row == 0 && firstwin->w_winrow > 0) {
if (is_drag) {
if (in_tab_line) {
move_tab_to_mouse();
@@ -3971,7 +3971,8 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist)
while (dist--) {
if (dir == BACKWARD) {
- if (curwin->w_curswant >= width1) {
+ if (curwin->w_curswant >= width1
+ && !hasFolding(curwin->w_cursor.lnum, NULL, NULL)) {
// Move back within the line. This can give a negative value
// for w_curswant if width1 < width2 (with cpoptions+=n),
// which will get clipped to column 0.
@@ -4003,14 +4004,16 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist)
n = ((linelen - width1 - 1) / width2 + 1) * width2 + width1;
else
n = width1;
- if (curwin->w_curswant + width2 < (colnr_T)n)
- /* move forward within line */
+ if (curwin->w_curswant + width2 < (colnr_T)n
+ && !hasFolding(curwin->w_cursor.lnum, NULL, NULL)) {
+ // move forward within line
curwin->w_curswant += width2;
- else {
- /* to next line */
- /* Move to the end of a closed fold. */
+ } else {
+ // to next line
+
+ // Move to the end of a closed fold.
(void)hasFolding(curwin->w_cursor.lnum, NULL,
- &curwin->w_cursor.lnum);
+ &curwin->w_cursor.lnum);
if (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) {
retval = false;
break;
@@ -5225,16 +5228,13 @@ static void nv_left(cmdarg_T *cap)
* 'h' wraps to previous line if 'whichwrap' has 'h'.
* CURS_LEFT wraps to previous line if 'whichwrap' has '<'.
*/
- if ( (((cap->cmdchar == K_BS
- || cap->cmdchar == Ctrl_H)
- && vim_strchr(p_ww, 'b') != NULL)
- || (cap->cmdchar == 'h'
- && vim_strchr(p_ww, 'h') != NULL)
- || (cap->cmdchar == K_LEFT
- && vim_strchr(p_ww, '<') != NULL))
- && curwin->w_cursor.lnum > 1) {
- --(curwin->w_cursor.lnum);
- coladvance((colnr_T)MAXCOL);
+ if ((((cap->cmdchar == K_BS || cap->cmdchar == Ctrl_H)
+ && vim_strchr(p_ww, 'b') != NULL)
+ || (cap->cmdchar == 'h' && vim_strchr(p_ww, 'h') != NULL)
+ || (cap->cmdchar == K_LEFT && vim_strchr(p_ww, '<') != NULL))
+ && curwin->w_cursor.lnum > 1) {
+ curwin->w_cursor.lnum--;
+ coladvance(MAXCOL);
curwin->w_set_curswant = true;
// When the NL before the first char has to be deleted we
@@ -5462,7 +5462,7 @@ static int normal_search(
curwin->w_set_curswant = true;
memset(&sia, 0, sizeof(sia));
- i = do_search(cap->oap, dir, pat, cap->count1,
+ i = do_search(cap->oap, dir, dir, pat, cap->count1,
opt | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG, &sia);
if (wrapped != NULL) {
*wrapped = sia.sa_wrapped;
@@ -5792,16 +5792,20 @@ static void nv_percent(cmdarg_T *cap)
} else {
cap->oap->motion_type = kMTLineWise;
setpcmark();
- /* Round up, so CTRL-G will give same value. Watch out for a
- * large line count, the line number must not go negative! */
- if (curbuf->b_ml.ml_line_count > 1000000)
+ // Round up, so 'normal 100%' always jumps at the line line.
+ // Beyond 21474836 lines, (ml_line_count * 100 + 99) would
+ // overflow on 32-bits, so use a formula with less accuracy
+ // to avoid overflows.
+ if (curbuf->b_ml.ml_line_count >= 21474836) {
curwin->w_cursor.lnum = (curbuf->b_ml.ml_line_count + 99L)
/ 100L * cap->count0;
- else
+ } else {
curwin->w_cursor.lnum = (curbuf->b_ml.ml_line_count *
cap->count0 + 99L) / 100L;
- if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count)
+ }
+ if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
+ }
beginline(BL_SOL | BL_FIX);
}
} else { // "%" : go to matching paren
@@ -6467,7 +6471,7 @@ static void nv_visual(cmdarg_T *cap)
}
if (resel_VIsual_vcol == MAXCOL) {
curwin->w_curswant = MAXCOL;
- coladvance((colnr_T)MAXCOL);
+ coladvance(MAXCOL);
} else if (VIsual_mode == Ctrl_V) {
validate_virtcol();
assert(cap->count0 >= INT_MIN && cap->count0 <= INT_MAX);
@@ -6776,9 +6780,10 @@ static void nv_g_cmd(cmdarg_T *cap)
}
coladvance((colnr_T)i);
if (flag) {
- do
+ do {
i = gchar_cursor();
- while (ascii_iswhite(i) && oneright());
+ } while (ascii_iswhite(i) && oneright());
+ curwin->w_valid &= ~VALID_WCOL;
}
curwin->w_set_curswant = true;
break;
@@ -7570,6 +7575,12 @@ static void nv_esc(cmdarg_T *cap)
got_int = false; /* don't stop executing autocommands et al. */
return;
}
+ } else if (cmdwin_type != 0 && ex_normal_busy) {
+ // When :normal runs out of characters while in the command line window
+ // vgetorpeek() will return ESC. Exit the cmdline window to break the
+ // loop.
+ cmdwin_result = K_IGNORE;
+ return;
}
if (VIsual_active) {
@@ -7600,7 +7611,7 @@ void set_cursor_for_append_to_line(void)
// Pretend Insert mode here to allow the cursor on the
// character past the end of the line
State = INSERT;
- coladvance((colnr_T)MAXCOL);
+ coladvance(MAXCOL);
State = save_State;
} else {
curwin->w_cursor.col += (colnr_T)STRLEN(get_cursor_pos_ptr());
@@ -7988,7 +7999,7 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
* line. */
if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
- coladvance((colnr_T)MAXCOL);
+ coladvance(MAXCOL);
}
}
auto_format(false, true);
@@ -8096,7 +8107,7 @@ static void nv_event(cmdarg_T *cap)
// lists or dicts being used.
may_garbage_collect = false;
bool may_restart = (restart_edit != 0);
- multiqueue_process_events(main_loop.events);
+ state_handle_k_event();
finish_op = false;
if (may_restart) {
// Tricky: if restart_edit was set before the handler we are in ctrl-o mode,
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 052b07ed44..190ca2e93b 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -288,7 +288,7 @@ void shift_line(
{
int count;
int i, j;
- int p_sw = get_sw_value(curbuf);
+ int p_sw = (int)get_sw_value_indent(curbuf);
count = get_indent(); // get current indent
@@ -332,8 +332,9 @@ static void shift_block(oparg_T *oap, int amount)
const int oldstate = State;
char_u *newp;
const int oldcol = curwin->w_cursor.col;
- const int p_sw = get_sw_value(curbuf);
- const int p_ts = (int)curbuf->b_p_ts;
+ int p_sw = (int)get_sw_value_indent(curbuf);
+ long *p_vts = curbuf->b_p_vts_array;
+ const long p_ts = curbuf->b_p_ts;
struct block_def bd;
int incr;
int i = 0, j = 0;
@@ -383,12 +384,11 @@ static void shift_block(oparg_T *oap, int amount)
}
/* OK, now total=all the VWS reqd, and textstart points at the 1st
* non-ws char in the block. */
- if (!curbuf->b_p_et)
- i = ((ws_vcol % p_ts) + total) / p_ts; /* number of tabs */
- if (i)
- j = ((ws_vcol % p_ts) + total) % p_ts; /* number of spp */
- else
+ if (!curbuf->b_p_et) {
+ tabstop_fromto(ws_vcol, ws_vcol + total, p_ts, p_vts, &i, &j);
+ } else {
j = total;
+ }
// if we're splitting a TAB, allow for it
int col_pre = bd.pre_whitesp_c - (bd.startspaces != 0);
@@ -1079,13 +1079,15 @@ do_execreg(
}
}
escaped = vim_strsave_escape_csi(reg->y_array[i]);
- retval = ins_typebuf(escaped, remap, 0, TRUE, silent);
+ retval = ins_typebuf(escaped, remap, 0, true, silent);
xfree(escaped);
- if (retval == FAIL)
+ if (retval == FAIL) {
return FAIL;
- if (colon && ins_typebuf((char_u *)":", remap, 0, TRUE, silent)
- == FAIL)
+ }
+ if (colon
+ && ins_typebuf((char_u *)":", remap, 0, true, silent) == FAIL) {
return FAIL;
+ }
}
reg_executing = regname == 0 ? '"' : regname; // disable the 'q' command
}
@@ -1109,8 +1111,9 @@ static void put_reedit_in_typebuf(int silent)
buf[0] = (char_u)(restart_edit == 'I' ? 'i' : restart_edit);
buf[1] = NUL;
}
- if (ins_typebuf(buf, REMAP_NONE, 0, TRUE, silent) == OK)
+ if (ins_typebuf(buf, REMAP_NONE, 0, true, silent) == OK) {
restart_edit = NUL;
+ }
}
}
@@ -1130,25 +1133,29 @@ static int put_in_typebuf(
int retval = OK;
put_reedit_in_typebuf(silent);
- if (colon)
- retval = ins_typebuf((char_u *)"\n", REMAP_NONE, 0, TRUE, silent);
+ if (colon) {
+ retval = ins_typebuf((char_u *)"\n", REMAP_NONE, 0, true, silent);
+ }
if (retval == OK) {
char_u *p;
- if (esc)
+ if (esc) {
p = vim_strsave_escape_csi(s);
- else
+ } else {
p = s;
- if (p == NULL)
+ }
+ if (p == NULL) {
retval = FAIL;
- else
- retval = ins_typebuf(p, esc ? REMAP_NONE : REMAP_YES,
- 0, TRUE, silent);
- if (esc)
+ } else {
+ retval = ins_typebuf(p, esc ? REMAP_NONE : REMAP_YES, 0, true, silent);
+ }
+ if (esc) {
xfree(p);
+ }
+ }
+ if (colon && retval == OK) {
+ retval = ins_typebuf((char_u *)":", REMAP_NONE, 0, true, silent);
}
- if (colon && retval == OK)
- retval = ins_typebuf((char_u *)":", REMAP_NONE, 0, TRUE, silent);
return retval;
}
@@ -1669,12 +1676,18 @@ int op_delete(oparg_T *oap)
curbuf_splice_pending++;
pos_T startpos = curwin->w_cursor; // start position for delete
+ bcount_t deleted_bytes = (bcount_t)STRLEN(
+ ml_get(startpos.lnum)) + 1 - startpos.col;
truncate_line(true); // delete from cursor to end of line
curpos = curwin->w_cursor; // remember curwin->w_cursor
curwin->w_cursor.lnum++;
+
+ for (linenr_T i = 1; i <= oap->line_count - 2; i++) {
+ deleted_bytes += (bcount_t)STRLEN(
+ ml_get(startpos.lnum + i)) + 1;
+ }
del_lines(oap->line_count - 2, false);
- bcount_t deleted_bytes = (bcount_t)curbuf->deleted_bytes2 - startpos.col;
// delete from start of line until op_end
n = (oap->end.col + 1 - !oap->inclusive);
@@ -2623,7 +2636,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
}
// NOTREACHED
case kMTUnknown:
- assert(false);
+ abort();
}
}
@@ -2800,7 +2813,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
size_t y_size;
size_t oldlen;
int y_width = 0;
- colnr_T vcol;
+ colnr_T vcol = 0;
int delcount;
int incr = 0;
struct block_def bd;
@@ -3061,14 +3074,17 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
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();
if (dir == FORWARD
- ? (int)curwin->w_cursor.coladd < curbuf->b_p_ts - 1
- : curwin->w_cursor.coladd > 0)
- coladvance_force(getviscol());
- else
+ ? tabstop_padding(viscol, curbuf->b_p_ts, curbuf->b_p_vts_array) != 1
+ : curwin->w_cursor.coladd > 0) {
+ coladvance_force(viscol);
+ } else {
curwin->w_cursor.coladd = 0;
- } else if (curwin->w_cursor.coladd > 0 || gchar_cursor() == NUL)
+ }
+ } else if (curwin->w_cursor.coladd > 0 || gchar_cursor() == NUL) {
coladvance_force(getviscol() + (dir == FORWARD));
+ }
}
lnum = curwin->w_cursor.lnum;
@@ -3177,7 +3193,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
// insert the new text
totlen = (size_t)(count * (yanklen + spaces)
+ bd.startspaces + bd.endspaces);
- newp = (char_u *) xmalloc(totlen + oldlen + 1);
+ int addcount = (int)totlen + lines_appended;
+ newp = (char_u *)xmalloc(totlen + oldlen + 1);
// copy part up to cursor to new line
ptr = newp;
memmove(ptr, oldp, (size_t)bd.textcol);
@@ -3194,6 +3211,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
if ((j < count - 1 || !shortline) && spaces) {
memset(ptr, ' ', (size_t)spaces);
ptr += spaces;
+ } else {
+ addcount -= spaces;
}
}
// may insert some spaces after the new text
@@ -3205,7 +3224,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
memmove(ptr, oldp + bd.textcol + delcount, (size_t)columns);
ml_replace(curwin->w_cursor.lnum, newp, false);
extmark_splice_cols(curbuf, (int)curwin->w_cursor.lnum-1, bd.textcol,
- delcount, (int)totlen + lines_appended, kExtmarkUndo);
+ delcount, addcount, kExtmarkUndo);
++curwin->w_cursor.lnum;
if (i == 0)
@@ -3422,15 +3441,11 @@ error:
if (dir == FORWARD)
curbuf->b_op_start.lnum++;
}
- // Skip mark_adjust when adding lines after the last one, there
- // can't be marks there.
- if (curbuf->b_op_start.lnum + (y_type == kMTCharWise) - 1 + nr_lines
- < curbuf->b_ml.ml_line_count) {
- ExtmarkOp kind = (y_type == kMTLineWise && !(flags & PUT_LINE_SPLIT))
- ? kExtmarkUndo : kExtmarkNOOP;
- mark_adjust(curbuf->b_op_start.lnum + (y_type == kMTCharWise),
- (linenr_T)MAXLNUM, nr_lines, 0L, kind);
- }
+
+ ExtmarkOp kind = (y_type == kMTLineWise && !(flags & PUT_LINE_SPLIT))
+ ? kExtmarkUndo : kExtmarkNOOP;
+ mark_adjust(curbuf->b_op_start.lnum + (y_type == kMTCharWise),
+ (linenr_T)MAXLNUM, nr_lines, 0L, kind);
// note changed text for displaying and folding
if (y_type == kMTCharWise) {
@@ -4309,11 +4324,12 @@ format_lines(
* tabs and spaces, according to current options */
(void)set_indent(get_indent(), SIN_CHANGED);
- /* put cursor on last non-space */
- State = NORMAL; /* don't go past end-of-line */
- coladvance((colnr_T)MAXCOL);
- while (curwin->w_cursor.col && ascii_isspace(gchar_cursor()))
+ // put cursor on last non-space
+ State = NORMAL; // don't go past end-of-line
+ coladvance(MAXCOL);
+ while (curwin->w_cursor.col && ascii_isspace(gchar_cursor())) {
dec_cursor();
+ }
/* do the formatting, without 'showmode' */
State = INSERT; /* for open_line() */
@@ -6092,7 +6108,7 @@ static void set_clipboard(int name, yankreg_T *reg)
break;
}
case kMTUnknown: {
- assert(false);
+ abort();
}
}
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 74bf6f0590..666c526a18 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -180,6 +180,8 @@ static long p_ts;
static long p_tw;
static int p_udf;
static long p_wm;
+static char_u *p_vsts;
+static char_u *p_vts;
static char_u *p_keymap;
// Saved values for when 'bin' is set.
@@ -194,6 +196,7 @@ static int p_et_nopaste;
static long p_sts_nopaste;
static long p_tw_nopaste;
static long p_wm_nopaste;
+static char_u *p_vsts_nopaste;
typedef struct vimoption {
char *fullname; // full option name
@@ -379,8 +382,8 @@ void set_init_1(bool clean_arg)
# else
static char *(names[3]) = {"TMPDIR", "TEMP", "TMP"};
# endif
- int len;
garray_T ga;
+ opt_idx = findoption("backupskip");
ga_init(&ga, 1, 100);
for (size_t n = 0; n < ARRAY_SIZE(names); n++) {
@@ -401,15 +404,23 @@ void set_init_1(bool clean_arg)
}
if (p != NULL && *p != NUL) {
// First time count the NUL, otherwise count the ','.
- len = (int)strlen(p) + 3;
- ga_grow(&ga, len);
- if (!GA_EMPTY(&ga)) {
- STRCAT(ga.ga_data, ",");
+ const size_t len = strlen(p) + 3;
+ char *item = xmalloc(len);
+ xstrlcpy(item, p, len);
+ add_pathsep(item);
+ xstrlcat(item, "*", len);
+ if (find_dup_item(ga.ga_data, (char_u *)item, options[opt_idx].flags)
+ == NULL) {
+ ga_grow(&ga, (int)len);
+ if (!GA_EMPTY(&ga)) {
+ STRCAT(ga.ga_data, ",");
+ }
+ STRCAT(ga.ga_data, p);
+ add_pathsep(ga.ga_data);
+ STRCAT(ga.ga_data, "*");
+ ga.ga_len += (int)len;
}
- STRCAT(ga.ga_data, p);
- add_pathsep(ga.ga_data);
- STRCAT(ga.ga_data, "*");
- ga.ga_len += len;
+ xfree(item);
}
if(mustfree) {
xfree(p);
@@ -713,6 +724,38 @@ static void set_string_default(const char *name, char *val, bool allocated)
}
}
+// For an option value that contains comma separated items, find "newval" in
+// "origval". Return NULL if not found.
+static char_u *find_dup_item(char_u *origval, const char_u *newval,
+ uint32_t flags)
+ FUNC_ATTR_NONNULL_ARG(2)
+{
+ int bs = 0;
+
+ if (origval == NULL) {
+ return NULL;
+ }
+
+ const size_t newlen = STRLEN(newval);
+ for (char_u *s = origval; *s != NUL; s++) {
+ if ((!(flags & P_COMMA) || s == origval || (s[-1] == ',' && !(bs & 1)))
+ && STRNCMP(s, newval, newlen) == 0
+ && (!(flags & P_COMMA) || s[newlen] == ',' || s[newlen] == NUL)) {
+ return s;
+ }
+ // Count backslashes. Only a comma with an even number of backslashes
+ // or a single backslash preceded by a comma before it is recognized as
+ // a separator.
+ if ((s > origval + 1 && s[-1] == '\\' && s[-2] != ',')
+ || (s == origval + 1 && s[-1] == '\\')) {
+ bs++;
+ } else {
+ bs = 0;
+ }
+ }
+ return NULL;
+}
+
/// Set the Vi-default value of a number option.
/// Used for 'lines' and 'columns'.
void set_number_default(char *name, long val)
@@ -1285,9 +1328,7 @@ int do_set(
char *saved_newval = NULL;
unsigned newlen;
int comma;
- int bs;
- int new_value_alloced; /* new string option
- was allocated */
+ bool new_value_alloced = false; // new string option was allocated
/* When using ":set opt=val" for a global option
* with a local value the local value will be
@@ -1486,34 +1527,20 @@ int do_set(
i = 0; // init for GCC
if (removing || (flags & P_NODUP)) {
i = (int)STRLEN(newval);
- bs = 0;
- for (s = origval; *s; s++) {
- if ((!(flags & P_COMMA)
- || s == origval
- || (s[-1] == ',' && !(bs & 1)))
- && STRNCMP(s, newval, i) == 0
- && (!(flags & P_COMMA)
- || s[i] == ','
- || s[i] == NUL)) {
- break;
- }
- // Count backslashes. Only a comma with an even number of
- // backslashes or a single backslash preceded by a comma
- // before it is recognized as a separator
- if ((s > origval + 1 && s[-1] == '\\' && s[-2] != ',')
- || (s == origval + 1 && s[-1] == '\\')) {
- bs++;
- } else {
- bs = 0;
- }
- }
+ s = find_dup_item(origval, newval, flags);
// do not add if already there
- if ((adding || prepending) && *s) {
+ if ((adding || prepending) && s != NULL) {
prepending = false;
adding = false;
STRCPY(newval, origval);
}
+
+ // if no duplicate, move pointer to end of
+ // original value
+ if (s == NULL) {
+ s = origval + (int)STRLEN(origval);
+ }
}
/* concatenate the two strings; add a ',' if
@@ -1942,6 +1969,7 @@ static void didset_options(void)
(void)opt_strings_flags(p_dy, p_dy_values, &dy_flags, true);
(void)opt_strings_flags(p_rdb, p_rdb_values, &rdb_flags, true);
(void)opt_strings_flags(p_tc, p_tc_values, &tc_flags, false);
+ (void)opt_strings_flags(p_tpf, p_tpf_values, &tpf_flags, true);
(void)opt_strings_flags(p_ve, p_ve_values, &ve_flags, true);
(void)opt_strings_flags(p_wop, p_wop_values, &wop_flags, true);
(void)opt_strings_flags(p_jop, p_jop_values, &jop_flags, true);
@@ -1973,6 +2001,10 @@ 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);
+ xfree(curbuf->b_p_vts_array);
+ tabstop_set(curbuf->b_p_vts, &curbuf->b_p_vts_array);
}
/// Check for string options that are NULL (normally only termcap options).
@@ -2039,6 +2071,8 @@ void check_buf_options(buf_T *buf)
check_string_option(&buf->b_p_lw);
check_string_option(&buf->b_p_bkc);
check_string_option(&buf->b_p_menc);
+ check_string_option(&buf->b_p_vsts);
+ check_string_option(&buf->b_p_vts);
}
/// Free the string allocated for an option.
@@ -2085,9 +2119,12 @@ int was_set_insecurely(win_T *const wp, char_u *opt, int opt_flags)
/// Get a pointer to the flags used for the P_INSECURE flag of option
/// "opt_idx". For some local options a local flags field is used.
+/// NOTE: Caller must make sure that "wp" is set to the window from which
+/// the option is used.
static uint32_t *insecure_flag(win_T *const wp, int opt_idx, int opt_flags)
{
- if (opt_flags & OPT_LOCAL)
+ if (opt_flags & OPT_LOCAL) {
+ assert(wp != NULL);
switch ((int)options[opt_idx].indir) {
case PV_STL: return &wp->w_p_stl_flags;
case PV_FDE: return &wp->w_p_fde_flags;
@@ -2096,6 +2133,7 @@ static uint32_t *insecure_flag(win_T *const wp, int opt_idx, int opt_flags)
case PV_FEX: return &wp->w_buffer->b_p_fex_flags;
case PV_INEX: return &wp->w_buffer->b_p_inex_flags;
}
+ }
// Nothing special, return global flags field.
return &options[opt_idx].flags;
@@ -2119,9 +2157,9 @@ static int shada_idx = -1;
// "set_sid".
void
set_string_option_direct(
- char_u *name,
+ const char *name,
int opt_idx,
- char_u *val,
+ const char_u *val,
int opt_flags, // OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL
int set_sid
)
@@ -2132,7 +2170,7 @@ set_string_option_direct(
int idx = opt_idx;
if (idx == -1) { // Use name.
- idx = findoption((const char *)name);
+ idx = findoption(name);
if (idx < 0) { // Not found (should not happen).
internal_error("set_string_option_direct()");
IEMSG2(_("For option %s"), name);
@@ -2305,7 +2343,7 @@ static char_u *
did_set_string_option(
int opt_idx, // index in options[] table
char_u **varp, // pointer to the option variable
- int new_value_alloced, // new value was allocated
+ bool new_value_alloced, // new value was allocated
char_u *oldval, // previous value of the option
char_u *errbuf, // buffer for errors, or NULL
size_t errbuflen, // length of errors buffer
@@ -3077,6 +3115,69 @@ ambw_end:
if (!parse_winhl_opt(curwin)) {
errmsg = e_invarg;
}
+ } else if (varp == &p_tpf) {
+ if (opt_strings_flags(p_tpf, p_tpf_values, &tpf_flags, true) != OK) {
+ errmsg = e_invarg;
+ }
+ } else if (varp == &(curbuf->b_p_vsts)) { // 'varsofttabstop'
+ 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;
+ }
+ } else {
+ for (cp = *varp; *cp; cp++) {
+ if (ascii_isdigit(*cp)) {
+ continue;
+ }
+ if (*cp == ',' && cp > *varp && *(cp - 1) != ',') {
+ continue;
+ }
+ errmsg = e_invarg;
+ break;
+ }
+ if (errmsg == NULL) {
+ long *oldarray = curbuf->b_p_vsts_array;
+ if (tabstop_set(*varp, &(curbuf->b_p_vsts_array))) {
+ xfree(oldarray);
+ } else {
+ errmsg = e_invarg;
+ }
+ }
+ }
+ } else if (varp == &(curbuf->b_p_vts)) { // 'vartabstop'
+ 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;
+ }
+ } else {
+ for (cp = *varp; *cp; cp++) {
+ if (ascii_isdigit(*cp)) {
+ continue;
+ }
+ if (*cp == ',' && cp > *varp && *(cp - 1) != ',') {
+ continue;
+ }
+ errmsg = e_invarg;
+ break;
+ }
+ if (errmsg == NULL) {
+ long *oldarray = curbuf->b_p_vts_array;
+ if (tabstop_set(*varp, &(curbuf->b_p_vts_array))) {
+ xfree(oldarray);
+ if (foldmethodIsIndent(curwin)) {
+ foldUpdateAll(curwin);
+ }
+ } else {
+ errmsg = e_invarg;
+ }
+ }
+ }
} else {
// Options that are a list of flags.
p = NULL;
@@ -3336,6 +3437,12 @@ skip:
return NULL; // no error
}
+void check_blending(win_T *wp)
+{
+ wp->w_grid_alloc.blending =
+ wp->w_p_winbl > 0 || (wp->w_floating && wp->w_float_config.shadow);
+}
+
/// Handle setting 'listchars' or 'fillchars'.
/// Assume monocell characters
@@ -3376,6 +3483,7 @@ static char_u *set_chars_option(win_T *wp, char_u **varp, bool set)
{ &wp->w_p_lcs_chars.prec, "precedes", NUL },
{ &wp->w_p_lcs_chars.space, "space", NUL },
{ &wp->w_p_lcs_chars.tab2, "tab", NUL },
+ { &wp->w_p_lcs_chars.lead, "lead", NUL },
{ &wp->w_p_lcs_chars.trail, "trail", NUL },
{ &wp->w_p_lcs_chars.conceal, "conceal", NUL },
};
@@ -3786,7 +3894,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp,
if (p_terse && p == NULL) {
STRCPY(IObuff, p_shm);
STRCAT(IObuff, "s");
- set_string_option_direct((char_u *)"shm", -1, IObuff, OPT_FREE, 0);
+ set_string_option_direct("shm", -1, IObuff, OPT_FREE, 0);
} else if (!p_terse && p != NULL) { // remove 's' from p_shm
STRMOVE(p, p + 1);
}
@@ -4278,7 +4386,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value,
// 'floatblend'
curwin->w_p_winbl = MAX(MIN(curwin->w_p_winbl, 100), 0);
curwin->w_hl_needs_update = true;
- curwin->w_grid.blending = curwin->w_p_winbl > 0;
+ check_blending(curwin);
}
@@ -4526,7 +4634,7 @@ bool is_tty_option(const char *name)
#define TCO_BUFFER_SIZE 8
/// @param name TUI-related option
/// @param[out,allocated] value option string value
-bool get_tty_option(char *name, char **value)
+bool get_tty_option(const char *name, char **value)
{
if (strequal(name, "t_Co")) {
if (value) {
@@ -4592,6 +4700,7 @@ bool set_tty_option(const char *name, char *value)
///
/// @return Option index or -1 if option was not found.
static int findoption(const char *const arg)
+ FUNC_ATTR_NONNULL_ALL
{
return findoption_len(arg, strlen(arg));
}
@@ -4605,17 +4714,17 @@ static int findoption(const char *const arg)
/// hidden String option: -2.
/// unknown option: -3.
int get_option_value(
- char_u *name,
+ const char *name,
long *numval,
char_u **stringval, ///< NULL when only checking existence
int opt_flags
)
{
- if (get_tty_option((char *)name, (char **)stringval)) {
+ if (get_tty_option(name, (char **)stringval)) {
return 0;
}
- int opt_idx = findoption((const char *)name);
+ int opt_idx = findoption(name);
if (opt_idx < 0) { // Unknown option.
return -3;
}
@@ -5650,6 +5759,8 @@ static char_u *get_varp(vimoption_T *p)
case PV_TW: return (char_u *)&(curbuf->b_p_tw);
case PV_UDF: return (char_u *)&(curbuf->b_p_udf);
case PV_WM: return (char_u *)&(curbuf->b_p_wm);
+ case PV_VSTS: return (char_u *)&(curbuf->b_p_vsts);
+ case PV_VTS: return (char_u *)&(curbuf->b_p_vts);
case PV_KMAP: return (char_u *)&(curbuf->b_p_keymap);
case PV_SCL: return (char_u *)&(curwin->w_p_scl);
case PV_WINHL: return (char_u *)&(curwin->w_p_winhl);
@@ -5790,7 +5901,8 @@ void didset_window_options(win_T *wp)
set_chars_option(wp, &wp->w_p_fcs, true);
set_chars_option(wp, &wp->w_p_lcs, true);
parse_winhl_opt(wp); // sets w_hl_needs_update also for w_p_winbl
- wp->w_grid.blending = wp->w_p_winbl > 0;
+ check_blending(wp);
+ wp->w_grid_alloc.blending = wp->w_p_winbl > 0;
}
@@ -5901,6 +6013,15 @@ void buf_copy_options(buf_T *buf, int flags)
buf->b_p_tfu = vim_strsave(p_tfu);
buf->b_p_sts = p_sts;
buf->b_p_sts_nopaste = p_sts_nopaste;
+ buf->b_p_vsts = vim_strsave(p_vsts);
+ if (p_vsts && p_vsts != empty_option) {
+ tabstop_set(p_vsts, &buf->b_p_vsts_array);
+ } else {
+ buf->b_p_vsts_array = 0;
+ }
+ buf->b_p_vsts_nopaste = p_vsts_nopaste
+ ? vim_strsave(p_vsts_nopaste)
+ : NULL;
buf->b_p_com = vim_strsave(p_com);
buf->b_p_cms = vim_strsave(p_cms);
buf->b_p_fo = vim_strsave(p_fo);
@@ -5972,10 +6093,21 @@ 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);
+ } else {
+ buf->b_p_vts_array = NULL;
+ }
} else {
buf->b_p_isk = vim_strsave(p_isk);
did_isk = true;
buf->b_p_ts = p_ts;
+ buf->b_p_vts = vim_strsave(p_vts);
+ if (p_vts && p_vts != empty_option && !buf->b_p_vts_array) {
+ tabstop_set(p_vts, &buf->b_p_vts_array);
+ } else {
+ buf->b_p_vts_array = NULL;
+ }
buf->b_help = false;
if (buf->b_p_bt[0] == 'h') {
clear_string_option(&buf->b_p_bt);
@@ -6172,6 +6304,8 @@ set_context_in_set_cmd(
xp->xp_backslash = XP_BS_THREE;
else
xp->xp_backslash = XP_BS_ONE;
+ } else if (p == (char_u *)&p_ft) {
+ xp->xp_context = EXPAND_FILETYPE;
} else {
xp->xp_context = EXPAND_FILES;
// for 'tags' need three backslashes for a space
@@ -6588,6 +6722,12 @@ static void paste_option_changed(void)
buf->b_p_sts_nopaste = buf->b_p_sts;
buf->b_p_ai_nopaste = buf->b_p_ai;
buf->b_p_et_nopaste = buf->b_p_et;
+ if (buf->b_p_vsts_nopaste) {
+ xfree(buf->b_p_vsts_nopaste);
+ }
+ buf->b_p_vsts_nopaste = buf->b_p_vsts && buf->b_p_vsts != empty_option
+ ? vim_strsave(buf->b_p_vsts)
+ : NULL;
}
// save global options
@@ -6602,6 +6742,12 @@ static void paste_option_changed(void)
p_sts_nopaste = p_sts;
p_tw_nopaste = p_tw;
p_wm_nopaste = p_wm;
+ if (p_vsts_nopaste) {
+ xfree(p_vsts_nopaste);
+ }
+ p_vsts_nopaste = p_vsts && p_vsts != empty_option
+ ? vim_strsave(p_vsts)
+ : NULL;
}
// Always set the option values, also when 'paste' is set when it is
@@ -6613,6 +6759,14 @@ static void paste_option_changed(void)
buf->b_p_sts = 0; // softtabstop is 0
buf->b_p_ai = 0; // no auto-indent
buf->b_p_et = 0; // no expandtab
+ if (buf->b_p_vsts) {
+ 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;
}
// set global options
@@ -6629,6 +6783,10 @@ static void paste_option_changed(void)
p_wm = 0;
p_sts = 0;
p_ai = 0;
+ if (p_vsts) {
+ free_string_option(p_vsts);
+ }
+ p_vsts = empty_option;
} else if (old_p_paste) {
// Paste switched from on to off: Restore saved values.
@@ -6639,6 +6797,20 @@ static void paste_option_changed(void)
buf->b_p_sts = buf->b_p_sts_nopaste;
buf->b_p_ai = buf->b_p_ai_nopaste;
buf->b_p_et = buf->b_p_et_nopaste;
+ if (buf->b_p_vsts) {
+ free_string_option(buf->b_p_vsts);
+ }
+ 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);
+ }
+ if (buf->b_p_vsts && buf->b_p_vsts != empty_option) {
+ tabstop_set(buf->b_p_vsts, &buf->b_p_vsts_array);
+ } else {
+ buf->b_p_vsts_array = 0;
+ }
}
// restore global options
@@ -6656,6 +6828,10 @@ static void paste_option_changed(void)
p_sts = p_sts_nopaste;
p_tw = p_tw_nopaste;
p_wm = p_wm_nopaste;
+ if (p_vsts) {
+ free_string_option(p_vsts);
+ }
+ p_vsts = p_vsts_nopaste ? vim_strsave(p_vsts_nopaste) : empty_option;
}
old_p_paste = p_paste;
@@ -6905,17 +7081,301 @@ int check_ff_value(char_u *p)
return check_opt_strings(p, p_ff_values, false);
}
+// Set the integer values corresponding to the string setting of 'vartabstop'.
+// "array" will be set, caller must free it if needed.
+bool tabstop_set(char_u *var, long **array)
+{
+ long valcount = 1;
+ int t;
+ char_u *cp;
+
+ if (var[0] == NUL || (var[0] == '0' && var[1] == NUL)) {
+ *array = NULL;
+ return true;
+ }
+
+ for (cp = var; *cp != NUL; cp++) {
+ if (cp == var || cp[-1] == ',') {
+ char_u *end;
+
+ if (strtol((char *)cp, (char **)&end, 10) <= 0) {
+ if (cp != end) {
+ EMSG(_(e_positive));
+ } else {
+ EMSG(_(e_invarg));
+ }
+ return false;
+ }
+ }
+
+ if (ascii_isdigit(*cp)) {
+ continue;
+ }
+ if (cp[0] == ',' && cp > var && cp[-1] != ',' && cp[1] != NUL) {
+ valcount++;
+ continue;
+ }
+ EMSG(_(e_invarg));
+ return false;
+ }
+
+ *array = (long *)xmalloc((unsigned)(valcount + 1) * sizeof(long));
+ (*array)[0] = valcount;
+
+ t = 1;
+ for (cp = var; *cp != NUL;) {
+ (*array)[t++] = atoi((char *)cp);
+ while (*cp != NUL && *cp != ',') {
+ cp++;
+ }
+ if (*cp != NUL) {
+ cp++;
+ }
+ }
+
+ return true;
+}
+
+// Calculate the number of screen spaces a tab will occupy.
+// If "vts" is set then the tab widths are taken from that array,
+// otherwise the value of ts is used.
+int tabstop_padding(colnr_T col, long ts_arg, long *vts)
+{
+ long ts = ts_arg == 0 ? 8 : ts_arg;
+ colnr_T tabcol = 0;
+ int t;
+ long padding = 0;
+
+ if (vts == NULL || vts[0] == 0) {
+ return (int)(ts - (col % ts));
+ }
+
+ const long tabcount = vts[0];
+
+ for (t = 1; t <= tabcount; t++) {
+ tabcol += (colnr_T)vts[t];
+ if (tabcol > col) {
+ padding = tabcol - col;
+ break;
+ }
+ }
+ if (t > tabcount) {
+ padding = vts[tabcount] - ((col - tabcol) % vts[tabcount]);
+ }
+
+ return (int)padding;
+}
+
+// Find the size of the tab that covers a particular column.
+int tabstop_at(colnr_T col, long ts, long *vts)
+{
+ colnr_T tabcol = 0;
+ int t;
+ long tab_size = 0;
+
+ if (vts == NULL || vts[0] == 0) {
+ return (int)ts;
+ }
+
+ const long tabcount = vts[0];
+ for (t = 1; t <= tabcount; t++) {
+ tabcol += (colnr_T)vts[t];
+ if (tabcol > col) {
+ tab_size = vts[t];
+ break;
+ }
+ }
+ if (t > tabcount) {
+ tab_size = vts[tabcount];
+ }
+
+ return (int)tab_size;
+}
+
+// Find the column on which a tab starts.
+colnr_T tabstop_start(colnr_T col, long ts, long *vts)
+{
+ colnr_T tabcol = 0;
+ int t;
+
+ if (vts == NULL || vts[0] == 0) {
+ return (int)((col / ts) * ts);
+ }
+
+ const long tabcount = vts[0];
+ for (t = 1; t <= tabcount; t++) {
+ tabcol += (colnr_T)vts[t];
+ if (tabcol > col) {
+ return (int)(tabcol - vts[t]);
+ }
+ }
+
+ const int excess = (int)(tabcol % vts[tabcount]);
+ return (int)(excess + ((col - excess) / vts[tabcount]) * vts[tabcount]);
+}
+
+// Find the number of tabs and spaces necessary to get from one column
+// to another.
+void tabstop_fromto(colnr_T start_col,
+ colnr_T end_col,
+ long ts_arg,
+ long *vts,
+ int *ntabs,
+ int *nspcs)
+{
+ int spaces = end_col - start_col;
+ colnr_T tabcol = 0;
+ long padding = 0;
+ int t;
+ long ts = ts_arg == 0 ? curbuf->b_p_ts : ts_arg;
+
+ if (vts == NULL || vts[0] == 0) {
+ int tabs = 0;
+
+ const int initspc = (int)(ts - (start_col % ts));
+ if (spaces >= initspc) {
+ spaces -= initspc;
+ tabs++;
+ }
+ tabs += (int)(spaces / ts);
+ spaces -= (int)((spaces / ts) * ts);
+
+ *ntabs = tabs;
+ *nspcs = spaces;
+ return;
+ }
+
+ // Find the padding needed to reach the next tabstop.
+ const long tabcount = vts[0];
+ for (t = 1; t <= tabcount; t++) {
+ tabcol += (colnr_T)vts[t];
+ if (tabcol > start_col) {
+ padding = tabcol - start_col;
+ break;
+ }
+ }
+ if (t > tabcount) {
+ padding = vts[tabcount] - ((start_col - tabcol) % vts[tabcount]);
+ }
+
+ // If the space needed is less than the padding no tabs can be used.
+ if (spaces < padding) {
+ *ntabs = 0;
+ *nspcs = spaces;
+ return;
+ }
+
+ *ntabs = 1;
+ spaces -= (int)padding;
+
+ // At least one tab has been used. See if any more will fit.
+ while (spaces != 0 && ++t <= tabcount) {
+ padding = vts[t];
+ if (spaces < padding) {
+ *nspcs = spaces;
+ return;
+ }
+ *ntabs += 1;
+ spaces -= (int)padding;
+ }
+
+ *ntabs += spaces / (int)vts[tabcount];
+ *nspcs = spaces % (int)vts[tabcount];
+}
+
+// See if two tabstop arrays contain the same values.
+bool tabstop_eq(long *ts1, long *ts2)
+{
+ int t;
+
+ if ((ts1 == 0 && ts2) || (ts1 && ts2 == 0)) {
+ return false;
+ }
+ if (ts1 == ts2) {
+ return true;
+ }
+ if (ts1[0] != ts2[0]) {
+ return false;
+ }
+
+ for (t = 1; t <= ts1[0]; t++) {
+ if (ts1[t] != ts2[t]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// Copy a tabstop array, allocating space for the new array.
+int *tabstop_copy(long *oldts)
+{
+ long *newts;
+ int t;
+
+ if (oldts == 0) {
+ return 0;
+ }
+
+ newts = xmalloc((unsigned)(oldts[0] + 1) * sizeof(long));
+ for (t = 0; t <= oldts[0]; t++) {
+ newts[t] = oldts[t];
+ }
+
+ return (int *)newts;
+}
+
+// Return a count of the number of tabstops.
+int tabstop_count(long *ts)
+{
+ return ts != NULL ? (int)ts[0] : 0;
+}
+
+// Return the first tabstop, or 8 if there are no tabstops defined.
+int tabstop_first(long *ts)
+{
+ return ts != NULL ? (int)ts[1] : 8;
+}
+
/// Return the effective shiftwidth value for current buffer, using the
/// 'tabstop' value when 'shiftwidth' is zero.
int get_sw_value(buf_T *buf)
{
- long result = buf->b_p_sw ? buf->b_p_sw : buf->b_p_ts;
+ long result = get_sw_value_col(buf, 0);
assert(result >= 0 && result <= INT_MAX);
return (int)result;
}
+// Idem, using the first non-black in the current line.
+long get_sw_value_indent(buf_T *buf)
+{
+ pos_T pos = curwin->w_cursor;
+
+ pos.col = (colnr_T)getwhitecols_curline();
+ return get_sw_value_pos(buf, &pos);
+}
+
+// Idem, using "pos".
+long get_sw_value_pos(buf_T *buf, pos_T *pos)
+{
+ pos_T save_cursor = curwin->w_cursor;
+ long sw_value;
+
+ curwin->w_cursor = *pos;
+ sw_value = get_sw_value_col(buf, get_nolist_virtcol());
+ curwin->w_cursor = save_cursor;
+ return sw_value;
+}
+
+// Idem, using virtual column "col".
+long get_sw_value_col(buf_T *buf, colnr_T col)
+{
+ return buf->b_p_sw ? buf->b_p_sw
+ : tabstop_at(col, buf->b_p_ts, buf->b_p_vts_array);
+}
+
/// Return the effective softtabstop value for the current buffer,
-/// using the effective shiftwidth value when 'softtabstop' is negative.
+/// using the shiftwidth value when 'softtabstop' is negative.
int get_sts_value(void)
{
long result = curbuf->b_p_sts < 0 ? get_sw_value(curbuf) : curbuf->b_p_sts;
@@ -7049,7 +7509,7 @@ void set_fileformat(int eol_style, int opt_flags)
// p is NULL if "eol_style" is EOL_UNKNOWN.
if (p != NULL) {
- set_string_option_direct((char_u *)"ff",
+ set_string_option_direct("ff",
-1,
(char_u *)p,
OPT_FREE | opt_flags,
@@ -7123,9 +7583,19 @@ int csh_like_shell(void)
/// buffer signs and on user configuration.
int win_signcol_count(win_T *wp)
{
+ return win_signcol_configured(wp, NULL);
+}
+
+/// 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) {
+ *is_fixed = 1;
+ }
+
// Note: It checks "no" or "number" in 'signcolumn' option
if (*scl == 'n'
&& (*(scl + 1) == 'o' || (*(scl + 1) == 'u'
@@ -7143,7 +7613,11 @@ int win_signcol_count(win_T *wp)
return 1;
}
- // auto or auto:<NUM>
+ if (is_fixed) {
+ // auto or auto:<NUM>
+ *is_fixed = 0;
+ }
+
if (!strncmp(scl, "auto:", 5)) {
// Variable depending on a configuration
maximum = scl[5] - '0';
@@ -7154,7 +7628,9 @@ int win_signcol_count(win_T *wp)
}
}
- return MAX(minimum, MIN(maximum, needed_signcols));
+ int ret = MAX(minimum, MIN(maximum, needed_signcols));
+ assert(ret <= SIGN_SHOW_MAX);
+ return ret;
}
/// Get window or buffer local options
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index 683afc670e..16749ba86b 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -165,8 +165,8 @@ enum {
SHM_WRI = 'w', ///< "[w]" instead of "written".
SHM_ABBREVIATIONS = 'a', ///< Use abbreviations from #SHM_ALL_ABBREVIATIONS.
SHM_WRITE = 'W', ///< Don't use "written" at all.
- SHM_TRUNC = 't', ///< Trunctate file messages.
- SHM_TRUNCALL = 'T', ///< Trunctate all messages.
+ SHM_TRUNC = 't', ///< Truncate file messages.
+ SHM_TRUNCALL = 'T', ///< Truncate all messages.
SHM_OVER = 'o', ///< Overwrite file messages.
SHM_OVERALL = 'O', ///< Overwrite more messages.
SHM_SEARCH = 's', ///< No search hit bottom messages.
@@ -621,6 +621,19 @@ EXTERN int p_sta; // 'smarttab'
EXTERN int p_sb; // 'splitbelow'
EXTERN long p_tpm; // 'tabpagemax'
EXTERN char_u *p_tal; // 'tabline'
+EXTERN char_u *p_tpf; // 'termpastefilter'
+EXTERN unsigned int tpf_flags; ///< flags from 'termpastefilter'
+#ifdef IN_OPTION_C
+static char *(p_tpf_values[]) =
+ { "BS", "HT", "FF", "ESC", "DEL", "C0", "C1", NULL };
+#endif
+# define TPF_BS 0x001
+# define TPF_HT 0x002
+# define TPF_FF 0x004
+# define TPF_ESC 0x008
+# define TPF_DEL 0x010
+# define TPF_C0 0x020
+# define TPF_C1 0x040
EXTERN char_u *p_sps; // 'spellsuggest'
EXTERN int p_spr; // 'splitright'
EXTERN int p_sol; // 'startofline'
@@ -811,6 +824,8 @@ enum {
, BV_UDF
, BV_UL
, BV_WM
+ , BV_VSTS
+ , BV_VTS
, BV_COUNT // must be the last one
};
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index df2bfbce34..d12b31bcaf 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -900,6 +900,7 @@ return {
normal_fname_chars=true,
vi_def=true,
alloced=true,
+ expand=true,
varname='p_ft',
defaults={if_true={vi=""}}
},
@@ -2821,6 +2822,14 @@ return {
defaults={if_true={vi=false}}
},
{
+ full_name='termpastefilter', abbreviation='tpf',
+ type='string', list='onecomma', scope={'global'},
+ deny_duplicates=true,
+ vim=true,
+ varname='p_tpf',
+ defaults={if_true={vi="", vim="BS,HT,ESC,DEL"}}
+ },
+ {
full_name='terse',
short_desc=N_("hides notification of search wrap"),
type='bool', scope={'global'},
@@ -2990,6 +2999,23 @@ return {
defaults={if_true={vi=4000}}
},
{
+ full_name='varsofttabstop', abbreviation='vsts',
+ short_desc=N_("list of numbers of spaces that <Tab> uses while editing"),
+ type='string', list='comma', scope={'buffer'},
+ vi_def=true,
+ varname='p_vsts',
+ defaults={if_true={vi=""}}
+ },
+ {
+ full_name='vartabstop', abbreviation='vts',
+ short_desc=N_("list of numbers of spaces that <Tab> in file uses"),
+ type='string', list='comma', scope={'buffer'},
+ vi_def=true,
+ varname='p_vts',
+ redraw={'current_buffer'},
+ defaults={if_true={vi=""}}
+ },
+ {
full_name='verbose', abbreviation='vbs',
short_desc=N_("give informative messages"),
type='number', scope={'global'},
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index 9d6518841a..eca245650a 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -159,16 +159,28 @@ bool os_char_avail(void)
return inbuf_poll(0, NULL) == kInputAvail;
}
-// Check for CTRL-C typed by reading all available characters.
+/// Poll for fast events. `got_int` will be set to `true` if CTRL-C was typed.
+///
+/// This invokes a full libuv loop iteration which can be quite costly.
+/// Prefer `line_breakcheck()` if called in a busy inner loop.
+///
+/// Caller must at least check `got_int` before calling this function again.
+/// checking for other low-level input state like `input_available()` might
+/// also be relevant (i e to throttle idle processing when user input is
+/// available)
void os_breakcheck(void)
{
+ if (got_int) {
+ return;
+ }
+
int save_us = updating_screen;
// We do not want screen_resize() to redraw here.
+ // TODO(bfredl): we are already special casing redraw events, is this
+ // hack still needed?
updating_screen++;
- if (!got_int) {
- loop_poll_events(&main_loop, 0);
- }
+ loop_poll_events(&main_loop, 0);
updating_screen = save_us;
}
diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c
index 4d7d9a45df..36d6dbe2db 100644
--- a/src/nvim/os/pty_process_unix.c
+++ b/src/nvim/os/pty_process_unix.c
@@ -20,6 +20,10 @@
# include <pty.h>
#endif
+#ifdef __APPLE__
+# include <crt_externs.h>
+#endif
+
#include <uv.h>
#include "nvim/lib/klist.h"
@@ -154,28 +158,14 @@ void pty_process_teardown(Loop *loop)
static void init_child(PtyProcess *ptyproc)
FUNC_ATTR_NONNULL_ALL
{
+#if defined(HAVE__NSGETENVIRON)
+#define environ (*_NSGetEnviron())
+#else
+ extern char **environ;
+#endif
// New session/process-group. #6530
setsid();
- os_unsetenv("COLUMNS");
- os_unsetenv("LINES");
- os_unsetenv("TERMCAP");
- os_unsetenv("COLORFGBG");
- // setting COLORTERM to "truecolor" if termguicolors is set and 256
- // otherwise, but only if it was set in the parent terminal at all
- if (os_env_exists("COLORTERM")) {
- const char *colorterm = os_getenv("COLORTERM");
- if (colorterm != NULL) {
- if (p_tgc) {
- os_setenv("COLORTERM", "truecolor", 1);
- } else {
- os_setenv("COLORTERM", "256", 1);
- }
- } else {
- os_unsetenv("COLORTERM");
- }
- }
-
signal(SIGCHLD, SIG_DFL);
signal(SIGHUP, SIG_DFL);
signal(SIGINT, SIG_DFL);
@@ -185,14 +175,17 @@ static void init_child(PtyProcess *ptyproc)
Process *proc = (Process *)ptyproc;
if (proc->cwd && os_chdir(proc->cwd) != 0) {
- ELOG("chdir failed: %s", strerror(errno));
+ ELOG("chdir(%s) failed: %s", proc->cwd, strerror(errno));
return;
}
char *prog = ptyproc->process.argv[0];
- os_setenv("TERM", ptyproc->term_name ? ptyproc->term_name : "ansi", 1);
- execvp(prog, ptyproc->process.argv);
- ELOG("execvp failed: %s: %s", strerror(errno), prog);
+
+ assert(proc->env);
+ environ = tv_dict_to_env(proc->env);
+ execvp(prog, proc->argv);
+ ELOG("execvp(%s) failed: %s", prog, strerror(errno));
+
_exit(122); // 122 is EXEC_FAILED in the Vim source.
}
diff --git a/src/nvim/os/pty_process_unix.h b/src/nvim/os/pty_process_unix.h
index f7c57b3839..8c822eafad 100644
--- a/src/nvim/os/pty_process_unix.h
+++ b/src/nvim/os/pty_process_unix.h
@@ -17,7 +17,6 @@ static inline PtyProcess pty_process_init(Loop *loop, void *data)
{
PtyProcess rv;
rv.process = process_init(loop, kProcessTypePty, data);
- rv.term_name = NULL;
rv.width = 80;
rv.height = 24;
rv.tty_fd = -1;
diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c
index 6f7100e846..2bf73d08e6 100644
--- a/src/nvim/os/pty_process_win.c
+++ b/src/nvim/os/pty_process_win.c
@@ -52,6 +52,7 @@ int pty_process_spawn(PtyProcess *ptyproc)
uv_connect_t *out_req = NULL;
wchar_t *cmd_line = NULL;
wchar_t *cwd = NULL;
+ wchar_t *env = NULL;
const char *emsg = NULL;
assert(proc->err.closed);
@@ -124,13 +125,22 @@ int pty_process_spawn(PtyProcess *ptyproc)
goto cleanup;
}
+ if (proc->env != NULL) {
+ status = build_env_block(proc->env, &env);
+ }
+
+ if (status != 0) {
+ emsg = "build_env_block failed";
+ goto cleanup;
+ }
+
if (ptyproc->type == kConpty) {
if (!os_conpty_spawn(conpty_object,
&process_handle,
NULL,
cmd_line,
cwd,
- NULL)) {
+ env)) {
emsg = "os_conpty_spawn failed";
status = (int)GetLastError();
goto cleanup;
@@ -141,7 +151,7 @@ int pty_process_spawn(PtyProcess *ptyproc)
NULL, // Optional application name
cmd_line,
cwd,
- NULL, // Optional environment variables
+ env,
&err);
if (spawncfg == NULL) {
emsg = "winpty_spawn_config_new failed";
@@ -193,11 +203,13 @@ int pty_process_spawn(PtyProcess *ptyproc)
cleanup:
if (status) {
// In the case of an error of MultiByteToWideChar or CreateProcessW.
- ELOG("pty_process_spawn: %s: error code: %d", emsg, status);
+ ELOG("pty_process_spawn(%s): %s: error code: %d",
+ proc->argv[0], emsg, status);
status = os_translate_sys_error(status);
} else if (err != NULL) {
status = (int)winpty_error_code(err);
- ELOG("pty_process_spawn: %s: error code: %d", emsg, status);
+ ELOG("pty_process_spawn(%s): %s: error code: %d",
+ proc->argv[0], emsg, status);
status = translate_winpty_error(status);
}
winpty_error_free(err);
@@ -213,6 +225,7 @@ cleanup:
xfree(in_req);
xfree(out_req);
xfree(cmd_line);
+ xfree(env);
xfree(cwd);
return status;
}
@@ -332,19 +345,17 @@ static int build_cmd_line(char **argv, wchar_t **cmd_line, bool is_cmdexe)
utf8_cmd_line_len += argc;
char *utf8_cmd_line = xmalloc(utf8_cmd_line_len);
*utf8_cmd_line = NUL;
- while (1) {
- QUEUE *head = QUEUE_HEAD(&args_q);
- QUEUE_REMOVE(head);
- ArgNode *arg_node = QUEUE_DATA(head, ArgNode, node);
+ QUEUE *q;
+ QUEUE_FOREACH(q, &args_q, {
+ ArgNode *arg_node = QUEUE_DATA(q, ArgNode, node);
xstrlcat(utf8_cmd_line, arg_node->arg, utf8_cmd_line_len);
xfree(arg_node->arg);
xfree(arg_node);
- if (QUEUE_EMPTY(&args_q)) {
- break;
- } else {
+ QUEUE_REMOVE(q);
+ if (!QUEUE_EMPTY(&args_q)) {
xstrlcat(utf8_cmd_line, " ", utf8_cmd_line_len);
}
- }
+ })
int result = utf8_to_utf16(utf8_cmd_line, -1, cmd_line);
xfree(utf8_cmd_line);
@@ -454,3 +465,66 @@ int translate_winpty_error(int winpty_errno)
default: return UV_UNKNOWN;
}
}
+
+typedef struct EnvNode {
+ wchar_t *str;
+ size_t len;
+ QUEUE node;
+} EnvNode;
+
+/// Build the environment block to pass to CreateProcessW.
+///
+/// @param[in] denv Dict of environment name/value pairs
+/// @param[out] env Allocated environment block
+///
+/// @returns zero on success or error code of MultiByteToWideChar function.
+static int build_env_block(dict_T *denv, wchar_t **env_block)
+{
+ const size_t denv_size = (size_t)tv_dict_len(denv);
+ size_t env_block_len = 0;
+ int rc;
+ char **env = tv_dict_to_env(denv);
+
+ QUEUE *q;
+ QUEUE env_q;
+ QUEUE_INIT(&env_q);
+ // Convert env vars to wchar_t and calculate how big the final env block
+ // needs to be
+ for (size_t i = 0; i < denv_size; i++) {
+ EnvNode *env_node = xmalloc(sizeof(*env_node));
+ rc = utf8_to_utf16(env[i], -1, &env_node->str);
+ if (rc != 0) {
+ goto cleanup;
+ }
+ env_node->len = wcslen(env_node->str) + 1;
+ env_block_len += env_node->len;
+ QUEUE_INSERT_TAIL(&env_q, &env_node->node);
+ }
+
+ // Additional '\0' after the final entry
+ env_block_len++;
+
+ *env_block = xmalloc(sizeof(**env_block) * env_block_len);
+ wchar_t *pos = *env_block;
+
+ QUEUE_FOREACH(q, &env_q, {
+ EnvNode *env_node = QUEUE_DATA(q, EnvNode, node);
+ memcpy(pos, env_node->str, env_node->len * sizeof(*pos));
+ pos += env_node->len;
+ })
+
+ *pos = L'\0';
+
+cleanup:
+ q = QUEUE_HEAD(&env_q);
+ while (q != &env_q) {
+ QUEUE *next = q->next;
+ EnvNode *env_node = QUEUE_DATA(q, EnvNode, node);
+ XFREE_CLEAR(env_node->str);
+ QUEUE_REMOVE(q);
+ xfree(env_node);
+ q = next;
+ }
+
+ return rc;
+}
diff --git a/src/nvim/os/pty_process_win.h b/src/nvim/os/pty_process_win.h
index 8ad5ba7286..f8ec79a3d6 100644
--- a/src/nvim/os/pty_process_win.h
+++ b/src/nvim/os/pty_process_win.h
@@ -37,7 +37,6 @@ static inline PtyProcess pty_process_init(Loop *loop, void *data)
{
PtyProcess rv;
rv.process = process_init(loop, kProcessTypePty, data);
- rv.term_name = NULL;
rv.width = 80;
rv.height = 24;
rv.object.winpty = NULL;
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c
index b5d890bf52..2974245857 100644
--- a/src/nvim/os/shell.c
+++ b/src/nvim/os/shell.c
@@ -123,7 +123,7 @@ int os_expand_wildcards(int num_pat, char_u **pat, int *num_file,
int shell_style = STYLE_ECHO;
int check_spaces;
static bool did_find_nul = false;
- bool ampersent = false;
+ bool ampersand = false;
// vimglob() function to define for Posix shell
static char *sh_vimglob_func =
"vimglob() { while [ $# -ge 1 ]; do echo \"$1\"; shift; done }; vimglob >";
@@ -245,7 +245,7 @@ int os_expand_wildcards(int num_pat, char_u **pat, int *num_file,
p--;
}
if (*p == '&') { // remove trailing '&'
- ampersent = true;
+ ampersand = true;
*p = ' ';
}
STRCAT(command, ">");
@@ -309,7 +309,7 @@ int os_expand_wildcards(int num_pat, char_u **pat, int *num_file,
shellopts |= kShellOptHideMess;
}
- if (ampersent) {
+ if (ampersand) {
STRCAT(command, "&"); // put the '&' after the redirection
}
@@ -331,7 +331,7 @@ int os_expand_wildcards(int num_pat, char_u **pat, int *num_file,
// When running in the background, give it some time to create the temp
// file, but don't wait for it to finish.
- if (ampersent) {
+ if (ampersand) {
os_delay(10L, true);
}
diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c
index 4b6533cd0c..9ea74716aa 100644
--- a/src/nvim/os/time.c
+++ b/src/nvim/os/time.c
@@ -62,6 +62,7 @@ uint64_t os_now(void)
/// @param ignoreinput If true, only SIGINT (CTRL-C) can interrupt.
void os_delay(uint64_t ms, bool ignoreinput)
{
+ DLOG("%" PRIu64 " ms", ms);
if (ignoreinput) {
if (ms > INT_MAX) {
ms = INT_MAX;
@@ -97,7 +98,7 @@ void os_microdelay(uint64_t us, bool ignoreinput)
const int rv = uv_cond_timedwait(&delay_cond, &delay_mutex, ns_delta);
if (0 != rv && UV_ETIMEDOUT != rv) {
- assert(false);
+ abort();
break;
} // Else: Timeout proceeded normally.
@@ -196,6 +197,22 @@ char *os_ctime(char *result, size_t result_len)
return os_ctime_r(&rawtime, result, result_len);
}
+/// Portable version of POSIX strptime()
+///
+/// @param str[in] string to convert
+/// @param format[in] format to parse "str"
+/// @param tm[out] time representation of "str"
+/// @return Pointer to first unprocessed character or NULL
+char *os_strptime(const char *str, const char *format, struct tm *tm)
+ FUNC_ATTR_NONNULL_ALL
+{
+#ifdef HAVE_STRPTIME
+ return strptime(str, format, tm);
+#else
+ return NULL;
+#endif
+}
+
/// Obtains the current Unix timestamp.
///
/// @return Seconds since epoch.
diff --git a/src/nvim/path.c b/src/nvim/path.c
index 2de7e00ddb..3e1713fbdd 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -342,7 +342,7 @@ int path_fnamencmp(const char *const fname1, const char *const fname2,
p1 += utfc_ptr2len((const char_u *)p1);
p2 += utfc_ptr2len((const char_u *)p2);
}
- return c1 - c2;
+ return p_fic ? CH_FOLD(c1) - CH_FOLD(c2) : c1 - c2;
#else
if (p_fic) {
return mb_strnicmp((const char_u *)fname1, (const char_u *)fname2, len);
diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c
index aef7ffa397..32c9750628 100644
--- a/src/nvim/popupmnu.c
+++ b/src/nvim/popupmnu.c
@@ -139,8 +139,10 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
cursor_col = curwin->w_wcol;
}
- pum_anchor_grid = (int)curwin->w_grid.handle;
- if (!ui_has(kUIMultigrid)) {
+ pum_anchor_grid = (int)curwin->w_grid.target->handle;
+ pum_win_row += curwin->w_grid.row_offset;
+ cursor_col += curwin->w_grid.col_offset;
+ if (!ui_has(kUIMultigrid) && curwin->w_grid.target != &default_grid) {
pum_anchor_grid = (int)default_grid.handle;
pum_win_row += curwin->w_winrow;
cursor_col += curwin->w_wincol;
@@ -396,7 +398,7 @@ void pum_redraw(void)
char_u *p = NULL;
int totwidth, width, w;
int thumb_pos = 0;
- int thumb_heigth = 1;
+ int thumb_height = 1;
int round;
int n;
@@ -447,11 +449,11 @@ void pum_redraw(void)
}
if (pum_scrollbar) {
- thumb_heigth = pum_height * pum_height / pum_size;
- if (thumb_heigth == 0) {
- thumb_heigth = 1;
+ thumb_height = pum_height * pum_height / pum_size;
+ if (thumb_height == 0) {
+ thumb_height = 1;
}
- thumb_pos = (pum_first * (pum_height - thumb_heigth)
+ thumb_pos = (pum_first * (pum_height - thumb_height)
+ (pum_size - pum_height) / 2)
/ (pum_size - pum_height);
}
@@ -614,11 +616,11 @@ void pum_redraw(void)
if (pum_scrollbar > 0) {
if (pum_rl) {
grid_putchar(&pum_grid, ' ', row, col_off - pum_width,
- i >= thumb_pos && i < thumb_pos + thumb_heigth
+ i >= thumb_pos && i < thumb_pos + thumb_height
? attr_thumb : attr_scroll);
} else {
grid_putchar(&pum_grid, ' ', row, col_off + pum_width,
- i >= thumb_pos && i < thumb_pos + thumb_heigth
+ i >= thumb_pos && i < thumb_pos + thumb_height
? attr_thumb : attr_scroll);
}
}
diff --git a/src/nvim/pos.h b/src/nvim/pos.h
index 8e86ea08c5..b7c4b6ef92 100644
--- a/src/nvim/pos.h
+++ b/src/nvim/pos.h
@@ -1,6 +1,9 @@
#ifndef NVIM_POS_H
#define NVIM_POS_H
+// for INT_MAX, LONG_MAX et al.
+#include <limits.h>
+
typedef long linenr_T; // line number type
/// Format used to print values which have linenr_T type
#define PRIdLINENR "ld"
@@ -12,8 +15,8 @@ typedef int colnr_T;
/// Maximal (invalid) line number
enum { MAXLNUM = 0x7fffffff };
-/// Maximal column number, 31 bits
-enum { MAXCOL = 0x7fffffff };
+/// Maximal column number
+enum { MAXCOL = INT_MAX };
// Minimum line number
enum { MINLNUM = 1 };
// minimum column number
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index a625c09f78..464d72eccb 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -2798,7 +2798,7 @@ static void qf_jump_goto_line(linenr_T qf_lnum, int qf_col, char_u qf_viscol,
// Move the cursor to the first line in the buffer
pos_T save_cursor = curwin->w_cursor;
curwin->w_cursor.lnum = 0;
- if (!do_search(NULL, '/', qf_pattern, (long)1, SEARCH_KEEP, NULL)) {
+ if (!do_search(NULL, '/', '/', qf_pattern, (long)1, SEARCH_KEEP, NULL)) {
curwin->w_cursor = save_cursor;
}
}
@@ -3617,6 +3617,15 @@ static int qf_open_new_cwindow(qf_info_T *qi, int height)
if (win_split(height, flags) == FAIL) {
return FAIL; // not enough room for window
}
+
+ // User autocommands may have invalidated the previous window after calling
+ // 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);
+ return FAIL;
+ }
+
RESET_BINDING(curwin);
if (IS_LL_STACK(qi)) {
@@ -3665,7 +3674,7 @@ static int qf_open_new_cwindow(qf_info_T *qi, int height)
static void qf_set_title_var(qf_list_T *qfl)
{
if (qfl->qf_title != NULL) {
- set_internal_string_var((char_u *)"w:quickfix_title", qfl->qf_title);
+ set_internal_string_var("w:quickfix_title", qfl->qf_title);
}
}
@@ -4951,7 +4960,7 @@ void ex_cfile(exarg_T *eap)
}
}
if (*eap->arg != NUL) {
- set_string_option_direct((char_u *)"ef", -1, eap->arg, OPT_FREE, 0);
+ set_string_option_direct("ef", -1, eap->arg, OPT_FREE, 0);
}
char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc;
@@ -5264,7 +5273,7 @@ void ex_vimgrep(exarg_T *eap)
qf_new_list(qi, title);
}
- // parse the list of arguments
+ // Parse the list of arguments, wildcards have already been expanded.
if (get_arglist_exp(p, &fcount, &fnames, true) == FAIL) {
goto theend;
}
@@ -5648,7 +5657,7 @@ static int get_qfline_items(qfline_T *qfp, list_T *list)
== FAIL)) {
// tv_dict_add* fail only if key already exist, but this is a newly
// allocated dictionary which is thus guaranteed to have no existing keys.
- assert(false);
+ abort();
}
return OK;
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index a2589ac431..184f5da97d 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -5895,7 +5895,7 @@ static void regdump(char_u *pattern, bt_regprog_T *r)
fprintf(f, " count %" PRId64, (int64_t)OPERAND_MIN(s));
s += 4;
} else if (op == RE_LNUM || op == RE_COL || op == RE_VCOL) {
- /* one int plus comperator */
+ // one int plus comparator
fprintf(f, " count %" PRId64, (int64_t)OPERAND_MIN(s));
s += 5;
}
@@ -6665,6 +6665,10 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest,
int len = 0; /* init for GCC */
static char_u *eval_result = NULL;
+ // We need to keep track of how many backslashes we escape, so that the byte
+ // counts for `extmark_splice` are correct.
+ int num_escaped = 0;
+
// Be paranoid...
if ((source == NULL && expr == NULL) || dest == NULL) {
EMSG(_(e_null));
@@ -6840,6 +6844,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest,
// later. Used to insert a literal CR.
default:
if (backslash) {
+ num_escaped += 1;
if (copy) {
*dst = '\\';
}
@@ -6979,7 +6984,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest,
*dst = NUL;
exit:
- return (int)((dst - dest) + 1);
+ return (int)((dst - dest) + 1 - num_escaped);
}
@@ -7139,6 +7144,7 @@ list_T *reg_submatch_list(int no)
tv_list_append_string(list, s, (const char *)rsm.sm_match->endp[no] - s);
}
+ tv_list_ref(list);
return list;
}
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
index 8b5ee59d40..b6bcee3fda 100644
--- a/src/nvim/regexp_nfa.c
+++ b/src/nvim/regexp_nfa.c
@@ -559,7 +559,9 @@ static char_u *nfa_get_match_text(nfa_state_T *start)
*/
static void realloc_post_list(void)
{
- size_t new_max = (post_end - post_start) + 1000;
+ // For weird patterns the number of states can be very high. Increasing by
+ // 50% seems a reasonable compromise between memory use and speed.
+ const size_t new_max = (post_end - post_start) * 3 / 2;
int *new_start = xrealloc(post_start, new_max * sizeof(int));
post_ptr = new_start + (post_ptr - post_start);
post_end = new_start + new_max;
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index a78f905a70..6be3b6fb60 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -143,7 +143,7 @@ long tab_page_click_defs_size = 0;
// for line_putchar. Contains the state that needs to be remembered from
// putting one character to the next.
typedef struct {
- const char_u *p;
+ const char *p;
int prev_c; // previous Arabic character
int prev_c1; // first composing char for prev_c
} LineState;
@@ -232,7 +232,7 @@ void screen_invalidate_highlights(void)
{
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
redraw_later(wp, NOT_VALID);
- wp->w_grid.valid = false;
+ wp->w_grid_alloc.valid = false;
}
}
@@ -582,11 +582,18 @@ int update_screen(int type)
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_redr_type == CLEAR && wp->w_floating && wp->w_grid.chars) {
- grid_invalidate(&wp->w_grid);
+ if (wp->w_redr_type == CLEAR && wp->w_floating && wp->w_grid_alloc.chars) {
+ grid_invalidate(&wp->w_grid_alloc);
wp->w_redr_type = NOT_VALID;
}
+ // reallocate grid if needed.
+ win_grid_alloc(wp);
+
+ if (wp->w_redr_border || wp->w_redr_type >= NOT_VALID) {
+ win_redr_border(wp);
+ }
+
if (wp->w_redr_type != 0) {
if (!did_one) {
did_one = TRUE;
@@ -774,8 +781,6 @@ static void win_update(win_T *wp, Providers *providers)
type = wp->w_redr_type;
- win_grid_alloc(wp);
-
if (type >= NOT_VALID) {
wp->w_redr_status = true;
wp->w_lines_valid = 0;
@@ -1857,7 +1862,7 @@ static int compute_foldcolumn(win_T *wp, int col)
/// Handles composing chars and arabic shaping state.
static int line_putchar(LineState *s, schar_T *dest, int maxcells, bool rl)
{
- const char_u *p = s->p;
+ const char_u *p = (char_u *)s->p;
int cells = utf_ptr2cells(p);
int c_len = utfc_ptr2len(p);
int u8c, u8cc[MAX_MCO];
@@ -2077,12 +2082,15 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
int change_start = MAXCOL; // first col of changed area
int change_end = -1; // last col of changed area
colnr_T trailcol = MAXCOL; // start of trailing spaces
+ colnr_T leadcol = 0; // start of leading spaces
bool need_showbreak = false; // overlong line, skip first x chars
+ sign_attrs_T sattrs[SIGN_SHOW_MAX]; // attributes for signs
+ int num_signs; // number of signs for line
int line_attr = 0; // attribute for the whole line
int line_attr_lowprio = 0; // low-priority attribute for the line
matchitem_T *cur; // points to the match list
match_T *shl; // points to search_hl or a match
- int shl_flag; // flag to indicate whether search_hl
+ bool shl_flag; // flag to indicate whether search_hl
// has been processed or not
bool prevcol_hl_flag; // flag to indicate whether prevcol
// equals startcol of search_hl or one
@@ -2096,6 +2104,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
char_u buf_fold[FOLD_TEXT_LEN + 1]; // Hold value returned by get_foldtext
+ bool area_active = false;
+
/* draw_state: items that are drawn in sequence: */
#define WL_START 0 /* nothing done yet */
# define WL_CMDLINE WL_START + 1 /* cmdline window column */
@@ -2315,7 +2325,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
getvcol(curwin, &pos, (colnr_T *)&tocol, NULL, NULL);
}
// do at least one character; happens when past end of line
- if (fromcol == tocol) {
+ if (fromcol == tocol && search_match_endcol) {
tocol = fromcol + 1;
}
area_highlighting = true;
@@ -2367,11 +2377,14 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
wp->w_last_cursorline = wp->w_cursor.lnum;
}
+ memset(sattrs, 0, sizeof(sattrs));
+ num_signs = buf_get_signattrs(wp->w_buffer, lnum, sattrs);
+
// If this line has a sign with line highlighting set line_attr.
// TODO(bfredl, vigoux): this should not take priority over decoration!
- v = buf_getsigntype(wp->w_buffer, lnum, SIGN_LINEHL, 0, 1);
- if (v != 0) {
- line_attr = sign_get_attr((int)v, SIGN_LINEHL);
+ sign_attrs_T * sattr = sign_get_attr(SIGN_LINEHL, sattrs, 0, 1);
+ if (sattr != NULL) {
+ line_attr = sattr->sat_linehl;
}
// Highlight the current line in the quickfix window.
@@ -2420,6 +2433,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
if (wp->w_p_list && !has_fold) {
if (wp->w_p_lcs_chars.space
|| wp->w_p_lcs_chars.trail
+ || wp->w_p_lcs_chars.lead
|| wp->w_p_lcs_chars.nbsp) {
extra_check = true;
}
@@ -2431,6 +2445,20 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
}
trailcol += (colnr_T) (ptr - line);
}
+ // find end of leading whitespace
+ if (wp->w_p_lcs_chars.lead) {
+ leadcol = 0;
+ while (ascii_iswhite(ptr[leadcol])) {
+ leadcol++;
+ }
+ if (ptr[leadcol] == NUL) {
+ // in a line full of spaces all of them are treated as trailing
+ leadcol = (colnr_T)0;
+ } else {
+ // keep track of the first column not filled with spaces
+ leadcol += (colnr_T)(ptr - line) + 1;
+ }
+ }
}
/*
@@ -2656,7 +2684,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
// already be in use.
xfree(p_extra_free);
p_extra_free = xmalloc(MAX_MCO * fdc + 1);
- n_extra = fill_foldcolumn(p_extra_free, wp, foldinfo, lnum);
+ n_extra = (int)fill_foldcolumn(p_extra_free, wp, foldinfo, lnum);
p_extra_free[n_extra] = NUL;
p_extra = p_extra_free;
c_extra = NUL;
@@ -2673,7 +2701,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
int count = win_signcol_count(wp);
if (count > 0) {
get_sign_display_info(
- false, wp, lnum, row,
+ false, wp, sattrs, row,
startrow, filler_lines, filler_todo, count,
&c_extra, &c_final, extra, sizeof(extra),
&p_extra, &n_extra,
@@ -2692,10 +2720,10 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
// in 'lnum', then display the sign instead of the line
// number.
if (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u'
- && buf_findsign_id(wp->w_buffer, lnum, (char_u *)"*") != 0) {
+ && num_signs > 0) {
int count = win_signcol_count(wp);
get_sign_display_info(
- true, wp, lnum, row,
+ true, wp, sattrs, row,
startrow, filler_lines, filler_todo, count,
&c_extra, &c_final, extra, sizeof(extra),
&p_extra, &n_extra,
@@ -2745,13 +2773,13 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
n_extra = number_width(wp) + 1;
char_attr = win_hl_attr(wp, HLF_N);
- int num_sign = buf_getsigntype(
- wp->w_buffer, lnum, SIGN_NUMHL, 0, 1);
- if (num_sign != 0) {
+ sign_attrs_T *num_sattr = sign_get_attr(SIGN_NUMHL, sattrs, 0, 1);
+ if (num_sattr != NULL) {
// :sign defined with "numhl" highlight.
- char_attr = sign_get_attr(num_sign, SIGN_NUMHL);
+ char_attr = num_sattr->sat_numhl;
} else if ((wp->w_p_cul || wp->w_p_rnu)
- && lnum == wp->w_cursor.lnum) {
+ && lnum == wp->w_cursor.lnum
+ && filler_todo == 0) {
// When 'cursorline' is set highlight the line number of
// the current line differently.
// TODO(vim): Can we use CursorLine instead of CursorLineNr
@@ -2850,6 +2878,12 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
if (draw_state == WL_LINE - 1 && n_extra == 0) {
sign_idx = 0;
draw_state = WL_LINE;
+
+ if (has_decor && row == startrow + filler_lines) {
+ // hide virt_text on text hidden by 'nowrap'
+ decor_redraw_col(wp->w_buffer, vcol, off, true, &decor_state);
+ }
+
if (saved_n_extra) {
/* Continue item from end of wrapped line. */
n_extra = saved_n_extra;
@@ -2870,6 +2904,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
&& vcol >= (long)wp->w_virtcol)
|| (number_only && draw_state > WL_NR))
&& filler_todo <= 0) {
+ draw_virt_text(buf, &col, grid->Columns);
grid_put_linebuf(grid, row, 0, col, -grid->Columns, wp->w_p_rl, wp,
wp->w_hl_attr_normal, false);
// Pretend we have finished updating the window. Except when
@@ -2933,10 +2968,14 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
&& vcol_prev < vcol // not at margin
&& vcol < tocol)) {
area_attr = attr; // start highlighting
+ if (area_highlighting) {
+ area_active = true;
+ }
} else if (area_attr != 0 && (vcol == tocol
|| (noinvcur
&& (colnr_T)vcol == wp->w_virtcol))) {
area_attr = 0; // stop highlighting
+ area_active = false;
}
if (!n_extra) {
@@ -2950,16 +2989,15 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
*/
v = (long)(ptr - line);
cur = wp->w_match_head;
- shl_flag = FALSE;
- while (cur != NULL || shl_flag == FALSE) {
- if (shl_flag == FALSE
- && ((cur != NULL
- && cur->priority > SEARCH_HL_PRIORITY)
- || cur == NULL)) {
+ shl_flag = false;
+ while (cur != NULL || !shl_flag) {
+ if (!shl_flag
+ && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) {
shl = &search_hl;
- shl_flag = TRUE;
- } else
+ shl_flag = true;
+ } else {
shl = &cur->hl;
+ }
if (cur != NULL) {
cur->pos.cur = 0;
}
@@ -2984,7 +3022,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
has_match_conc = v == (long)shl->startcol ? 2 : 1;
match_conc = cur->conceal_char;
} else {
- has_match_conc = match_conc = 0;
+ has_match_conc = 0;
}
} else if (v == (long)shl->endcol) {
shl->attr_cur = 0;
@@ -3026,16 +3064,15 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
search_attr_from_match = false;
search_attr = search_hl.attr_cur;
cur = wp->w_match_head;
- shl_flag = FALSE;
- while (cur != NULL || shl_flag == FALSE) {
- if (shl_flag == FALSE
- && ((cur != NULL
- && cur->priority > SEARCH_HL_PRIORITY)
- || cur == NULL)) {
+ shl_flag = false;
+ while (cur != NULL || !shl_flag) {
+ if (!shl_flag
+ && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) {
shl = &search_hl;
- shl_flag = TRUE;
- } else
+ shl_flag = true;
+ } else {
shl = &cur->hl;
+ }
if (shl->attr_cur != 0) {
search_attr = shl->attr_cur;
search_attr_from_match = shl != &search_hl;
@@ -3048,6 +3085,12 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
&& (wp->w_p_list && lcs_eol_one == -1)) {
search_attr = 0;
}
+
+ // Do not allow a conceal over EOL otherwise EOL will be missed
+ // and bad things happen.
+ if (*ptr == NUL) {
+ has_match_conc = 0;
+ }
}
if (diff_hlf != (hlf_T)0) {
@@ -3115,6 +3158,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
mb_utf8 = false;
}
} else {
+ assert(p_extra != NULL);
c = *p_extra;
mb_c = c;
// If the UTF-8 character is more than one byte:
@@ -3392,9 +3436,15 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
char_attr = hl_combine_attr(spell_attr, char_attr);
}
+ if (wp->w_buffer->terminal) {
+ char_attr = hl_combine_attr(term_attrs[vcol], char_attr);
+ }
+
if (has_decor && v > 0) {
- int extmark_attr = decor_redraw_col(wp->w_buffer, (colnr_T)v-1,
- &decor_state);
+ bool selected = (area_active || (area_highlighting && noinvcur
+ && (colnr_T)vcol == wp->w_virtcol));
+ int extmark_attr = decor_redraw_col(wp->w_buffer, (colnr_T)v-1, off,
+ selected, &decor_state);
if (extmark_attr != 0) {
if (!attr_pri) {
char_attr = hl_combine_attr(char_attr, extmark_attr);
@@ -3404,10 +3454,6 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
}
}
- if (wp->w_buffer->terminal) {
- char_attr = hl_combine_attr(term_attrs[vcol], char_attr);
- }
-
// Found last space before word: check for line break.
if (wp->w_p_lbr && c0 == c && vim_isbreak(c)
&& !vim_isbreak((int)(*ptr))) {
@@ -3416,8 +3462,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
// TODO: is passing p for start of the line OK?
n_extra = win_lbr_chartabsize(wp, line, p, (colnr_T)vcol, NULL) - 1;
if (c == TAB && n_extra + col > grid->Columns) {
- n_extra = (int)wp->w_buffer->b_p_ts
- - vcol % (int)wp->w_buffer->b_p_ts - 1;
+ n_extra = tabstop_padding(vcol, wp->w_buffer->b_p_ts,
+ wp->w_buffer->b_p_vts_array) - 1;
}
c_extra = mb_off > 0 ? MB_FILLER_CHAR : ' ';
c_final = NUL;
@@ -3437,6 +3483,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
|| (mb_utf8 && (mb_c == 160 || mb_c == 0x202f)))
&& curwin->w_p_lcs_chars.nbsp)
|| (c == ' ' && curwin->w_p_lcs_chars.space
+ && ptr - line >= leadcol
&& ptr - line <= trailcol))) {
c = (c == ' ') ? wp->w_p_lcs_chars.space : wp->w_p_lcs_chars.nbsp;
n_attr = 1;
@@ -3452,8 +3499,10 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
}
}
- if (trailcol != MAXCOL && ptr > line + trailcol && c == ' ') {
- c = wp->w_p_lcs_chars.trail;
+ if ((trailcol != MAXCOL && ptr > line + trailcol && c == ' ')
+ || (leadcol != 0 && ptr < line + leadcol && c == ' ')) {
+ c = (ptr > line + trailcol) ? wp->w_p_lcs_chars.trail
+ : wp->w_p_lcs_chars.lead;
n_attr = 1;
extra_attr = win_hl_attr(wp, HLF_0);
saved_attr2 = char_attr; // save current attr
@@ -3483,8 +3532,9 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
vcol_adjusted = vcol - MB_CHARLEN(p_sbr);
}
// tab amount depends on current column
- tab_len = (int)wp->w_buffer->b_p_ts
- - vcol_adjusted % (int)wp->w_buffer->b_p_ts - 1;
+ tab_len = tabstop_padding(vcol_adjusted,
+ wp->w_buffer->b_p_ts,
+ wp->w_buffer->b_p_vts_array) - 1;
if (!wp->w_p_lbr || !wp->w_p_list) {
n_extra = tab_len;
@@ -3517,6 +3567,10 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
xfree(p_extra_free);
p_extra_free = p;
for (i = 0; i < tab_len; i++) {
+ if (*p == NUL) {
+ tab_len = i;
+ break;
+ }
int lcs = wp->w_p_lcs_chars.tab2;
// if tab3 is given, need to change the char
@@ -3672,12 +3726,13 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
&& vim_strchr(wp->w_p_cocu, 'v') == NULL)) {
char_attr = conceal_attr;
if ((prev_syntax_id != syntax_seqnr || has_match_conc > 1)
- && (syn_get_sub_char() != NUL || match_conc
+ && (syn_get_sub_char() != NUL
+ || (has_match_conc && match_conc)
|| wp->w_p_cole == 1)
&& wp->w_p_cole != 3) {
// First time at this concealed item: display one
// character.
- if (match_conc) {
+ if (has_match_conc && match_conc) {
c = match_conc;
} else if (syn_get_sub_char() != NUL) {
c = syn_get_sub_char();
@@ -3837,16 +3892,15 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
// 'search_hl' and the match list.
char_attr = search_hl.attr;
cur = wp->w_match_head;
- shl_flag = FALSE;
- while (cur != NULL || shl_flag == FALSE) {
- if (shl_flag == FALSE
- && ((cur != NULL
- && cur->priority > SEARCH_HL_PRIORITY)
- || cur == NULL)) {
+ shl_flag = false;
+ while (cur != NULL || !shl_flag) {
+ if (!shl_flag
+ && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) {
shl = &search_hl;
- shl_flag = TRUE;
- } else
+ shl_flag = true;
+ } else {
shl = &cur->hl;
+ }
if ((ptr - line) - 1 == (long)shl->startcol
&& (shl == &search_hl || !shl->is_addpos)) {
char_attr = shl->attr;
@@ -3897,9 +3951,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
.hl_id = hl_err }));
do_virttext = true;
} else if (has_decor) {
- VirtText *vp = decor_redraw_virt_text(wp->w_buffer, &decor_state);
- if (vp) {
- virt_text = *vp;
+ virt_text = decor_redraw_eol(wp->w_buffer, &decor_state, &line_attr);
+ if (kv_size(virt_text)) {
do_virttext = true;
}
}
@@ -3915,7 +3968,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
int i;
size_t virt_pos = 0;
- LineState s = LINE_STATE((char_u *)"");
+ LineState s = LINE_STATE("");
int virt_attr = 0;
// Make sure alignment is the same regardless
@@ -3959,7 +4012,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
if (do_virttext && !delay_virttext) {
if (*s.p == NUL) {
if (virt_pos < virt_text.size) {
- s.p = (char_u *)kv_A(virt_text, virt_pos).text;
+ s.p = kv_A(virt_text, virt_pos).text;
int hl_id = kv_A(virt_text, virt_pos).hl_id;
virt_attr = hl_id > 0 ? syn_id2attr(hl_id) : 0;
virt_pos++;
@@ -4025,6 +4078,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
col += n;
}
}
+
+ draw_virt_text(buf, &col, grid->Columns);
grid_put_linebuf(grid, row, 0, col, grid->Columns, wp->w_p_rl, wp,
wp->w_hl_attr_normal, false);
row++;
@@ -4243,7 +4298,10 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
&& (grid->Columns == Columns // Window spans the width of the screen,
|| ui_has(kUIMultigrid)) // or has dedicated grid.
&& !wp->w_p_rl; // Not right-to-left.
- grid_put_linebuf(grid, row, 0, col - boguscols, grid->Columns, wp->w_p_rl,
+
+ int draw_col = col - boguscols;
+ draw_virt_text(buf, &draw_col, grid->Columns);
+ grid_put_linebuf(grid, row, 0, draw_col, grid->Columns, wp->w_p_rl,
wp, wp->w_hl_attr_normal, wrap);
if (wrap) {
ScreenGrid *current_grid = grid;
@@ -4319,6 +4377,56 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
return row;
}
+void draw_virt_text(buf_T *buf, int *end_col, int max_col)
+{
+ DecorState *state = &decor_state;
+ for (size_t i = 0; i < kv_size(state->active); i++) {
+ DecorRange *item = &kv_A(state->active, i);
+ if (item->start_row == state->row && kv_size(item->decor.virt_text)
+ && item->decor.virt_text_pos == kVTOverlay
+ && item->virt_col >= 0) {
+ VirtText vt = item->decor.virt_text;
+ HlMode hl_mode = item->decor.hl_mode;
+ LineState s = LINE_STATE("");
+ int virt_attr = 0;
+ int col = item->virt_col;
+ size_t virt_pos = 0;
+ item->virt_col = -2; // deactivate
+
+ while (col < max_col) {
+ if (!*s.p) {
+ if (virt_pos == kv_size(vt)) {
+ break;
+ }
+ s.p = kv_A(vt, virt_pos).text;
+ int hl_id = kv_A(vt, virt_pos).hl_id;
+ virt_attr = hl_id > 0 ? syn_id2attr(hl_id) : 0;
+ virt_pos++;
+ continue;
+ }
+ int attr;
+ bool through = false;
+ if (hl_mode == kHlModeCombine) {
+ attr = hl_combine_attr(linebuf_attr[col], virt_attr);
+ } else if (hl_mode == kHlModeBlend) {
+ through = (*s.p == ' ');
+ attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through);
+ } else {
+ attr = virt_attr;
+ }
+ schar_T dummy[2];
+ int cells = line_putchar(&s, through ? dummy : &linebuf_char[col],
+ max_col-col, false);
+ linebuf_attr[col++] = attr;
+ if (cells > 1) {
+ linebuf_attr[col++] = attr;
+ }
+ }
+ *end_col = MAX(*end_col, col);
+ }
+ }
+}
+
/// Determine if dedicated window grid should be used or the default_grid
///
/// If UI did not request multigrid support, draw all windows on the
@@ -4331,14 +4439,10 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
/// screen positions.
void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off)
{
- if (!(*grid)->chars && *grid != &default_grid) {
- *row_off += (*grid)->row_offset;
- *col_off += (*grid)->col_offset;
- if (*grid == &msg_grid_adj && msg_grid.chars) {
- *grid = &msg_grid;
- } else {
- *grid = &default_grid;
- }
+ if ((*grid)->target) {
+ *row_off += (*grid)->row_offset;
+ *col_off += (*grid)->col_offset;
+ *grid = (*grid)->target;
}
}
@@ -4352,7 +4456,7 @@ void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off)
static void get_sign_display_info(
bool nrcol,
win_T *wp,
- linenr_T lnum,
+ sign_attrs_T sattrs[],
int row,
int startrow,
int filler_lines,
@@ -4369,8 +4473,6 @@ static void get_sign_display_info(
int *sign_idxp
)
{
- int text_sign;
-
// Draw cells with the sign value or blank.
*c_extrap = ' ';
*c_finalp = NUL;
@@ -4382,10 +4484,9 @@ static void get_sign_display_info(
}
if (row == startrow + filler_lines && filler_todo <= 0) {
- text_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_TEXT,
- *sign_idxp, count);
- if (text_sign != 0) {
- *pp_extra = sign_get_text(text_sign);
+ sign_attrs_T *sattr = sign_get_attr(SIGN_TEXT, sattrs, *sign_idxp, count);
+ if (sattr != NULL) {
+ *pp_extra = sattr->sat_text;
if (*pp_extra != NULL) {
*c_extrap = NUL;
*c_finalp = NUL;
@@ -4418,7 +4519,7 @@ static void get_sign_display_info(
(*pp_extra)[*n_extrap] = NUL;
}
}
- *char_attrp = sign_get_attr(text_sign, SIGN_TEXT);
+ *char_attrp = sattr->sat_texthl;
}
}
@@ -5092,9 +5193,9 @@ static void redraw_custom_statusline(win_T *wp)
// When there is an error disable the statusline, otherwise the
// display is messed up with errors and a redraw triggers the problem
// again and again.
- set_string_option_direct((char_u *)"statusline", -1,
- (char_u *)"", OPT_FREE | (*wp->w_p_stl != NUL
- ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR);
+ set_string_option_direct("statusline", -1, (char_u *)"",
+ OPT_FREE | (*wp->w_p_stl != NUL
+ ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR);
}
did_emsg |= saved_did_emsg;
entered = false;
@@ -5175,7 +5276,7 @@ get_keymap_str (
static void
win_redr_custom (
win_T *wp,
- int draw_ruler /* TRUE or FALSE */
+ bool draw_ruler
)
{
static int entered = FALSE;
@@ -5342,6 +5443,64 @@ theend:
entered = FALSE;
}
+static void win_redr_border(win_T *wp)
+{
+ wp->w_redr_border = false;
+ if (!(wp->w_floating && wp->w_float_config.border)) {
+ return;
+ }
+
+ ScreenGrid *grid = &wp->w_grid_alloc;
+
+ schar_T *chars = wp->w_float_config.border_chars;
+ int *attrs = wp->w_float_config.border_attr;
+
+
+ int *adj = wp->w_border_adj;
+ int irow = wp->w_height_inner, icol = wp->w_width_inner;
+
+ if (adj[0]) {
+ grid_puts_line_start(grid, 0);
+ if (adj[3]) {
+ grid_put_schar(grid, 0, 0, chars[0], attrs[0]);
+ }
+ for (int i = 0; i < icol; i++) {
+ grid_put_schar(grid, 0, i+adj[3], chars[1], attrs[1]);
+ }
+ if (adj[1]) {
+ grid_put_schar(grid, 0, icol+adj[3], chars[2], attrs[2]);
+ }
+ grid_puts_line_flush(false);
+ }
+
+ for (int i = 0; i < irow; i++) {
+ if (adj[3]) {
+ grid_puts_line_start(grid, i+adj[0]);
+ grid_put_schar(grid, i+adj[0], 0, chars[7], attrs[7]);
+ grid_puts_line_flush(false);
+ }
+ if (adj[1]) {
+ int ic = (i == 0 && !adj[0] && chars[2][0]) ? 2 : 3;
+ grid_puts_line_start(grid, i+adj[0]);
+ grid_put_schar(grid, i+adj[0], icol+adj[3], chars[ic], attrs[ic]);
+ grid_puts_line_flush(false);
+ }
+ }
+
+ if (adj[2]) {
+ grid_puts_line_start(grid, irow+adj[0]);
+ if (adj[3]) {
+ grid_put_schar(grid, irow+adj[0], 0, chars[6], attrs[6]);
+ }
+ for (int i = 0; i < icol; i++) {
+ int ic = (i == 0 && !adj[3] && chars[6][0]) ? 6 : 5;
+ grid_put_schar(grid, irow+adj[0], i+adj[3], chars[ic], attrs[ic]);
+ }
+ grid_put_schar(grid, irow+adj[0], icol+adj[3], chars[4], attrs[4]);
+ grid_puts_line_flush(false);
+ }
+}
+
// Low-level functions to manipulate invidual character cells on the
// screen grid.
@@ -5479,6 +5638,20 @@ void grid_puts_line_start(ScreenGrid *grid, int row)
put_dirty_grid = grid;
}
+void grid_put_schar(ScreenGrid *grid, int row, int col, char_u *schar, int attr)
+{
+ assert(put_dirty_row == row);
+ unsigned int off = grid->line_offset[row] + col;
+ if (grid->attrs[off] != attr || schar_cmp(grid->chars[off], schar)) {
+ schar_copy(grid->chars[off], schar);
+ grid->attrs[off] = attr;
+
+ put_dirty_first = MIN(put_dirty_first, col);
+ // TODO(bfredl): Y U NO DOUBLEWIDTH?
+ put_dirty_last = MAX(put_dirty_last, col+1);
+ }
+}
+
/// like grid_puts(), but output "text[len]". When "len" is -1 output up to
/// a NUL.
void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row,
@@ -6054,7 +6227,7 @@ void check_for_delay(int check_msg_scroll)
&& !did_wait_return
&& emsg_silent == 0) {
ui_flush();
- os_delay(1000L, true);
+ os_delay(1006L, true);
emsg_on_display = false;
if (check_msg_scroll) {
msg_scroll = false;
@@ -6070,12 +6243,15 @@ void check_for_delay(int check_msg_scroll)
void win_grid_alloc(win_T *wp)
{
ScreenGrid *grid = &wp->w_grid;
+ ScreenGrid *grid_allocated = &wp->w_grid_alloc;
int rows = wp->w_height_inner;
int cols = wp->w_width_inner;
+ int total_rows = wp->w_height_outer;
+ int total_cols = wp->w_width_outer;
bool want_allocation = ui_has(kUIMultigrid) || wp->w_floating;
- bool has_allocation = (grid->chars != NULL);
+ bool has_allocation = (grid_allocated->chars != NULL);
if (grid->Rows != rows) {
wp->w_lines_valid = 0;
@@ -6084,35 +6260,47 @@ void win_grid_alloc(win_T *wp)
}
int was_resized = false;
- if ((has_allocation != want_allocation)
- || grid->Rows != rows
- || grid->Columns != cols) {
- if (want_allocation) {
- grid_alloc(grid, rows, cols, wp->w_grid.valid, false);
- grid->valid = true;
- } else {
- // Single grid mode, all rendering will be redirected to default_grid.
- // Only keep track of the size and offset of the window.
- grid_free(grid);
- grid->Rows = rows;
- grid->Columns = cols;
- grid->valid = false;
+ if (want_allocation && (!has_allocation
+ || grid_allocated->Rows != total_rows
+ || grid_allocated->Columns != total_cols)) {
+ grid_alloc(grid_allocated, total_rows, total_cols,
+ wp->w_grid_alloc.valid, false);
+ grid_allocated->valid = true;
+ if (wp->w_floating && wp->w_float_config.border) {
+ wp->w_redr_border = true;
}
was_resized = true;
- } else if (want_allocation && has_allocation && !wp->w_grid.valid) {
- grid_invalidate(grid);
- grid->valid = true;
+ } else if (!want_allocation && has_allocation) {
+ // Single grid mode, all rendering will be redirected to default_grid.
+ // Only keep track of the size and offset of the window.
+ grid_free(grid_allocated);
+ grid_allocated->valid = false;
+ was_resized = true;
+ } else if (want_allocation && has_allocation && !wp->w_grid_alloc.valid) {
+ grid_invalidate(grid_allocated);
+ grid_allocated->valid = true;
}
- grid->row_offset = wp->w_winrow;
- grid->col_offset = wp->w_wincol;
+ grid->Rows = rows;
+ grid->Columns = cols;
+
+ if (want_allocation) {
+ grid->target = grid_allocated;
+ grid->row_offset = wp->w_border_adj[0];
+ grid->col_offset = wp->w_border_adj[3];
+ } else {
+ grid->target = &default_grid;
+ grid->row_offset = wp->w_winrow;
+ grid->col_offset = wp->w_wincol;
+ }
// send grid resize event if:
// - a grid was just resized
// - screen_resize was called and all grid sizes must be sent
// - the UI wants multigrid event (necessary)
if ((send_grid_resize || was_resized) && want_allocation) {
- ui_call_grid_resize(grid->handle, grid->Columns, grid->Rows);
+ ui_call_grid_resize(grid_allocated->handle,
+ grid_allocated->Columns, grid_allocated->Rows);
}
}
@@ -6201,6 +6389,9 @@ retry:
tab_page_click_defs = new_tab_page_click_defs;
tab_page_click_defs_size = Columns;
+ default_grid.comp_height = Rows;
+ default_grid.comp_width = Columns;
+
default_grid.row_offset = 0;
default_grid.col_offset = 0;
default_grid.handle = DEFAULT_GRID_HANDLE;
@@ -6852,7 +7043,7 @@ void draw_tabline(void)
did_emsg = false;
win_redr_custom(NULL, false);
if (did_emsg) {
- set_string_option_direct((char_u *)"tabline", -1,
+ set_string_option_direct("tabline", -1,
(char_u *)"", OPT_FREE, SID_ERROR);
}
did_emsg |= saved_did_emsg;
@@ -7105,20 +7296,23 @@ static void win_redr_ruler(win_T *wp, int always)
if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count)
return;
- /* Don't draw the ruler while doing insert-completion, it might overwrite
- * the (long) mode message. */
- if (wp == lastwin && lastwin->w_status_height == 0)
- if (edit_submode != NULL)
+ // Don't draw the ruler while doing insert-completion, it might overwrite
+ // the (long) mode message.
+ if (wp == lastwin && lastwin->w_status_height == 0) {
+ if (edit_submode != NULL) {
return;
+ }
+ }
if (*p_ruf) {
int save_called_emsg = called_emsg;
- called_emsg = FALSE;
- win_redr_custom(wp, TRUE);
- if (called_emsg)
- set_string_option_direct((char_u *)"rulerformat", -1,
- (char_u *)"", OPT_FREE, SID_ERROR);
+ called_emsg = false;
+ win_redr_custom(wp, true);
+ if (called_emsg) {
+ set_string_option_direct("rulerformat", -1, (char_u *)"",
+ OPT_FREE, SID_ERROR);
+ }
called_emsg |= save_called_emsg;
return;
}
@@ -7334,6 +7528,10 @@ void screen_resize(int width, int height)
Rows = height;
Columns = width;
check_shellsize();
+ int max_p_ch = Rows - min_rows() + 1;
+ if (!ui_has(kUIMessages) && p_ch > max_p_ch) {
+ p_ch = max_p_ch ? max_p_ch : 1;
+ }
height = Rows;
width = Columns;
p_lines = Rows;
@@ -7436,8 +7634,9 @@ void win_new_shellsize(void)
static long old_Columns = 0;
if (old_Rows != Rows) {
- // if 'window' uses the whole screen, keep it using that */
- if (p_window == old_Rows - 1 || old_Rows == 0) {
+ // If 'window' uses the whole screen, keep it using that.
+ // Don't change it when set with "-w size" on the command line.
+ if (p_window == old_Rows - 1 || (old_Rows == 0 && p_window == 0)) {
p_window = Rows - 1;
}
old_Rows = Rows;
@@ -7452,7 +7651,7 @@ void win_new_shellsize(void)
win_T *get_win_by_grid_handle(handle_T handle)
{
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_grid.handle == handle) {
+ if (wp->w_grid_alloc.handle == handle) {
return wp;
}
}
diff --git a/src/nvim/search.c b/src/nvim/search.c
index 2802da6f7f..abe05bbd12 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -1021,8 +1021,9 @@ static int first_submatch(regmmatch_T *rp)
* Return 0 for failure, 1 for found, 2 for found and line offset added.
*/
int do_search(
- oparg_T *oap, /* can be NULL */
- int dirc, /* '/' or '?' */
+ oparg_T *oap, // can be NULL
+ int dirc, // '/' or '?'
+ int search_delim, // delimiter for search, e.g. '%' in s%regex%replacement
char_u *pat,
long count,
int options,
@@ -1101,8 +1102,8 @@ int do_search(
searchstr = pat;
dircp = NULL;
- /* use previous pattern */
- if (pat == NULL || *pat == NUL || *pat == dirc) {
+ // use previous pattern
+ if (pat == NULL || *pat == NUL || *pat == search_delim) {
if (spats[RE_SEARCH].pat == NULL) { // no previous pattern
searchstr = spats[RE_SUBST].pat;
if (searchstr == NULL) {
@@ -1122,15 +1123,15 @@ int do_search(
* If there is a matching '/' or '?', toss it.
*/
ps = strcopy;
- p = skip_regexp(pat, dirc, p_magic, &strcopy);
+ p = skip_regexp(pat, search_delim, p_magic, &strcopy);
if (strcopy != ps) {
/* made a copy of "pat" to change "\?" to "?" */
searchcmdlen += (int)(STRLEN(pat) - STRLEN(strcopy));
pat = strcopy;
searchstr = strcopy;
}
- if (*p == dirc) {
- dircp = p; /* remember where we put the NUL */
+ if (*p == search_delim) {
+ dircp = p; // remember where we put the NUL
*p++ = NUL;
}
spats[0].off.line = FALSE;
@@ -1320,7 +1321,7 @@ int do_search(
RE_LAST, sia);
if (dircp != NULL) {
- *dircp = dirc; // restore second '/' or '?' for normal_cmd()
+ *dircp = search_delim; // restore second '/' or '?' for normal_cmd()
}
if (!shortmess(SHM_SEARCH)
@@ -1400,6 +1401,7 @@ int do_search(
}
dirc = *++pat;
+ search_delim = dirc;
if (dirc != '?' && dirc != '/') {
retval = 0;
EMSG(_("E386: Expected '?' or '/' after ';'"));
@@ -2326,6 +2328,9 @@ showmatch(
return;
}
}
+ if (*p == NUL) {
+ return;
+ }
if ((lpos = findmatch(NULL, NUL)) == NULL) { // no match, so beep
vim_beep(BO_MATCH);
@@ -2368,10 +2373,11 @@ showmatch(
* brief pause, unless 'm' is present in 'cpo' and a character is
* available.
*/
- if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL)
- os_delay(p_mat * 100L, true);
- else if (!char_avail())
- os_delay(p_mat * 100L, false);
+ if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL) {
+ os_delay(p_mat * 100L + 8, true);
+ } else if (!char_avail()) {
+ os_delay(p_mat * 100L + 9, false);
+ }
curwin->w_cursor = save_cursor; // restore cursor position
*so = save_so;
*siso = save_siso;
@@ -2685,8 +2691,9 @@ fwd_word(
while (--count >= 0) {
/* When inside a range of folded lines, move to the last char of the
* last line. */
- if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum))
- coladvance((colnr_T)MAXCOL);
+ if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) {
+ coladvance(MAXCOL);
+ }
sclass = cls();
/*
@@ -2803,8 +2810,9 @@ int end_word(long count, int bigword, int stop, int empty)
while (--count >= 0) {
/* When inside a range of folded lines, move to the last char of the
* last line. */
- if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum))
- coladvance((colnr_T)MAXCOL);
+ if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) {
+ coladvance(MAXCOL);
+ }
sclass = cls();
if (inc_cursor() == -1)
return FAIL;
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index 2444910bb3..c0e787380f 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -765,7 +765,7 @@ static ShaDaReadResult sd_reader_skip(ShaDaReadDef *const sd_reader,
(uint64_t) offset);
return kSDReadStatusNotShaDa;
}
- assert(false);
+ abort();
}
return kSDReadStatusSuccess;
}
@@ -1224,7 +1224,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
}
case kSDReadStatusFinished: {
// Should be handled by the while condition.
- assert(false);
+ abort();
}
case kSDReadStatusNotShaDa:
case kSDReadStatusReadError: {
@@ -1236,7 +1236,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
}
switch (cur_entry.type) {
case kSDItemMissing: {
- assert(false);
+ abort();
}
case kSDItemUnknown: {
break;
@@ -1628,7 +1628,7 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer,
((size_t) (!CHECK_DEFAULT(entry, attr)))
switch (entry.type) {
case kSDItemMissing: {
- assert(false);
+ abort();
}
case kSDItemUnknown: {
if (spacker->callback(spacker->data, entry.data.unknown_item.contents,
@@ -1850,7 +1850,7 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer,
break;
}
default: {
- assert(false);
+ abort();
}
}
}
@@ -2147,7 +2147,7 @@ static inline ShaDaWriteResult shada_read_when_writing(
}
case kSDReadStatusFinished: {
// Should be handled by the while condition.
- assert(false);
+ abort();
}
case kSDReadStatusNotShaDa: {
ret = kSDWriteReadNotShada;
@@ -2184,7 +2184,7 @@ static inline ShaDaWriteResult shada_read_when_writing(
}
case kSDItemHeader:
case kSDItemBufferList: {
- assert(false);
+ abort();
}
case kSDItemUnknown: {
ret = shada_pack_entry(packer, entry, 0);
@@ -4044,7 +4044,7 @@ shada_read_next_item_start:
}
case kSDItemMissing:
case kSDItemUnknown: {
- assert(false);
+ abort();
}
}
entry->type = (ShadaEntryType) type_u64;
diff --git a/src/nvim/sign.c b/src/nvim/sign.c
index fc9f53c192..5c7b497a19 100644
--- a/src/nvim/sign.c
+++ b/src/nvim/sign.c
@@ -18,6 +18,7 @@
#include "nvim/move.h"
#include "nvim/screen.h"
#include "nvim/syntax.h"
+#include "nvim/option.h"
/// Struct to hold the sign properties.
typedef struct sign sign_T;
@@ -83,13 +84,13 @@ static signgroup_T * sign_group_ref(const char_u *groupname)
group = xmalloc((unsigned)(sizeof(signgroup_T) + STRLEN(groupname)));
STRCPY(group->sg_name, groupname);
- group->refcount = 1;
- group->next_sign_id = 1;
+ group->sg_refcount = 1;
+ group->sg_next_sign_id = 1;
hash_add_item(&sg_table, hi, group->sg_name, hash);
} else {
// existing group
group = HI2SG(hi);
- group->refcount++;
+ group->sg_refcount++;
}
return group;
@@ -105,8 +106,8 @@ static void sign_group_unref(char_u *groupname)
hi = hash_find(&sg_table, groupname);
if (!HASHITEM_EMPTY(hi)) {
group = HI2SG(hi);
- group->refcount--;
- if (group->refcount == 0) {
+ group->sg_refcount--;
+ if (group->sg_refcount == 0) {
// All the signs in this group are removed
hash_remove(&sg_table, hi);
xfree(group);
@@ -117,12 +118,12 @@ static void sign_group_unref(char_u *groupname)
/// Returns TRUE if 'sign' is in 'group'.
/// A sign can either be in the global group (sign->group == NULL)
/// or in a named group. If 'group' is '*', then the sign is part of the group.
-int sign_in_group(signlist_T *sign, const char_u *group)
+int sign_in_group(sign_entry_T *sign, const char_u *group)
{
return ((group != NULL && STRCMP(group, "*") == 0)
- || (group == NULL && sign->group == NULL)
- || (group != NULL && sign->group != NULL
- && STRCMP(group, sign->group->sg_name) == 0));
+ || (group == NULL && sign->se_group == NULL)
+ || (group != NULL && sign->se_group != NULL
+ && STRCMP(group, sign->se_group->sg_name) == 0));
}
/// Get the next free sign identifier in the specified group
@@ -130,7 +131,7 @@ int sign_group_get_next_signid(buf_T *buf, const char_u *groupname)
{
int id = 1;
signgroup_T *group = NULL;
- signlist_T *sign;
+ sign_entry_T *sign;
hashitem_T *hi;
int found = false;
@@ -147,13 +148,13 @@ int sign_group_get_next_signid(buf_T *buf, const char_u *groupname)
if (group == NULL) {
id = next_sign_id++; // global group
} else {
- id = group->next_sign_id++;
+ id = group->sg_next_sign_id++;
}
// Check whether this sign is already placed in the buffer
found = true;
FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (id == sign->id && sign_in_group(sign, groupname)) {
+ if (id == sign->se_id && sign_in_group(sign, groupname)) {
found = false; // sign identifier is in use
break;
}
@@ -167,8 +168,8 @@ int sign_group_get_next_signid(buf_T *buf, const char_u *groupname)
/// 'next' signs.
static void insert_sign(
buf_T *buf, // buffer to store sign in
- signlist_T *prev, // previous sign entry
- signlist_T *next, // next sign entry
+ sign_entry_T *prev, // previous sign entry
+ sign_entry_T *next, // next sign entry
int id, // sign ID
const char_u *group, // sign group; NULL for global group
int prio, // sign priority
@@ -177,21 +178,21 @@ static void insert_sign(
bool has_text_or_icon // sign has text or icon
)
{
- signlist_T *newsign = xmalloc(sizeof(signlist_T));
- newsign->id = id;
- newsign->lnum = lnum;
- newsign->typenr = typenr;
- newsign->has_text_or_icon = has_text_or_icon;
+ sign_entry_T *newsign = xmalloc(sizeof(sign_entry_T));
+ newsign->se_id = id;
+ newsign->se_lnum = lnum;
+ newsign->se_typenr = typenr;
+ newsign->se_has_text_or_icon = has_text_or_icon;
if (group != NULL) {
- newsign->group = sign_group_ref(group);
+ newsign->se_group = sign_group_ref(group);
} else {
- newsign->group = NULL;
+ newsign->se_group = NULL;
}
- newsign->priority = prio;
- newsign->next = next;
- newsign->prev = prev;
+ newsign->se_priority = prio;
+ newsign->se_next = next;
+ newsign->se_prev = prev;
if (next != NULL) {
- next->prev = newsign;
+ next->se_prev = newsign;
}
buf->b_signcols_max = -1;
@@ -206,14 +207,14 @@ static void insert_sign(
// first sign in signlist
buf->b_signlist = newsign;
} else {
- prev->next = newsign;
+ prev->se_next = newsign;
}
}
/// Insert a new sign sorted by line number and sign priority.
static void insert_sign_by_lnum_prio(
buf_T *buf, // buffer to store sign in
- signlist_T *prev, // previous sign entry
+ sign_entry_T *prev, // previous sign entry
int id, // sign ID
const char_u *group, // sign group; NULL for global group
int prio, // sign priority
@@ -222,19 +223,19 @@ static void insert_sign_by_lnum_prio(
bool has_text_or_icon // sign has text or icon
)
{
- signlist_T *sign;
+ sign_entry_T *sign;
// keep signs sorted by lnum, priority and id: insert new sign at
// the proper position in the list for this lnum.
- while (prev != NULL && prev->lnum == lnum
- && (prev->priority < prio
- || (prev->priority == prio && prev->id <= id))) {
- prev = prev->prev;
+ while (prev != NULL && prev->se_lnum == lnum
+ && (prev->se_priority < prio
+ || (prev->se_priority == prio && prev->se_id <= id))) {
+ prev = prev->se_prev;
}
if (prev == NULL) {
sign = buf->b_signlist;
} else {
- sign = prev->next;
+ sign = prev->se_next;
}
insert_sign(buf, prev, sign, id, group, prio, lnum, typenr, has_text_or_icon);
@@ -254,16 +255,16 @@ char_u * sign_typenr2name(int typenr)
}
/// Return information about a sign in a Dict
-dict_T * sign_get_info(signlist_T *sign)
+dict_T * sign_get_info(sign_entry_T *sign)
{
dict_T *d = tv_dict_alloc();
- tv_dict_add_nr(d, S_LEN("id"), sign->id);
- tv_dict_add_str(d, S_LEN("group"), ((sign->group == NULL)
+ tv_dict_add_nr(d, S_LEN("id"), sign->se_id);
+ tv_dict_add_str(d, S_LEN("group"), ((sign->se_group == NULL)
? (char *)""
- : (char *)sign->group->sg_name));
- tv_dict_add_nr(d, S_LEN("lnum"), sign->lnum);
- tv_dict_add_str(d, S_LEN("name"), (char *)sign_typenr2name(sign->typenr));
- tv_dict_add_nr(d, S_LEN("priority"), sign->priority);
+ : (char *)sign->se_group->sg_name));
+ tv_dict_add_nr(d, S_LEN("lnum"), sign->se_lnum);
+ tv_dict_add_str(d, S_LEN("name"), (char *)sign_typenr2name(sign->se_typenr));
+ tv_dict_add_nr(d, S_LEN("priority"), sign->se_priority);
return d;
}
@@ -271,17 +272,17 @@ dict_T * sign_get_info(signlist_T *sign)
// Sort the signs placed on the same line as "sign" by priority. Invoked after
// changing the priority of an already placed sign. Assumes the signs in the
// buffer are sorted by line number and priority.
-static void sign_sort_by_prio_on_line(buf_T *buf, signlist_T *sign)
+static void sign_sort_by_prio_on_line(buf_T *buf, sign_entry_T *sign)
FUNC_ATTR_NONNULL_ALL
{
// If there is only one sign in the buffer or only one sign on the line or
// the sign is already sorted by priority, then return.
- if ((sign->prev == NULL
- || sign->prev->lnum != sign->lnum
- || sign->prev->priority > sign->priority)
- && (sign->next == NULL
- || sign->next->lnum != sign->lnum
- || sign->next->priority < sign->priority)) {
+ if ((sign->se_prev == NULL
+ || sign->se_prev->se_lnum != sign->se_lnum
+ || sign->se_prev->se_priority > sign->se_priority)
+ && (sign->se_next == NULL
+ || sign->se_next->se_lnum != sign->se_lnum
+ || sign->se_next->se_priority < sign->se_priority)) {
return;
}
@@ -289,55 +290,55 @@ static void sign_sort_by_prio_on_line(buf_T *buf, signlist_T *sign)
// Find a sign after which 'sign' should be inserted
// First search backward for a sign with higher priority on the same line
- signlist_T *p = sign;
- while (p->prev != NULL
- && p->prev->lnum == sign->lnum
- && p->prev->priority <= sign->priority) {
- p = p->prev;
+ sign_entry_T *p = sign;
+ while (p->se_prev != NULL
+ && p->se_prev->se_lnum == sign->se_lnum
+ && p->se_prev->se_priority <= sign->se_priority) {
+ p = p->se_prev;
}
if (p == sign) {
// Sign not found. Search forward for a sign with priority just before
// 'sign'.
- p = sign->next;
- while (p->next != NULL
- && p->next->lnum == sign->lnum
- && p->next->priority > sign->priority) {
- p = p->next;
+ p = sign->se_next;
+ while (p->se_next != NULL
+ && p->se_next->se_lnum == sign->se_lnum
+ && p->se_next->se_priority > sign->se_priority) {
+ p = p->se_next;
}
}
// Remove 'sign' from the list
if (buf->b_signlist == sign) {
- buf->b_signlist = sign->next;
+ buf->b_signlist = sign->se_next;
}
- if (sign->prev != NULL) {
- sign->prev->next = sign->next;
+ if (sign->se_prev != NULL) {
+ sign->se_prev->se_next = sign->se_next;
}
- if (sign->next != NULL) {
- sign->next->prev = sign->prev;
+ if (sign->se_next != NULL) {
+ sign->se_next->se_prev = sign->se_prev;
}
- sign->prev = NULL;
- sign->next = NULL;
+ sign->se_prev = NULL;
+ sign->se_next = NULL;
// Re-insert 'sign' at the right place
- if (p->priority <= sign->priority) {
+ if (p->se_priority <= sign->se_priority) {
// 'sign' has a higher priority and should be inserted before 'p'
- sign->prev = p->prev;
- sign->next = p;
- p->prev = sign;
- if (sign->prev != NULL) {
- sign->prev->next = sign;
+ sign->se_prev = p->se_prev;
+ sign->se_next = p;
+ p->se_prev = sign;
+ if (sign->se_prev != NULL) {
+ sign->se_prev->se_next = sign;
}
if (buf->b_signlist == p) {
buf->b_signlist = sign;
}
} else {
// 'sign' has a lower priority and should be inserted after 'p'
- sign->prev = p;
- sign->next = p->next;
- p->next = sign;
- if (sign->next != NULL) {
- sign->next->prev = sign;
+ sign->se_prev = p;
+ sign->se_next = p->se_next;
+ p->se_next = sign;
+ if (sign->se_next != NULL) {
+ sign->se_next->se_prev = sign;
}
}
}
@@ -354,19 +355,19 @@ void buf_addsign(
bool has_text_or_icon // sign has text or icon
)
{
- signlist_T *sign; // a sign in the signlist
- signlist_T *prev; // the previous sign
+ sign_entry_T *sign; // a sign in the signlist
+ sign_entry_T *prev; // the previous sign
prev = NULL;
FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (lnum == sign->lnum && id == sign->id
+ if (lnum == sign->se_lnum && id == sign->se_id
&& sign_in_group(sign, groupname)) {
// Update an existing sign
- sign->typenr = typenr;
- sign->priority = prio;
+ sign->se_typenr = typenr;
+ sign->se_priority = prio;
sign_sort_by_prio_on_line(buf, sign);
return;
- } else if (lnum < sign->lnum) {
+ } else if (lnum < sign->se_lnum) {
insert_sign_by_lnum_prio(
buf,
prev,
@@ -398,69 +399,119 @@ linenr_T buf_change_sign_type(
buf_T *buf, // buffer to store sign in
int markId, // sign ID
const char_u *group, // sign group
- int typenr // typenr of sign we are adding
+ int typenr, // typenr of sign we are adding
+ int prio // sign priority
)
{
- signlist_T *sign; // a sign in the signlist
+ sign_entry_T *sign; // a sign in the signlist
FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (sign->id == markId && sign_in_group(sign, group)) {
- sign->typenr = typenr;
- return sign->lnum;
+ if (sign->se_id == markId && sign_in_group(sign, group)) {
+ sign->se_typenr = typenr;
+ sign->se_priority = prio;
+ sign_sort_by_prio_on_line(buf, sign);
+ return sign->se_lnum;
}
}
return (linenr_T)0;
}
-/// Gets a sign from a given line.
-///
-/// Return the type number of the sign at line number 'lnum' in buffer 'buf'
-/// which has the attribute specified by 'type'. Returns 0 if a sign is not
-/// found at the line number or it doesn't have the specified attribute.
-/// @param buf Buffer in which to search
-/// @param lnum Line in which to search
+/// Return the sign attrs which has the attribute specified by 'type'. Returns
+/// NULL if a sign is not found with the specified attribute.
/// @param type Type of sign to look for
+/// @param sattrs Sign attrs to search through
/// @param idx if there multiple signs, this index will pick the n-th
-// out of the most `max_signs` sorted ascending by Id.
+/// out of the most `max_signs` sorted ascending by Id.
/// @param max_signs the number of signs, with priority for the ones
-// with the highest Ids.
-/// @return Identifier of the matching sign, or 0
-int buf_getsigntype(buf_T *buf, linenr_T lnum, SignType type,
- int idx, int max_signs)
+/// with the highest Ids.
+/// @return Attrs of the matching sign, or NULL
+sign_attrs_T * sign_get_attr(SignType type, sign_attrs_T sattrs[],
+ int idx, int max_signs)
{
- signlist_T *sign; // a sign in a b_signlist
- signlist_T *matches[9];
+ sign_attrs_T *matches[SIGN_SHOW_MAX];
int nr_matches = 0;
- FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (sign->lnum == lnum
- && (type == SIGN_ANY
- || (type == SIGN_TEXT
- && sign_get_text(sign->typenr) != NULL)
- || (type == SIGN_LINEHL
- && sign_get_attr(sign->typenr, SIGN_LINEHL) != 0)
- || (type == SIGN_NUMHL
- && sign_get_attr(sign->typenr, SIGN_NUMHL) != 0))) {
- matches[nr_matches] = sign;
+ for (int i = 0; i < SIGN_SHOW_MAX; i++) {
+ if ( (type == SIGN_TEXT && sattrs[i].sat_text != NULL)
+ || (type == SIGN_LINEHL && sattrs[i].sat_linehl != 0)
+ || (type == SIGN_NUMHL && sattrs[i].sat_numhl != 0)) {
+ matches[nr_matches] = &sattrs[i];
nr_matches++;
- // signlist is sorted with most important (priority, id), thus we
+ // attr list is sorted with most important (priority, id), thus we
// may stop as soon as we have max_signs matches
- if (nr_matches == ARRAY_SIZE(matches) || nr_matches >= max_signs) {
+ if (nr_matches >= max_signs) {
break;
}
}
}
- if (nr_matches > 0) {
- if (idx >= nr_matches) {
- return 0;
- }
+ if (nr_matches > idx) {
+ return matches[nr_matches - idx - 1];
+ }
+
+ return NULL;
+}
+
+/// Lookup a sign by typenr. Returns NULL if sign is not found.
+static sign_T * find_sign_by_typenr(int typenr)
+{
+ sign_T *sp;
- return matches[nr_matches - idx -1]->typenr;
+ for (sp = first_sign; sp != NULL; sp = sp->sn_next) {
+ if (sp->sn_typenr == typenr) {
+ return sp;
+ }
}
+ return NULL;
+}
- return 0;
+/// Return the attributes of all the signs placed on line 'lnum' in buffer
+/// 'buf'. Used when refreshing the screen. Returns the number of signs.
+/// @param buf Buffer in which to search
+/// @param lnum Line in which to search
+/// @param sattrs Output array for attrs
+/// @return Number of signs of which attrs were found
+int buf_get_signattrs(buf_T *buf, linenr_T lnum, sign_attrs_T sattrs[])
+{
+ sign_entry_T *sign;
+ sign_T *sp;
+
+ int nr_matches = 0;
+
+ FOR_ALL_SIGNS_IN_BUF(buf, sign) {
+ if (sign->se_lnum > lnum) {
+ // Signs are sorted by line number in the buffer. No need to check
+ // for signs after the specified line number 'lnum'.
+ break;
+ }
+
+ if (sign->se_lnum == lnum) {
+ sign_attrs_T sattr;
+ memset(&sattr, 0, sizeof(sattr));
+ sattr.sat_typenr = sign->se_typenr;
+ sp = find_sign_by_typenr(sign->se_typenr);
+ if (sp != NULL) {
+ sattr.sat_text = sp->sn_text;
+ if (sattr.sat_text != NULL && sp->sn_text_hl != 0) {
+ sattr.sat_texthl = syn_id2attr(sp->sn_text_hl);
+ }
+ if (sp->sn_line_hl != 0) {
+ sattr.sat_linehl = syn_id2attr(sp->sn_line_hl);
+ }
+ if (sp->sn_num_hl != 0) {
+ sattr.sat_numhl = syn_id2attr(sp->sn_num_hl);
+ }
+ }
+
+ sattrs[nr_matches] = sattr;
+ nr_matches++;
+ if (nr_matches == SIGN_SHOW_MAX) {
+ break;
+ }
+ }
+ }
+ return nr_matches;
}
/// Delete sign 'id' in group 'group' from buffer 'buf'.
@@ -478,26 +529,26 @@ linenr_T buf_delsign(
char_u *group // sign group
)
{
- signlist_T **lastp; // pointer to pointer to current sign
- signlist_T *sign; // a sign in a b_signlist
- signlist_T *next; // the next sign in a b_signlist
+ sign_entry_T **lastp; // pointer to pointer to current sign
+ sign_entry_T *sign; // a sign in a b_signlist
+ sign_entry_T *next; // the next sign in a b_signlist
linenr_T lnum; // line number whose sign was deleted
buf->b_signcols_max = -1;
lastp = &buf->b_signlist;
lnum = 0;
for (sign = buf->b_signlist; sign != NULL; sign = next) {
- next = sign->next;
- if ((id == 0 || sign->id == id)
- && (atlnum == 0 || sign->lnum == atlnum)
+ next = sign->se_next;
+ if ((id == 0 || sign->se_id == id)
+ && (atlnum == 0 || sign->se_lnum == atlnum)
&& sign_in_group(sign, group)) {
*lastp = next;
if (next != NULL) {
- next->prev = sign->prev;
+ next->se_prev = sign->se_prev;
}
- lnum = sign->lnum;
- if (sign->group != NULL) {
- sign_group_unref(sign->group->sg_name);
+ lnum = sign->se_lnum;
+ if (sign->se_group != NULL) {
+ sign_group_unref(sign->se_group->sg_name);
}
xfree(sign);
redraw_buf_line_later(buf, lnum);
@@ -511,7 +562,7 @@ linenr_T buf_delsign(
break;
}
} else {
- lastp = &sign->next;
+ lastp = &sign->se_next;
}
}
@@ -535,11 +586,11 @@ int buf_findsign(
char_u *group // sign group
)
{
- signlist_T *sign; // a sign in the signlist
+ sign_entry_T *sign; // a sign in the signlist
FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (sign->id == id && sign_in_group(sign, group)) {
- return (int)sign->lnum;
+ if (sign->se_id == id && sign_in_group(sign, group)) {
+ return (int)sign->se_lnum;
}
}
@@ -548,16 +599,22 @@ int buf_findsign(
/// Return the sign at line 'lnum' in buffer 'buf'. Returns NULL if a sign is
/// not found at the line. If 'groupname' is NULL, searches in the global group.
-static signlist_T * buf_getsign_at_line(
+static sign_entry_T * buf_getsign_at_line(
buf_T *buf, // buffer whose sign we are searching for
linenr_T lnum, // line number of sign
char_u *groupname // sign group name
)
{
- signlist_T *sign; // a sign in the signlist
+ sign_entry_T *sign; // a sign in the signlist
FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (sign->lnum == lnum && sign_in_group(sign, groupname)) {
+ if (sign->se_lnum > lnum) {
+ // Signs are sorted by line number in the buffer. No need to check
+ // for signs after the specified line number 'lnum'.
+ break;
+ }
+
+ if (sign->se_lnum == lnum && sign_in_group(sign, groupname)) {
return sign;
}
}
@@ -572,11 +629,11 @@ int buf_findsign_id(
char_u *groupname // sign group name
)
{
- signlist_T *sign; // a sign in the signlist
+ sign_entry_T *sign; // a sign in the signlist
sign = buf_getsign_at_line(buf, lnum, groupname);
if (sign != NULL) {
- return sign->id;
+ return sign->se_id;
}
return 0;
@@ -585,9 +642,9 @@ int buf_findsign_id(
/// Delete signs in buffer "buf".
void buf_delete_signs(buf_T *buf, char_u *group)
{
- signlist_T *sign;
- signlist_T **lastp; // pointer to pointer to current sign
- signlist_T *next;
+ sign_entry_T *sign;
+ sign_entry_T **lastp; // pointer to pointer to current sign
+ sign_entry_T *next;
// When deleting the last sign need to redraw the windows to remove the
// sign column. Not when curwin is NULL (this means we're exiting).
@@ -597,18 +654,18 @@ void buf_delete_signs(buf_T *buf, char_u *group)
lastp = &buf->b_signlist;
for (sign = buf->b_signlist; sign != NULL; sign = next) {
- next = sign->next;
+ next = sign->se_next;
if (sign_in_group(sign, group)) {
*lastp = next;
if (next != NULL) {
- next->prev = sign->prev;
+ next->se_prev = sign->se_prev;
}
- if (sign->group != NULL) {
- sign_group_unref(sign->group->sg_name);
+ if (sign->se_group != NULL) {
+ sign_group_unref(sign->se_group->sg_name);
}
xfree(sign);
} else {
- lastp = &sign->next;
+ lastp = &sign->se_next;
}
}
buf->b_signcols_max = -1;
@@ -618,7 +675,7 @@ void buf_delete_signs(buf_T *buf, char_u *group)
void sign_list_placed(buf_T *rbuf, char_u *sign_group)
{
buf_T *buf;
- signlist_T *sign;
+ sign_entry_T *sign;
char lbuf[MSG_BUF_LEN];
char group[MSG_BUF_LEN];
@@ -642,16 +699,16 @@ void sign_list_placed(buf_T *rbuf, char_u *sign_group)
if (!sign_in_group(sign, sign_group)) {
continue;
}
- if (sign->group != NULL) {
+ if (sign->se_group != NULL) {
vim_snprintf(group, MSG_BUF_LEN, _(" group=%s"),
- sign->group->sg_name);
+ sign->se_group->sg_name);
} else {
group[0] = '\0';
}
vim_snprintf(lbuf, MSG_BUF_LEN,
_(" line=%ld id=%d%s name=%s priority=%d"),
- (long)sign->lnum, sign->id, group,
- sign_typenr2name(sign->typenr), sign->priority);
+ (long)sign->se_lnum, sign->se_id, group,
+ sign_typenr2name(sign->se_typenr), sign->se_priority);
MSG_PUTS(lbuf);
msg_putchar('\n');
}
@@ -670,26 +727,43 @@ void sign_mark_adjust(
long amount_after
)
{
- signlist_T *sign; // a sign in a b_signlist
- linenr_T new_lnum; // new line number to assign to sign
+ sign_entry_T *sign; // a sign in a b_signlist
+ sign_entry_T *next; // the next sign in a b_signlist
+ sign_entry_T *last = NULL; // pointer to pointer to current sign
+ sign_entry_T **lastp = NULL; // pointer to pointer to current sign
+ linenr_T new_lnum; // new line number to assign to sign
+ int is_fixed = 0;
+ int signcol = win_signcol_configured(curwin, &is_fixed);
curbuf->b_signcols_max = -1;
+ lastp = &curbuf->b_signlist;
- FOR_ALL_SIGNS_IN_BUF(curbuf, sign) {
- new_lnum = sign->lnum;
- if (sign->lnum >= line1 && sign->lnum <= line2) {
- if (amount != MAXLNUM) {
+ for (sign = curbuf->b_signlist; sign != NULL; sign = next) {
+ next = sign->se_next;
+ new_lnum = sign->se_lnum;
+ if (sign->se_lnum >= line1 && sign->se_lnum <= line2) {
+ if (amount == MAXLNUM && (!is_fixed || signcol >= 2)) {
+ *lastp = next;
+ if (next) {
+ next->se_prev = last;
+ }
+ xfree(sign);
+ continue;
+ } else {
new_lnum += amount;
}
- } else if (sign->lnum > line2) {
+ } else if (sign->se_lnum > line2) {
new_lnum += amount_after;
}
// If the new sign line number is past the last line in the buffer,
// then don't adjust the line number. Otherwise, it will always be past
// the last line and will not be visible.
- if (sign->lnum >= line1 && new_lnum <= curbuf->b_ml.ml_line_count) {
- sign->lnum = new_lnum;
+ if (sign->se_lnum >= line1 && new_lnum <= curbuf->b_ml.ml_line_count) {
+ sign->se_lnum = new_lnum;
}
+
+ last = sign;
+ lastp = &sign->se_next;
}
}
@@ -973,8 +1047,8 @@ int sign_place(
sp->sn_typenr,
has_text_or_icon);
} else {
- // ":sign place {id} file={fname}": change sign type
- lnum = buf_change_sign_type(buf, *sign_id, sign_group, sp->sn_typenr);
+ // ":sign place {id} file={fname}": change sign type and/or priority
+ lnum = buf_change_sign_type(buf, *sign_id, sign_group, sp->sn_typenr, prio);
}
if (lnum > 0) {
redraw_buf_line_later(buf, lnum);
@@ -1488,7 +1562,7 @@ void sign_getlist(const char_u *name, list_T *retlist)
list_T *get_buffer_signs(buf_T *buf)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- signlist_T *sign;
+ sign_entry_T *sign;
dict_T *d;
list_T *const l = tv_list_alloc(kListLenMayKnow);
@@ -1509,7 +1583,7 @@ static void sign_get_placed_in_buf(
{
dict_T *d;
list_T *l;
- signlist_T *sign;
+ sign_entry_T *sign;
d = tv_dict_alloc();
tv_list_append_dict(retlist, d);
@@ -1524,9 +1598,9 @@ static void sign_get_placed_in_buf(
continue;
}
if ((lnum == 0 && sign_id == 0)
- || (sign_id == 0 && lnum == sign->lnum)
- || (lnum == 0 && sign_id == sign->id)
- || (lnum == sign->lnum && sign_id == sign->id)) {
+ || (sign_id == 0 && lnum == sign->se_lnum)
+ || (lnum == 0 && sign_id == sign->se_id)
+ || (lnum == sign->se_lnum && sign_id == sign->se_id)) {
tv_list_append_dict(l, sign_get_info(sign));
}
}
@@ -1613,50 +1687,6 @@ static void sign_undefine(sign_T *sp, sign_T *sp_prev)
xfree(sp);
}
-/// Gets highlighting attribute for sign "typenr" corresponding to "type".
-int sign_get_attr(int typenr, SignType type)
-{
- sign_T *sp;
- int sign_hl = 0;
-
- for (sp = first_sign; sp != NULL; sp = sp->sn_next) {
- if (sp->sn_typenr == typenr) {
- switch (type) {
- case SIGN_TEXT:
- sign_hl = sp->sn_text_hl;
- break;
- case SIGN_LINEHL:
- sign_hl = sp->sn_line_hl;
- break;
- case SIGN_NUMHL:
- sign_hl = sp->sn_num_hl;
- break;
- default:
- abort();
- }
- if (sign_hl > 0) {
- return syn_id2attr(sign_hl);
- }
- break;
- }
- }
- return 0;
-}
-
-/// Get text mark for sign "typenr".
-/// Returns NULL if there isn't one.
-char_u * sign_get_text(int typenr)
-{
- sign_T *sp;
-
- for (sp = first_sign; sp != NULL; sp = sp->sn_next) {
- if (sp->sn_typenr == typenr) {
- return sp->sn_text;
- }
- }
- return NULL;
-}
-
/// Undefine/free all signs.
void free_signs(void)
{
@@ -1860,3 +1890,267 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg)
}
}
+/// Define a sign using the attributes in 'dict'. Returns 0 on success and -1 on
+/// failure.
+int sign_define_from_dict(const char *name_arg, dict_T *dict)
+{
+ char *name = NULL;
+ char *icon = NULL;
+ char *linehl = NULL;
+ char *text = NULL;
+ char *texthl = NULL;
+ char *numhl = NULL;
+ int retval = -1;
+
+ if (name_arg == NULL) {
+ if (dict == NULL) {
+ return -1;
+ }
+ name = tv_dict_get_string(dict, "name", true);
+ } else {
+ name = xstrdup(name_arg);
+ }
+ if (name == NULL || name[0] == NUL) {
+ goto cleanup;
+ }
+ if (dict != NULL) {
+ icon = tv_dict_get_string(dict, "icon" , true);
+ linehl = tv_dict_get_string(dict, "linehl", true);
+ text = tv_dict_get_string(dict, "text" , true);
+ texthl = tv_dict_get_string(dict, "texthl", true);
+ numhl = tv_dict_get_string(dict, "numhl" , true);
+ }
+
+ if (sign_define_by_name((char_u *)name, (char_u *)icon, (char_u *)linehl,
+ (char_u *)text, (char_u *)texthl, (char_u *)numhl)
+ == OK) {
+ retval = 0;
+ }
+
+cleanup:
+ xfree(name);
+ xfree(icon);
+ xfree(linehl);
+ xfree(text);
+ xfree(texthl);
+ xfree(numhl);
+
+ return retval;
+}
+
+/// Define multiple signs using attributes from list 'l' and store the return
+/// values in 'retlist'.
+void sign_define_multiple(list_T *l, list_T *retlist)
+{
+ int retval;
+
+ TV_LIST_ITER_CONST(l, li, {
+ retval = -1;
+ if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) {
+ retval = sign_define_from_dict(NULL, TV_LIST_ITEM_TV(li)->vval.v_dict);
+ } else {
+ EMSG(_(e_dictreq));
+ }
+ tv_list_append_number(retlist, retval);
+ });
+}
+
+/// Place a new sign using the values specified in dict 'dict'. Returns the sign
+/// identifier if successfully placed, otherwise returns 0.
+int sign_place_from_dict(
+ typval_T *id_tv,
+ typval_T *group_tv,
+ typval_T *name_tv,
+ typval_T *buf_tv,
+ dict_T *dict)
+{
+ int sign_id = 0;
+ char_u *group = NULL;
+ char_u *sign_name = NULL;
+ buf_T *buf = NULL;
+ dictitem_T *di;
+ linenr_T lnum = 0;
+ int prio = SIGN_DEF_PRIO;
+ bool notanum = false;
+ int ret_sign_id = -1;
+
+ // sign identifier
+ if (id_tv == NULL) {
+ di = tv_dict_find(dict, "id", -1);
+ if (di != NULL) {
+ id_tv = &di->di_tv;
+ }
+ }
+ if (id_tv == NULL) {
+ sign_id = 0;
+ } else {
+ sign_id = (int)tv_get_number_chk(id_tv, &notanum);
+ if (notanum) {
+ return -1;
+ }
+ if (sign_id < 0) {
+ EMSG(_(e_invarg));
+ return -1;
+ }
+ }
+
+ // sign group
+ if (group_tv == NULL) {
+ di = tv_dict_find(dict, "group", -1);
+ if (di != NULL) {
+ group_tv = &di->di_tv;
+ }
+ }
+ if (group_tv == NULL) {
+ group = NULL; // global group
+ } else {
+ group = (char_u *)tv_get_string_chk(group_tv);
+ if (group == NULL) {
+ goto cleanup;
+ }
+ if (group[0] == '\0') { // global sign group
+ group = NULL;
+ } else {
+ group = vim_strsave(group);
+ if (group == NULL) {
+ return -1;
+ }
+ }
+ }
+
+ // sign name
+ if (name_tv == NULL) {
+ di = tv_dict_find(dict, "name", -1);
+ if (di != NULL) {
+ name_tv = &di->di_tv;
+ }
+ }
+ if (name_tv == NULL) {
+ goto cleanup;
+ }
+ sign_name = (char_u *)tv_get_string_chk(name_tv);
+ if (sign_name == NULL) {
+ goto cleanup;
+ }
+
+ // buffer to place the sign
+ if (buf_tv == NULL) {
+ di = tv_dict_find(dict, "buffer", -1);
+ if (di != NULL) {
+ buf_tv = &di->di_tv;
+ }
+ }
+ if (buf_tv == NULL) {
+ goto cleanup;
+ }
+ buf = get_buf_arg(buf_tv);
+ if (buf == NULL) {
+ goto cleanup;
+ }
+
+ // line number of the sign
+ di = tv_dict_find(dict, "lnum", -1);
+ if (di != NULL) {
+ lnum = tv_get_lnum(&di->di_tv);
+ if (lnum <= 0) {
+ EMSG(_(e_invarg));
+ goto cleanup;
+ }
+ }
+
+ // sign priority
+ di = tv_dict_find(dict, "priority", -1);
+ if (di != NULL) {
+ prio = (int)tv_get_number_chk(&di->di_tv, &notanum);
+ if (notanum) {
+ goto cleanup;
+ }
+ }
+
+ if (sign_place(&sign_id, group, sign_name, buf, lnum, prio) == OK) {
+ ret_sign_id = sign_id;
+ }
+
+cleanup:
+ xfree(group);
+
+ return ret_sign_id;
+}
+
+/// Undefine multiple signs
+void sign_undefine_multiple(list_T *l, list_T *retlist)
+{
+ char_u *name;
+ int retval;
+
+ TV_LIST_ITER_CONST(l, li, {
+ retval = -1;
+ name = (char_u *)tv_get_string_chk(TV_LIST_ITEM_TV(li));
+ if (name != NULL && (sign_undefine_by_name(name) == OK)) {
+ retval = 0;
+ }
+ tv_list_append_number(retlist, retval);
+ });
+}
+
+/// Unplace the sign with attributes specified in 'dict'. Returns 0 on success
+/// and -1 on failure.
+int sign_unplace_from_dict(typval_T *group_tv, dict_T *dict)
+{
+ dictitem_T *di;
+ int sign_id = 0;
+ buf_T *buf = NULL;
+ char_u *group = NULL;
+ int retval = -1;
+
+ // sign group
+ if (group_tv != NULL) {
+ group = (char_u *)tv_get_string(group_tv);
+ } else {
+ group = (char_u *)tv_dict_get_string(dict, "group", false);
+ }
+ if (group != NULL) {
+ if (group[0] == '\0') { // global sign group
+ group = NULL;
+ } else {
+ group = vim_strsave(group);
+ if (group == NULL) {
+ return -1;
+ }
+ }
+ }
+
+ if (dict != NULL) {
+ if ((di = tv_dict_find(dict, "buffer", -1)) != NULL) {
+ buf = get_buf_arg(&di->di_tv);
+ if (buf == NULL) {
+ goto cleanup;
+ }
+ }
+ if (tv_dict_find(dict, "id", -1) != NULL) {
+ sign_id = (int)tv_dict_get_number(dict, "id");
+ if (sign_id <= 0) {
+ EMSG(_(e_invarg));
+ goto cleanup;
+ }
+ }
+ }
+
+ if (buf == NULL) {
+ // Delete the sign in all the buffers
+ retval = 0;
+ FOR_ALL_BUFFERS(buf2) {
+ if (sign_unplace(sign_id, group, buf2, 0) != OK) {
+ retval = -1;
+ }
+ }
+ } else if (sign_unplace(sign_id, group, buf, 0) == OK) {
+ retval = 0;
+ }
+
+cleanup:
+ xfree(group);
+
+ return retval;
+}
+
diff --git a/src/nvim/sign_defs.h b/src/nvim/sign_defs.h
index c898dba890..721b2db25b 100644
--- a/src/nvim/sign_defs.h
+++ b/src/nvim/sign_defs.h
@@ -10,39 +10,47 @@
// Sign group
typedef struct signgroup_S
{
- uint16_t refcount; // number of signs in this group
- int next_sign_id; // next sign id for this group
- char_u sg_name[1]; // sign group name
+ uint16_t sg_refcount; // number of signs in this group
+ int sg_next_sign_id; // next sign id for this group
+ char_u sg_name[1]; // sign group name
} signgroup_T;
// Macros to get the sign group structure from the group name
#define SGN_KEY_OFF offsetof(signgroup_T, sg_name)
#define HI2SG(hi) ((signgroup_T *)((hi)->hi_key - SGN_KEY_OFF))
-typedef struct signlist signlist_T;
-
-struct signlist
-{
- int id; // unique identifier for each placed sign
- linenr_T lnum; // line number which has this sign
- int typenr; // typenr of sign
- bool has_text_or_icon; // has text or icon
- signgroup_T *group; // sign group
- int priority; // priority for highlighting
- signlist_T *next; // next signlist entry
- signlist_T *prev; // previous entry -- for easy reordering
+typedef struct sign_entry sign_entry_T;
+
+struct sign_entry {
+ int se_id; // unique identifier for each placed sign
+ int se_typenr; // typenr of sign
+ int se_priority; // priority for highlighting
+ bool se_has_text_or_icon; // has text or icon
+ linenr_T se_lnum; // line number which has this sign
+ signgroup_T *se_group; // sign group
+ sign_entry_T *se_next; // next entry in a list of signs
+ sign_entry_T *se_prev; // previous entry -- for easy reordering
};
+/// Sign attributes. Used by the screen refresh routines.
+typedef struct sign_attrs_S {
+ int sat_typenr;
+ char_u *sat_text;
+ int sat_texthl;
+ int sat_linehl;
+ int sat_numhl;
+} sign_attrs_T;
+
+#define SIGN_SHOW_MAX 9
+
// Default sign priority for highlighting
#define SIGN_DEF_PRIO 10
-// type argument for buf_getsigntype() and sign_get_attr()
+// type argument for sign_get_attr()
typedef enum {
- SIGN_ANY,
SIGN_LINEHL,
- SIGN_ICON,
- SIGN_TEXT,
SIGN_NUMHL,
+ SIGN_TEXT,
} SignType;
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index 6425c9fed5..f6dc3a04a7 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -112,6 +112,7 @@
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/undo.h"
+#include "nvim/ui.h"
#include "nvim/os/os.h"
#include "nvim/os/input.h"
@@ -2889,8 +2890,14 @@ void spell_suggest(int count)
msg_col = 0;
// Ask for choice.
selected = prompt_for_number(&mouse_used);
- if (mouse_used)
+
+ if (ui_has(kUIMessages)) {
+ ui_call_msg_clear();
+ }
+
+ if (mouse_used) {
selected -= lines_left;
+ }
lines_left = Rows; // avoid more prompt
// don't delay for 'smd' in normal_cmd()
msg_scroll = msg_scroll_save;
@@ -3028,7 +3035,7 @@ void ex_spellrepall(exarg_T *eap)
sub_nlines = 0;
curwin->w_cursor.lnum = 0;
while (!got_int) {
- if (do_search(NULL, '/', frompat, 1L, SEARCH_KEEP, NULL) == 0
+ if (do_search(NULL, '/', '/', frompat, 1L, SEARCH_KEEP, NULL) == 0
|| u_save_cursor() == FAIL) {
break;
}
@@ -6624,7 +6631,7 @@ void ex_spelldump(exarg_T *eap)
if (no_spell_checking(curwin)) {
return;
}
- get_option_value((char_u *)"spl", &dummy, &spl, OPT_LOCAL);
+ get_option_value("spl", &dummy, &spl, OPT_LOCAL);
// Create a new empty buffer in a new window.
do_cmdline_cmd("new");
diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c
index 90af010164..3c125959a9 100644
--- a/src/nvim/spellfile.c
+++ b/src/nvim/spellfile.c
@@ -5387,7 +5387,8 @@ spell_add_word (
len, word, NameBuff);
}
}
- if (fseek(fd, fpos_next, SEEK_SET) <= 0) {
+ if (fseek(fd, fpos_next, SEEK_SET) != 0) {
+ PERROR(_("Seek error in spellfile"));
break;
}
}
diff --git a/src/nvim/state.c b/src/nvim/state.c
index b195c1d96b..a3c74789d1 100644
--- a/src/nvim/state.c
+++ b/src/nvim/state.c
@@ -75,6 +75,34 @@ getkey:
}
}
+/// process events on main_loop, but interrupt if input is available
+///
+/// This should be used to handle K_EVENT in states accepting input
+/// otherwise bursts of events can block break checking indefinitely.
+void state_handle_k_event(void)
+{
+ while (true) {
+ Event event = multiqueue_get(main_loop.events);
+ if (event.handler) {
+ event.handler(event.argv);
+ }
+
+ if (multiqueue_empty(main_loop.events)) {
+ // don't breakcheck before return, caller should return to main-loop
+ // and handle input already.
+ return;
+ }
+
+ // TODO(bfredl): as an further micro-optimization, we could check whether
+ // event.handler already checked input.
+ os_breakcheck();
+ if (input_available() || got_int) {
+ return;
+ }
+ }
+}
+
+
/// Return true if in the current mode we need to use virtual.
bool virtual_active(void)
{
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index f99eca7953..77a751e5ad 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -59,7 +59,9 @@ struct hl_group {
bool sg_cleared; ///< "hi clear" was used
int sg_attr; ///< Screen attr @see ATTR_ENTRY
int sg_link; ///< link to this highlight group ID
+ int sg_deflink; ///< default link; restored in highlight_clear()
int sg_set; ///< combination of flags in \ref SG_SET
+ sctx_T sg_deflink_sctx; ///< script where the default link was set
sctx_T sg_script_ctx; ///< script in which the group was last set
// for terminal UIs
int sg_cterm; ///< "cterm=" highlighting attr
@@ -3419,7 +3421,7 @@ static void syn_cmd_on(exarg_T *eap, int syncing)
*/
static void syn_cmd_enable(exarg_T *eap, int syncing)
{
- set_internal_string_var((char_u *)"syntax_cmd", (char_u *)"enable");
+ set_internal_string_var("syntax_cmd", (char_u *)"enable");
syn_cmd_onoff(eap, "syntax");
do_unlet(S_LEN("g:syntax_cmd"), true);
}
@@ -3432,7 +3434,7 @@ static void syn_cmd_reset(exarg_T *eap, int syncing)
{
eap->nextcmd = check_nextcmd(eap->arg);
if (!eap->skip) {
- set_internal_string_var((char_u *)"syntax_cmd", (char_u *)"reset");
+ set_internal_string_var("syntax_cmd", (char_u *)"reset");
do_cmdline_cmd("runtime! syntax/syncolor.vim");
do_unlet(S_LEN("g:syntax_cmd"), true);
}
@@ -5304,13 +5306,17 @@ get_id_list(
xfree(name);
break;
}
- if (name[1] == 'A')
- id = SYNID_ALLBUT;
- else if (name[1] == 'T')
- id = SYNID_TOP;
- else
- id = SYNID_CONTAINED;
- id += current_syn_inc_tag;
+ if (name[1] == 'A') {
+ id = SYNID_ALLBUT + current_syn_inc_tag;
+ } else if (name[1] == 'T') {
+ if (curwin->w_s->b_syn_topgrp >= SYNID_CLUSTER) {
+ id = curwin->w_s->b_syn_topgrp;
+ } else {
+ id = SYNID_TOP + current_syn_inc_tag;
+ }
+ } else {
+ id = SYNID_CONTAINED + current_syn_inc_tag;
+ }
} else if (name[1] == '@') {
if (skip) {
id = -1;
@@ -5614,14 +5620,14 @@ void ex_ownsyntax(exarg_T *eap)
// Move value of b:current_syntax to w:current_syntax.
new_value = get_var_value("b:current_syntax");
if (new_value != NULL) {
- set_internal_string_var((char_u *)"w:current_syntax", new_value);
+ set_internal_string_var("w:current_syntax", new_value);
}
// Restore value of b:current_syntax.
if (old_value == NULL) {
do_unlet(S_LEN("b:current_syntax"), true);
} else {
- set_internal_string_var((char_u *)"b:current_syntax", old_value);
+ set_internal_string_var("b:current_syntax", old_value);
xfree(old_value);
}
}
@@ -6044,6 +6050,9 @@ static const char *highlight_init_both[] = {
"default link Whitespace NonText",
"default link MsgSeparator StatusLine",
"default link NormalFloat Pmenu",
+ "default link FloatBorder VertSplit",
+ "default FloatShadow blend=80 guibg=Black",
+ "default FloatShadowThrough blend=100 guibg=Black",
"RedrawDebugNormal cterm=reverse gui=reverse",
"RedrawDebugClear ctermbg=Yellow guibg=Yellow",
"RedrawDebugComposed ctermbg=Green guibg=Green",
@@ -6601,6 +6610,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
const char *to_end;
int from_id;
int to_id;
+ struct hl_group *hlgroup = NULL;
from_end = (const char *)skiptowhite((const char_u *)from_start);
to_start = (const char *)skipwhite((const char_u *)from_end);
@@ -6627,7 +6637,16 @@ void do_highlight(const char *line, const bool forceit, const bool init)
(int)(to_end - to_start));
}
- if (from_id > 0 && (!init || HL_TABLE()[from_id - 1].sg_set == 0)) {
+ if (from_id > 0) {
+ hlgroup = &HL_TABLE()[from_id - 1];
+ if (dodefault && (forceit || hlgroup->sg_deflink == 0)) {
+ hlgroup->sg_deflink = to_id;
+ hlgroup->sg_deflink_sctx = current_sctx;
+ hlgroup->sg_deflink_sctx.sc_lnum += sourcing_lnum;
+ }
+ }
+
+ if (from_id > 0 && (!init || hlgroup->sg_set == 0)) {
// Don't allow a link when there already is some highlighting
// for the group, unless '!' is used
if (to_id > 0 && !forceit && !init
@@ -6635,17 +6654,16 @@ void do_highlight(const char *line, const bool forceit, const bool init)
if (sourcing_name == NULL && !dodefault) {
EMSG(_("E414: group has settings, highlight link ignored"));
}
- } else if (HL_TABLE()[from_id - 1].sg_link != to_id
- || HL_TABLE()[from_id - 1].sg_script_ctx.sc_sid
- != current_sctx.sc_sid
- || HL_TABLE()[from_id - 1].sg_cleared) {
+ } else if (hlgroup->sg_link != to_id
+ || hlgroup->sg_script_ctx.sc_sid != current_sctx.sc_sid
+ || hlgroup->sg_cleared) {
if (!init) {
- HL_TABLE()[from_id - 1].sg_set |= SG_LINK;
+ hlgroup->sg_set |= SG_LINK;
}
- HL_TABLE()[from_id - 1].sg_link = to_id;
- HL_TABLE()[from_id - 1].sg_script_ctx = current_sctx;
- HL_TABLE()[from_id - 1].sg_script_ctx.sc_lnum += sourcing_lnum;
- HL_TABLE()[from_id - 1].sg_cleared = false;
+ hlgroup->sg_link = to_id;
+ hlgroup->sg_script_ctx = current_sctx;
+ hlgroup->sg_script_ctx.sc_lnum += sourcing_lnum;
+ hlgroup->sg_cleared = false;
redraw_all_later(SOME_VALID);
// Only call highlight changed() once after multiple changes
@@ -7076,13 +7094,14 @@ void restore_cterm_colors(void)
*/
static int hl_has_settings(int idx, int check_link)
{
- return HL_TABLE()[idx].sg_attr != 0
- || HL_TABLE()[idx].sg_cterm_fg != 0
- || HL_TABLE()[idx].sg_cterm_bg != 0
- || HL_TABLE()[idx].sg_rgb_fg_name != NULL
- || HL_TABLE()[idx].sg_rgb_bg_name != NULL
- || HL_TABLE()[idx].sg_rgb_sp_name != NULL
- || (check_link && (HL_TABLE()[idx].sg_set & SG_LINK));
+ return HL_TABLE()[idx].sg_cleared == 0
+ && (HL_TABLE()[idx].sg_attr != 0
+ || HL_TABLE()[idx].sg_cterm_fg != 0
+ || HL_TABLE()[idx].sg_cterm_bg != 0
+ || HL_TABLE()[idx].sg_rgb_fg_name != NULL
+ || HL_TABLE()[idx].sg_rgb_bg_name != NULL
+ || HL_TABLE()[idx].sg_rgb_sp_name != NULL
+ || (check_link && (HL_TABLE()[idx].sg_set & SG_LINK)));
}
/*
@@ -7105,12 +7124,11 @@ static void highlight_clear(int idx)
XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_bg_name);
XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_sp_name);
HL_TABLE()[idx].sg_blend = -1;
- // Clear the script ID only when there is no link, since that is not
- // cleared.
- if (HL_TABLE()[idx].sg_link == 0) {
- HL_TABLE()[idx].sg_script_ctx.sc_sid = 0;
- HL_TABLE()[idx].sg_script_ctx.sc_lnum = 0;
- }
+ // Restore default link and context if they exist. Otherwise clears.
+ HL_TABLE()[idx].sg_link = HL_TABLE()[idx].sg_deflink;
+ // Since we set the default link, set the location to where the default
+ // link was set.
+ HL_TABLE()[idx].sg_script_ctx = HL_TABLE()[idx].sg_deflink_sctx;
}
diff --git a/src/nvim/syntax.h b/src/nvim/syntax.h
index 9fbad74f64..38f848f178 100644
--- a/src/nvim/syntax.h
+++ b/src/nvim/syntax.h
@@ -27,6 +27,8 @@
#define HL_CONCEAL 0x20000 /* can be concealed */
#define HL_CONCEALENDS 0x40000 /* can be concealed */
+#define SYN_GROUP_STATIC(s) syn_check_group((char_u *)S_LEN(s))
+
typedef struct {
char *name;
RgbValue color;
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index c6b1a0d04c..a6310344e9 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -625,7 +625,7 @@ do_tag(
}
if (ic && !msg_scrolled && msg_silent == 0) {
ui_flush();
- os_delay(1000L, true);
+ os_delay(1007L, true);
}
}
@@ -908,7 +908,7 @@ add_llist_tags(
if (len > 128) {
len = 128;
}
- xstrlcpy((char *)tag_name, (const char *)tagp.tagname, len);
+ xstrlcpy((char *)tag_name, (const char *)tagp.tagname, len + 1);
tag_name[len] = NUL;
// Save the tag file name
@@ -975,7 +975,8 @@ add_llist_tags(
if (cmd_len > (CMDBUFFSIZE - 5)) {
cmd_len = CMDBUFFSIZE - 5;
}
- xstrlcat((char *)cmd, (char *)cmd_start, cmd_len);
+ snprintf((char *)cmd + len, CMDBUFFSIZE + 1 - len,
+ "%.*s", cmd_len, cmd_start);
len += cmd_len;
if (cmd[len - 1] == '$') {
@@ -1141,7 +1142,7 @@ static int find_tagfunc_tags(
int result = FAIL;
typval_T args[4];
typval_T rettv;
- char_u flagString[3];
+ char_u flagString[4];
dict_T *d;
taggy_T *tag = &curwin->w_tagstack[curwin->w_tagstackidx];
@@ -1170,9 +1171,10 @@ static int find_tagfunc_tags(
args[3].v_type = VAR_UNKNOWN;
vim_snprintf((char *)flagString, sizeof(flagString),
- "%s%s",
+ "%s%s%s",
g_tag_at_cursor ? "c": "",
- flags & TAG_INS_COMP ? "i": "");
+ flags & TAG_INS_COMP ? "i": "",
+ flags & TAG_REGEXP ? "r": "");
save_pos = curwin->w_cursor;
result = call_vim_function(curbuf->b_p_tfu, 3, args, &rettv);
@@ -1461,7 +1463,7 @@ find_tags(
p_ic = ignorecase_opt(pat, true, true);
break;
default:
- assert(false);
+ abort();
}
help_save = curbuf->b_help;
@@ -2809,7 +2811,7 @@ static int jumpto_tag(
// start search before first line
curwin->w_cursor.lnum = 0;
}
- if (do_search(NULL, pbuf[0], pbuf + 1, (long)1,
+ if (do_search(NULL, pbuf[0], pbuf[0], pbuf + 1, (long)1,
search_options, NULL)) {
retval = OK;
} else {
@@ -2819,8 +2821,8 @@ static int jumpto_tag(
/*
* try again, ignore case now
*/
- p_ic = TRUE;
- if (!do_search(NULL, pbuf[0], pbuf + 1, (long)1,
+ p_ic = true;
+ if (!do_search(NULL, pbuf[0], pbuf[0], pbuf + 1, (long)1,
search_options, NULL)) {
// Failed to find pattern, take a guess: "^func ("
found = 2;
@@ -2828,11 +2830,12 @@ static int jumpto_tag(
cc = *tagp.tagname_end;
*tagp.tagname_end = NUL;
snprintf((char *)pbuf, LSIZE, "^%s\\s\\*(", tagp.tagname);
- if (!do_search(NULL, '/', pbuf, (long)1, search_options, NULL)) {
+ if (!do_search(NULL, '/', '/', pbuf, (long)1, search_options, NULL)) {
// Guess again: "^char * \<func ("
snprintf((char *)pbuf, LSIZE, "^\\[#a-zA-Z_]\\.\\*\\<%s\\s\\*(",
tagp.tagname);
- if (!do_search(NULL, '/', pbuf, (long)1, search_options, NULL)) {
+ if (!do_search(NULL, '/', '/', pbuf, (long)1,
+ search_options, NULL)) {
found = 0;
}
}
@@ -2850,7 +2853,7 @@ static int jumpto_tag(
MSG(_("E435: Couldn't find tag, just guessing!"));
if (!msg_scrolled && msg_silent == 0) {
ui_flush();
- os_delay(1000L, true);
+ os_delay(1010L, true);
}
}
retval = OK;
@@ -3002,7 +3005,8 @@ static int test_for_current(char_u *fname, char_u *fname_end, char_u *tag_fname,
*/
static int find_extra(char_u **pp)
{
- char_u *str = *pp;
+ char_u *str = *pp;
+ char_u first_char = **pp;
// Repeat for addresses separated with ';'
for (;; ) {
@@ -3010,7 +3014,7 @@ static int find_extra(char_u **pp)
str = skipdigits(str);
} else if (*str == '/' || *str == '?') {
str = skip_regexp(str + 1, *str, false, NULL);
- if (*str != **pp) {
+ if (*str != first_char) {
str = NULL;
} else {
str++;
@@ -3028,6 +3032,7 @@ static int find_extra(char_u **pp)
break;
}
str++; // skip ';'
+ first_char = *str;
}
if (str != NULL && STRNCMP(str, ";\"", 2) == 0) {
@@ -3152,7 +3157,7 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname)
bool is_static;
ret = find_tags(pat, &num_matches, &matches,
- TAG_REGEXP | TAG_NOIC, (int)MAXCOL, buf_fname);
+ TAG_REGEXP | TAG_NOIC, MAXCOL, buf_fname);
if (ret == OK && num_matches > 0) {
for (i = 0; i < num_matches; ++i) {
int parse_result = parse_match(matches[i], &tp);
@@ -3404,6 +3409,7 @@ int set_tagstack(win_T *wp, const dict_T *d, int action)
if ((di = tv_dict_find(d, "items", -1)) != NULL) {
if (di->di_tv.v_type != VAR_LIST) {
+ EMSG(_(e_listreq));
return FAIL;
}
l = di->di_tv.vval.v_list;
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index 39e2ca6171..afad20f557 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -169,19 +169,20 @@ void terminal_teardown(void)
multiqueue_free(refresh_timer.events);
time_watcher_close(&refresh_timer, NULL);
pmap_free(ptr_t)(invalidated_terminals);
+ invalidated_terminals = NULL;
}
// public API {{{
-Terminal *terminal_open(TerminalOptions opts)
+Terminal *terminal_open(buf_T *buf, TerminalOptions opts)
{
// Create a new terminal instance and configure it
Terminal *rv = xcalloc(1, sizeof(Terminal));
rv->opts = opts;
rv->cursor.visible = true;
// Associate the terminal instance with the new buffer
- rv->buf_handle = curbuf->handle;
- curbuf->terminal = rv;
+ rv->buf_handle = buf->handle;
+ buf->terminal = rv;
// Create VTerm
rv->vt = vterm_new(opts.height, opts.width);
vterm_set_utf8(rv->vt, 1);
@@ -198,28 +199,36 @@ Terminal *terminal_open(TerminalOptions opts)
// have as many lines as screen rows when refresh_scrollback is called
rv->invalid_start = 0;
rv->invalid_end = opts.height;
- refresh_screen(rv, curbuf);
+
+ aco_save_T aco;
+ aucmd_prepbuf(&aco, buf);
+
+ refresh_screen(rv, buf);
set_option_value("buftype", 0, "terminal", OPT_LOCAL); // -V666
// Default settings for terminal buffers
- curbuf->b_p_ma = false; // 'nomodifiable'
- curbuf->b_p_ul = -1; // 'undolevels'
- curbuf->b_p_scbk = // 'scrollback' (initialize local from global)
+ buf->b_p_ma = false; // 'nomodifiable'
+ buf->b_p_ul = -1; // 'undolevels'
+ buf->b_p_scbk = // 'scrollback' (initialize local from global)
(p_scbk < 0) ? 10000 : MAX(1, p_scbk);
- curbuf->b_p_tw = 0; // 'textwidth'
+ buf->b_p_tw = 0; // 'textwidth'
set_option_value("wrap", false, NULL, OPT_LOCAL);
set_option_value("list", false, NULL, OPT_LOCAL);
- buf_set_term_title(curbuf, (char *)curbuf->b_ffname);
+ if (buf->b_ffname != NULL) {
+ buf_set_term_title(buf, (char *)buf->b_ffname);
+ }
RESET_BINDING(curwin);
// Reset cursor in current window.
curwin->w_cursor = (pos_T){ .lnum = 1, .col = 0, .coladd = 0 };
// Apply TermOpen autocmds _before_ configuring the scrollback buffer.
- apply_autocmds(EVENT_TERMOPEN, NULL, NULL, false, curbuf);
+ apply_autocmds(EVENT_TERMOPEN, NULL, NULL, false, buf);
// Local 'scrollback' _after_ autocmds.
- curbuf->b_p_scbk = (curbuf->b_p_scbk < 1) ? SB_MAX : curbuf->b_p_scbk;
+ buf->b_p_scbk = (buf->b_p_scbk < 1) ? SB_MAX : buf->b_p_scbk;
+
+ aucmd_restbuf(&aco);
// Configure the scrollback buffer.
- rv->sb_size = (size_t)curbuf->b_p_scbk;
+ rv->sb_size = (size_t)buf->b_p_scbk;
rv->sb_buffer = xmalloc(sizeof(ScrollbackLine *) * rv->sb_size);
// Configure the color palette. Try to get the color from:
@@ -457,7 +466,7 @@ static int terminal_execute(VimState *state, int key)
case K_EVENT:
// We cannot let an event free the terminal yet. It is still needed.
s->term->refcount++;
- multiqueue_process_events(main_loop.events);
+ state_handle_k_event();
s->term->refcount--;
if (s->term->buf_handle == 0) {
s->close = true;
@@ -511,7 +520,9 @@ void terminal_destroy(Terminal *term)
}
if (!term->refcount) {
- if (pmap_has(ptr_t)(invalidated_terminals, term)) {
+ // might be destroyed after terminal_teardown is invoked
+ if (invalidated_terminals
+ && pmap_has(ptr_t)(invalidated_terminals, term)) {
// flush any pending changes to the buffer
block_autocmds();
refresh_terminal(term);
@@ -535,8 +546,47 @@ void terminal_send(Terminal *term, char *data, size_t size)
term->opts.write_cb(data, size, term->opts.data);
}
+static bool is_filter_char(int c)
+{
+ unsigned int flag = 0;
+ switch (c) {
+ case 0x08:
+ flag = TPF_BS;
+ break;
+ case 0x09:
+ flag = TPF_HT;
+ break;
+ case 0x0A:
+ case 0x0D:
+ break;
+ case 0x0C:
+ flag = TPF_FF;
+ break;
+ case 0x1b:
+ flag = TPF_ESC;
+ break;
+ case 0x7F:
+ flag = TPF_DEL;
+ break;
+ default:
+ if (c < ' ') {
+ flag = TPF_C0;
+ } else if (c >= 0x80 && c <= 0x9F) {
+ flag = TPF_C1;
+ }
+ }
+ return !!(tpf_flags & flag);
+}
+
void terminal_paste(long count, char_u **y_array, size_t y_size)
{
+ if (y_size == 0) {
+ 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
// feed the lines to the terminal
for (size_t j = 0; j < y_size; j++) {
@@ -544,9 +594,28 @@ void terminal_paste(long count, char_u **y_array, size_t y_size)
// terminate the previous line
terminal_send(curbuf->terminal, "\n", 1);
}
- terminal_send(curbuf->terminal, (char *)y_array[j], STRLEN(y_array[j]));
+ size_t len = STRLEN(y_array[j]);
+ if (len > buff_len) {
+ buff = xrealloc(buff, len);
+ buff_len = len;
+ }
+ char_u *dst = buff;
+ char_u *src = y_array[j];
+ while (*src != '\0') {
+ len = (size_t)utf_ptr2len(src);
+ int c = utf_ptr2char(src);
+ if (!is_filter_char(c)) {
+ memcpy(dst, src, len);
+ dst += len;
+ }
+ src += len;
+ }
+ terminal_send(curbuf->terminal, (char *)buff, (size_t)(dst - buff));
}
}
+ xfree(buff);
+ vterm_keyboard_end_paste(curbuf->terminal->vt);
+ terminal_flush_output(curbuf->terminal);
}
void terminal_flush_output(Terminal *term)
@@ -1269,6 +1338,7 @@ static void refresh_scrollback(Terminal *term, buf_T *buf)
// focused) of a invalidated terminal
static void refresh_screen(Terminal *term, buf_T *buf)
{
+ assert(buf == curbuf); // TODO(bfredl): remove this condition
int changed = 0;
int added = 0;
int height;
diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile
index e52fd888bd..4641408069 100644
--- a/src/nvim/testdir/Makefile
+++ b/src/nvim/testdir/Makefile
@@ -31,20 +31,11 @@ endif
SCRIPTS ?= $(SCRIPTS_DEFAULT)
# Tests using runtest.vim.
-NEW_TESTS_ALOT := test_alot_utf8 test_alot
+NEW_TESTS_ALOT := test_alot_utf8 test_alot test_alot_latin
NEW_TESTS_IN_ALOT := $(shell sed -n '/^source/ s/^source //; s/\.vim$$//p' $(addsuffix .vim,$(NEW_TESTS_ALOT)))
-NEW_TESTS_IN_ALOT_LATIN := $(shell sed -n '/^source/ s/^source //; s/\.vim$$//p' test_alot_latin.vim)
# Ignored tests.
-# test_alot_latin: Nvim does not allow setting encoding.
-# test_autochdir: ported to Lua, but kept for easier merging.
-# test_eval_func: used as include in old-style test (test_eval.in).
-# test_listlbr: Nvim does not allow setting encoding.
# test_largefile: uses too much resources to run on CI.
NEW_TESTS_IGNORE := \
- test_alot_latin $(NEW_TESTS_IN_ALOT_LATIN) \
- test_autochdir \
- test_eval_func \
- test_listlbr \
test_largefile \
NEW_TESTS := $(sort $(basename $(notdir $(wildcard test_*.vim))))
diff --git a/src/nvim/testdir/check.vim b/src/nvim/testdir/check.vim
index 24d3959f83..7b06e53dd5 100644
--- a/src/nvim/testdir/check.vim
+++ b/src/nvim/testdir/check.vim
@@ -108,3 +108,30 @@ func CheckNotMSWindows()
throw 'Skipped: does not work on MS-Windows'
endif
endfunc
+
+" Command to check for satisfying any of the conditions.
+" e.g. CheckAnyOf Feature:bsd Feature:sun Linux
+command -nargs=+ CheckAnyOf call CheckAnyOf(<f-args>)
+func CheckAnyOf(...)
+ let excp = []
+ for arg in a:000
+ try
+ exe 'Check' .. substitute(arg, ':', ' ', '')
+ return
+ catch /^Skipped:/
+ let excp += [substitute(v:exception, '^Skipped:\s*', '', '')]
+ endtry
+ endfor
+ throw 'Skipped: ' .. join(excp, '; ')
+endfunc
+
+" Command to check for satisfying all of the conditions.
+" e.g. CheckAllOf Unix Gui Option:ballooneval
+command -nargs=+ CheckAllOf call CheckAllOf(<f-args>)
+func CheckAllOf(...)
+ for arg in a:000
+ exe 'Check' .. substitute(arg, ':', ' ', '')
+ endfor
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim
index 275edece1e..49993c03aa 100644
--- a/src/nvim/testdir/runtest.vim
+++ b/src/nvim/testdir/runtest.vim
@@ -13,6 +13,9 @@
" For csh:
" setenv TEST_FILTER Test_channel
"
+" While working on a test you can make $TEST_NO_RETRY non-empty to not retry:
+" export TEST_NO_RETRY=yes
+"
" To ignore failure for tests that are known to fail in a certain environment,
" set $TEST_MAY_FAIL to a comma separated list of function names. E.g. for
" sh/bash:
@@ -373,9 +376,6 @@ let s:flaky_tests = [
\ 'Test_with_partial_callback()',
\ ]
-" Pattern indicating a common flaky test failure.
-let s:flaky_errors_re = 'StopVimInTerminal\|VerifyScreenDump'
-
" Locate Test_ functions and execute them.
redir @q
silent function /^Test_
@@ -410,14 +410,19 @@ for s:test in sort(s:tests)
let total_errors = []
let run_nr = 1
+ " A test can set g:test_is_flaky to retry running the test.
+ let g:test_is_flaky = 0
+
call RunTheTest(s:test)
" Repeat a flaky test. Give up when:
+ " - $TEST_NO_RETRY is not empty
" - it fails again with the same message
" - it fails five times (with a different message)
if len(v:errors) > 0
+ \ && $TEST_NO_RETRY == ''
\ && (index(s:flaky_tests, s:test) >= 0
- \ || v:errors[0] =~ s:flaky_errors_re)
+ \ || g:test_is_flaky)
while 1
call add(s:messages, 'Found errors in ' . s:test . ':')
call extend(s:messages, v:errors)
diff --git a/src/nvim/testdir/script_util.vim b/src/nvim/testdir/script_util.vim
new file mode 100644
index 0000000000..9913b1dfaf
--- /dev/null
+++ b/src/nvim/testdir/script_util.vim
@@ -0,0 +1,69 @@
+" Functions shared by the tests for Vim Script
+
+" Commands to track the execution path of a script
+com! XpathINIT let g:Xpath = ''
+com! -nargs=1 -bar Xpath let g:Xpath ..= <args>
+com! XloopINIT let g:Xloop = 1
+com! -nargs=1 -bar Xloop let g:Xpath ..= <args> .. g:Xloop
+com! XloopNEXT let g:Xloop += 1
+
+" MakeScript() - Make a script file from a function. {{{2
+"
+" Create a script that consists of the body of the function a:funcname.
+" Replace any ":return" by a ":finish", any argument variable by a global
+" variable, and every ":call" by a ":source" for the next following argument
+" 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.
+func MakeScript(funcname, ...)
+ let script = tempname()
+ execute "redir! >" . script
+ execute "function" a:funcname
+ redir END
+ execute "edit" script
+ " Delete the "function" and the "endfunction" lines. Do not include the
+ " word "function" in the pattern since it might be translated if LANG is
+ " set. When MakeScript() is being debugged, this deletes also the debugging
+ " output of its line 3 and 4.
+ exec '1,/.*' . a:funcname . '(.*)/d'
+ /^\d*\s*endfunction\>/,$d
+ %s/^\d*//e
+ %s/return/finish/e
+ %s/\<a:\(\h\w*\)/g:\1/ge
+ normal gg0
+ let cnt = 0
+ while search('\<call\s*\%(\u\|s:\)\w*\s*(.*)', 'W') > 0
+ let cnt = cnt + 1
+ s/\<call\s*\%(\u\|s:\)\w*\s*(.*)/\='source ' . a:{cnt}/
+ endwhile
+ g/^\s*$/d
+ write
+ bwipeout
+ return script
+endfunc
+
+" ExecAsScript - Source a temporary script made from a function. {{{2
+"
+" Make a temporary script file from the function a:funcname, ":source" it, and
+" delete it afterwards. However, if an exception is thrown the file may remain,
+" the caller should call DeleteTheScript() afterwards.
+let s:script_name = ''
+function! ExecAsScript(funcname)
+ " Make a script from the function passed as argument.
+ let s:script_name = MakeScript(a:funcname)
+
+ " Source and delete the script.
+ exec "source" s:script_name
+ call delete(s:script_name)
+ let s:script_name = ''
+endfunction
+
+function! DeleteTheScript()
+ if s:script_name
+ call delete(s:script_name)
+ let s:script_name = ''
+ endif
+endfunc
+
+com! -nargs=1 -bar ExecAsScript call ExecAsScript(<f-args>)
+
diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim
index 4f056abdc0..e50602ccad 100644
--- a/src/nvim/testdir/test_alot.vim
+++ b/src/nvim/testdir/test_alot.vim
@@ -33,11 +33,11 @@ source test_move.vim
source test_partial.vim
source test_popup.vim
source test_put.vim
-source test_recover.vim
+source test_rename.vim
source test_scroll_opt.vim
+source test_shift.vim
source test_sort.vim
source test_sha256.vim
-source test_statusline.vim
source test_suspend.vim
source test_syn_attr.vim
source test_tabline.vim
diff --git a/src/nvim/testdir/test_alot_latin.vim b/src/nvim/testdir/test_alot_latin.vim
index ebb3bde4ce..23a404cac1 100644
--- a/src/nvim/testdir/test_alot_latin.vim
+++ b/src/nvim/testdir/test_alot_latin.vim
@@ -4,7 +4,4 @@
" These tests use latin1 'encoding'. Setting 'encoding' is in the individual
" files, so that they can be run by themselves.
-" Nvim does not allow setting 'encoding', so skip this test group.
-finish
-
source test_regexp_latin.vim
diff --git a/src/nvim/testdir/test_alot_utf8.vim b/src/nvim/testdir/test_alot_utf8.vim
index be0bd01413..70f14320a6 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_listlbr_utf8.vim
source test_matchadd_conceal_utf8.vim
source test_mksession_utf8.vim
source test_regexp_utf8.vim
diff --git a/src/nvim/testdir/test_arglist.vim b/src/nvim/testdir/test_arglist.vim
index 92fedf9bfb..a1ef8325ec 100644
--- a/src/nvim/testdir/test_arglist.vim
+++ b/src/nvim/testdir/test_arglist.vim
@@ -80,6 +80,24 @@ func Test_argadd()
call assert_equal(0, len(argv()))
endfunc
+func Test_argadd_empty_curbuf()
+ new
+ let curbuf = bufnr('%')
+ call writefile(['test', 'Xargadd'], 'Xargadd')
+ " must not re-use the current buffer.
+ argadd Xargadd
+ call assert_equal(curbuf, bufnr('%'))
+ call assert_equal('', bufname('%'))
+ call assert_equal(1, line('$'))
+ rew
+ call assert_notequal(curbuf, bufnr('%'))
+ call assert_equal('Xargadd', bufname('%'))
+ call assert_equal(2, line('$'))
+
+ %argd
+ bwipe!
+endfunc
+
func Init_abc()
args a b c
next
diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim
index b4f7478807..1d114221dc 100644
--- a/src/nvim/testdir/test_assert.vim
+++ b/src/nvim/testdir/test_assert.vim
@@ -52,6 +52,37 @@ func Test_assert_fails_in_try_block()
endtry
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)
+ call assert_equal(1, assert_inrange(5, 7, 8))
+ 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')
+ call assert_equal(0, assert_inrange(7.0, 7, 7))
+ call assert_equal(0, assert_inrange(7, 7.0, 7))
+ call assert_equal(0, assert_inrange(7, 7, 7.0))
+ call assert_equal(0, assert_inrange(5, 7, 5.0))
+ call assert_equal(0, assert_inrange(5, 7, 6.0))
+ call assert_equal(0, assert_inrange(5, 7, 7.0))
+
+ call assert_equal(1, assert_inrange(5, 7, 4.0))
+ call assert_match("Expected range 5.0 - 7.0, but got 4.0", v:errors[0])
+ call remove(v:errors, 0)
+ call assert_equal(1, assert_inrange(5, 7, 8.0))
+ call assert_match("Expected range 5.0 - 7.0, but got 8.0", v:errors[0])
+ call remove(v:errors, 0)
+ endif
+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_autochdir.vim b/src/nvim/testdir/test_autochdir.vim
index 67c537b407..d071f4b325 100644
--- a/src/nvim/testdir/test_autochdir.vim
+++ b/src/nvim/testdir/test_autochdir.vim
@@ -1,10 +1,10 @@
" Test 'autochdir' behavior
-if !exists("+autochdir")
- throw 'Skipped: autochdir feature missing'
-endif
+source check.vim
+CheckOption autochdir
func Test_set_filename()
+ CheckFunction test_autochdir
let cwd = getcwd()
call test_autochdir()
set acd
@@ -17,3 +17,5 @@ func Test_set_filename()
exe 'cd ' . cwd
call delete('samples/Xtest')
endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index c571e37ac3..5611560b1b 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -76,7 +76,7 @@ if has('timers')
endfunc
func Test_OptionSet_modeline()
- throw 'skipped: Nvim does not support test_override()'
+ CheckFunction test_override
call test_override('starting', 1)
au! OptionSet
augroup set_tabstop
@@ -276,28 +276,28 @@ func Test_augroup_warning()
augroup TheWarning
au VimEnter * echo 'entering'
augroup END
- call assert_true(match(execute('au VimEnter'), "TheWarning.*VimEnter") >= 0)
+ call assert_match("TheWarning.*VimEnter", execute('au VimEnter'))
redir => res
augroup! TheWarning
redir END
- call assert_true(match(res, "W19:") >= 0)
- call assert_true(match(execute('au VimEnter'), "-Deleted-.*VimEnter") >= 0)
+ call assert_match("W19:", res)
+ call assert_match("-Deleted-.*VimEnter", execute('au VimEnter'))
" check "Another" does not take the pace of the deleted entry
augroup Another
augroup END
- call assert_true(match(execute('au VimEnter'), "-Deleted-.*VimEnter") >= 0)
+ call assert_match("-Deleted-.*VimEnter", execute('au VimEnter'))
augroup! Another
" no warning for postpone aucmd delete
augroup StartOK
au VimEnter * call RemoveGroup()
augroup END
- call assert_true(match(execute('au VimEnter'), "StartOK.*VimEnter") >= 0)
+ call assert_match("StartOK.*VimEnter", execute('au VimEnter'))
redir => res
doautocmd VimEnter
redir END
- call assert_true(match(res, "W19:") < 0)
+ call assert_notmatch("W19:", res)
au! VimEnter
endfunc
@@ -325,7 +325,7 @@ func Test_augroup_deleted()
au VimEnter * echo
augroup end
augroup! x
- call assert_true(match(execute('au VimEnter'), "-Deleted-.*VimEnter") >= 0)
+ call assert_match("-Deleted-.*VimEnter", execute('au VimEnter'))
au! VimEnter
endfunc
@@ -507,7 +507,7 @@ func s:AutoCommandOptionSet(match)
endfunc
func Test_OptionSet()
- throw 'skipped: Nvim does not support test_override()'
+ CheckFunction test_override
if !has("eval") || !exists("+autochdir")
return
endif
@@ -648,7 +648,7 @@ func Test_OptionSet()
endfunc
func Test_OptionSet_diffmode()
- throw 'skipped: Nvim does not support test_override()'
+ CheckFunction test_override
call test_override('starting', 1)
" 18: Changing an option when entering diff mode
new
@@ -682,7 +682,7 @@ func Test_OptionSet_diffmode()
endfunc
func Test_OptionSet_diffmode_close()
- throw 'skipped: Nvim does not support test_override()'
+ CheckFunction test_override
call test_override('starting', 1)
" 19: Try to close the current window when entering diff mode
" should not segfault
@@ -1279,32 +1279,15 @@ func Test_TextYankPost()
bwipe!
endfunc
-func Test_nocatch_wipe_all_buffers()
- " Real nasty autocommand: wipe all buffers on any event.
- au * * bwipe *
- call assert_fails('next x', 'E93')
- bwipe
- au!
-endfunc
-
-func Test_nocatch_wipe_dummy_buffer()
- " Nasty autocommand: wipe buffer on any event.
- au * x bwipe
- call assert_fails('lv½ /x', 'E480')
- au!
-endfunc
-
-func Test_wipe_cbuffer()
- sv x
- au * * bw
- lb
- au!
+func Test_autocommand_all_events()
+ call assert_fails('au * * bwipe', 'E1155:')
+ call assert_fails('au * x bwipe', 'E1155:')
endfunc
" Test TextChangedI and TextChangedP
+" See test/functional/viml/completion_spec.lua'
func Test_ChangedP()
- " Nvim does not support test_override().
- throw 'skipped: see test/functional/viml/completion_spec.lua'
+ CheckFunction test_override
new
call setline(1, ['foo', 'bar', 'foobar'])
call test_override("char_avail", 1)
@@ -1367,7 +1350,7 @@ func SetLineOne()
endfunc
func Test_TextChangedI_with_setline()
- throw 'skipped: Nvim does not support test_override()'
+ CheckFunction test_override
new
call test_override('char_avail', 1)
autocmd TextChangedI <buffer> call SetLineOne()
@@ -1383,9 +1366,11 @@ func Test_TextChangedI_with_setline()
endfunc
func Test_Changed_FirstTime()
- if !has('terminal') || has('gui_running')
- return
- endif
+ CheckFeature terminal
+ CheckNotGui
+ " Starting a terminal to run Vim is always considered flaky.
+ let g:test_is_flaky = 1
+
" Prepare file for TextChanged event.
call writefile([''], 'Xchanged.txt')
let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile'], {'term_rows': 3})
@@ -1939,21 +1924,40 @@ func Test_autocmd_window()
%bw!
edit one.txt
tabnew two.txt
+ vnew three.txt
+ tabnew four.txt
+ tabprevious
let g:blist = []
- augroup aucmd_win_test
+ augroup aucmd_win_test1
au!
au BufEnter * call add(g:blist, [expand('<afile>'),
\ win_gettype(bufwinnr(expand('<afile>')))])
augroup END
doautoall BufEnter
- call assert_equal([['one.txt', 'autocmd'], ['two.txt', '']], g:blist)
+ call assert_equal([
+ \ ['one.txt', 'autocmd'],
+ \ ['two.txt', ''],
+ \ ['four.txt', 'autocmd'],
+ \ ['three.txt', ''],
+ \ ], g:blist)
- augroup aucmd_win_test
+ augroup aucmd_win_test1
au!
augroup END
- augroup! aucmd_win_test
+ augroup! aucmd_win_test1
%bw!
endfunc
+func Test_autocmd_closes_window()
+ au BufNew,BufWinLeave * e %e
+ file yyy
+ au BufNew,BufWinLeave * ball
+ call assert_fails('n xxx', 'E143:')
+
+ bwipe %
+ au! BufNew
+ au! BufWinLeave
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_backspace_opt.vim b/src/nvim/testdir/test_backspace_opt.vim
index d680b442db..11459991ea 100644
--- a/src/nvim/testdir/test_backspace_opt.vim
+++ b/src/nvim/testdir/test_backspace_opt.vim
@@ -1,15 +1,5 @@
" Tests for 'backspace' settings
-func Exec(expr)
- let str=''
- try
- exec a:expr
- catch /.*/
- let str=v:exception
- endtry
- return str
-endfunc
-
func Test_backspace_option()
set backspace=
call assert_equal('', &backspace)
@@ -41,10 +31,10 @@ func Test_backspace_option()
set backspace-=eol
call assert_equal('', &backspace)
" Check the error
- call assert_equal(0, match(Exec('set backspace=ABC'), '.*E474'))
- call assert_equal(0, match(Exec('set backspace+=def'), '.*E474'))
+ call assert_fails('set backspace=ABC', 'E474:')
+ call assert_fails('set backspace+=def', 'E474:')
" NOTE: Vim doesn't check following error...
- "call assert_equal(0, match(Exec('set backspace-=ghi'), '.*E474'))
+ "call assert_fails('set backspace-=ghi', 'E474:')
" Check backwards compatibility with version 5.4 and earlier
set backspace=0
@@ -55,8 +45,8 @@ func Test_backspace_option()
call assert_equal('2', &backspace)
set backspace=3
call assert_equal('3', &backspace)
- call assert_false(match(Exec('set backspace=4'), '.*E474'))
- call assert_false(match(Exec('set backspace=10'), '.*E474'))
+ call assert_fails('set backspace=4', 'E474:')
+ call assert_fails('set backspace=10', 'E474:')
" Cleared when 'compatible' is set
" set compatible
diff --git a/src/nvim/testdir/test_breakindent.vim b/src/nvim/testdir/test_breakindent.vim
index a4c1f62a43..ff5029b889 100644
--- a/src/nvim/testdir/test_breakindent.vim
+++ b/src/nvim/testdir/test_breakindent.vim
@@ -12,56 +12,88 @@ source view_util.vim
let s:input ="\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP"
-function s:screen_lines(lnum, width) abort
+func s:screen_lines(lnum, width) abort
return ScreenLines([a:lnum, a:lnum + 2], a:width)
-endfunction
+endfunc
-function! s:compare_lines(expect, actual)
+func! s:compare_lines(expect, actual)
call assert_equal(join(a:expect, "\n"), join(a:actual, "\n"))
-endfunction
+endfunc
-function s:test_windows(...)
+func s:test_windows(...)
call NewWindow(10, 20)
setl ts=4 sw=4 sts=4 breakindent
put =s:input
exe get(a:000, 0, '')
-endfunction
+endfunc
-function s:close_windows(...)
+func s:close_windows(...)
call CloseWindow()
exe get(a:000, 0, '')
-endfunction
+endfunc
-function Test_breakindent01()
+func Test_breakindent01()
" simple breakindent test
call s:test_windows('setl briopt=min:0')
- let lines=s:screen_lines(line('.'),8)
- let expect=[
-\ " abcd",
-\ " qrst",
-\ " GHIJ",
-\ ]
+ let lines = s:screen_lines(line('.'),8)
+ let expect = [
+ \ " abcd",
+ \ " qrst",
+ \ " GHIJ",
+ \ ]
call s:compare_lines(expect, lines)
call s:close_windows()
-endfunction
+endfunc
-function Test_breakindent02()
+func Test_breakindent01_vartabs()
+ " like 01 but with vartabs feature
+ if !has("vartabs")
+ return
+ endif
+ call s:test_windows('setl briopt=min:0 vts=4')
+ let lines = s:screen_lines(line('.'),8)
+ let expect = [
+ \ " abcd",
+ \ " qrst",
+ \ " GHIJ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set vts&')
+endfunc
+
+func Test_breakindent02()
" simple breakindent test with showbreak set
call s:test_windows('setl briopt=min:0 sbr=>>')
- let lines=s:screen_lines(line('.'),8)
- let expect=[
-\ " abcd",
-\ " >>qr",
-\ " >>EF",
-\ ]
+ let lines = s:screen_lines(line('.'),8)
+ let expect = [
+ \ " abcd",
+ \ " >>qr",
+ \ " >>EF",
+ \ ]
call s:compare_lines(expect, lines)
call s:close_windows('set sbr=')
-endfunction
+endfunc
+
+func Test_breakindent02_vartabs()
+ if !has("vartabs")
+ return
+ endif
+ " simple breakindent test with showbreak set
+ call s:test_windows('setl briopt=min:0 sbr=>> vts=4')
+ let lines = s:screen_lines(line('.'),8)
+ let expect = [
+ \ " abcd",
+ \ " >>qr",
+ \ " >>EF",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set sbr= vts&')
+endfunc
-function Test_breakindent03()
+func Test_breakindent03()
" simple breakindent test with showbreak set and briopt including sbr
call s:test_windows('setl briopt=sbr,min:0 sbr=++')
- let lines=s:screen_lines(line('.'),8)
+ let lines = s:screen_lines(line('.'),8)
let expect=[
\ " abcd",
\ "++ qrst",
@@ -70,77 +102,177 @@ function Test_breakindent03()
call s:compare_lines(expect, lines)
" clean up
call s:close_windows('set sbr=')
-endfunction
+endfunc
+
+func Test_breakindent03_vartabs()
+ " simple breakindent test with showbreak set and briopt including sbr
+ if !has("vartabs")
+ return
+ endif
+ call s:test_windows('setl briopt=sbr,min:0 sbr=++ vts=4')
+ let lines = s:screen_lines(line('.'),8)
+ let expect = [
+ \ " abcd",
+ \ "++ qrst",
+ \ "++ GHIJ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ " clean up
+ call s:close_windows('set sbr= vts&')
+endfunc
-function Test_breakindent04()
+func Test_breakindent04()
" breakindent set with min width 18
call s:test_windows('setl sbr= briopt=min:18')
- let lines=s:screen_lines(line('.'),8)
- let expect=[
-\ " abcd",
-\ " qrstuv",
-\ " IJKLMN",
-\ ]
+ let lines = s:screen_lines(line('.'),8)
+ let expect = [
+ \ " abcd",
+ \ " qrstuv",
+ \ " IJKLMN",
+ \ ]
call s:compare_lines(expect, lines)
" clean up
call s:close_windows('set sbr=')
-endfunction
+endfunc
-function Test_breakindent05()
+func Test_breakindent04_vartabs()
+ " breakindent set with min width 18
+ if !has("vartabs")
+ return
+ endif
+ call s:test_windows('setl sbr= briopt=min:18 vts=4')
+ let lines = s:screen_lines(line('.'),8)
+ let expect = [
+ \ " abcd",
+ \ " qrstuv",
+ \ " IJKLMN",
+ \ ]
+ call s:compare_lines(expect, lines)
+ " clean up
+ call s:close_windows('set sbr= vts&')
+endfunc
+
+func Test_breakindent05()
" breakindent set and shift by 2
call s:test_windows('setl briopt=shift:2,min:0')
- let lines=s:screen_lines(line('.'),8)
- let expect=[
-\ " abcd",
-\ " qr",
-\ " EF",
-\ ]
+ let lines = s:screen_lines(line('.'),8)
+ let expect = [
+ \ " abcd",
+ \ " qr",
+ \ " EF",
+ \ ]
call s:compare_lines(expect, lines)
call s:close_windows()
-endfunction
+endfunc
+
+func Test_breakindent05_vartabs()
+ " breakindent set and shift by 2
+ if !has("vartabs")
+ return
+ endif
+ call s:test_windows('setl briopt=shift:2,min:0 vts=4')
+ let lines = s:screen_lines(line('.'),8)
+ let expect = [
+ \ " abcd",
+ \ " qr",
+ \ " EF",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set vts&')
+endfunc
-function Test_breakindent06()
+func Test_breakindent06()
" breakindent set and shift by -1
call s:test_windows('setl briopt=shift:-1,min:0')
- let lines=s:screen_lines(line('.'),8)
- let expect=[
-\ " abcd",
-\ " qrstu",
-\ " HIJKL",
-\ ]
+ let lines = s:screen_lines(line('.'),8)
+ let expect = [
+ \ " abcd",
+ \ " qrstu",
+ \ " HIJKL",
+ \ ]
call s:compare_lines(expect, lines)
call s:close_windows()
-endfunction
+endfunc
-function Test_breakindent07()
+func Test_breakindent06_vartabs()
+ " breakindent set and shift by -1
+ if !has("vartabs")
+ return
+ endif
+ call s:test_windows('setl briopt=shift:-1,min:0 vts=4')
+ let lines = s:screen_lines(line('.'),8)
+ let expect = [
+ \ " abcd",
+ \ " qrstu",
+ \ " HIJKL",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set vts&')
+endfunc
+
+func Test_breakindent07()
" breakindent set and shift by 1, Number set sbr=? and briopt:sbr
call s:test_windows('setl briopt=shift:1,sbr,min:0 nu sbr=? nuw=4 cpo+=n')
- let lines=s:screen_lines(line('.'),10)
- let expect=[
-\ " 2 ab",
-\ "? m",
-\ "? x",
-\ ]
+ let lines = s:screen_lines(line('.'),10)
+ let expect = [
+ \ " 2 ab",
+ \ "? m",
+ \ "? x",
+ \ ]
call s:compare_lines(expect, lines)
" clean up
call s:close_windows('set sbr= cpo-=n')
-endfunction
+endfunc
+
+func Test_breakindent07_vartabs()
+ if !has("vartabs")
+ return
+ endif
+ " breakindent set and shift by 1, Number set sbr=? and briopt:sbr
+ call s:test_windows('setl briopt=shift:1,sbr,min:0 nu sbr=? nuw=4 cpo+=n vts=4')
+ let lines = s:screen_lines(line('.'),10)
+ let expect = [
+ \ " 2 ab",
+ \ "? m",
+ \ "? x",
+ \ ]
+ call s:compare_lines(expect, lines)
+ " clean up
+ call s:close_windows('set sbr= cpo-=n vts&')
+endfunc
-function Test_breakindent07a()
+func Test_breakindent07a()
" breakindent set and shift by 1, Number set sbr=? and briopt:sbr
call s:test_windows('setl briopt=shift:1,sbr,min:0 nu sbr=? nuw=4')
- let lines=s:screen_lines(line('.'),10)
- let expect=[
-\ " 2 ab",
-\ " ? m",
-\ " ? x",
-\ ]
+ let lines = s:screen_lines(line('.'),10)
+ let expect = [
+ \ " 2 ab",
+ \ " ? m",
+ \ " ? x",
+ \ ]
call s:compare_lines(expect, lines)
" clean up
call s:close_windows('set sbr=')
-endfunction
+endfunc
+
+func Test_breakindent07a_vartabs()
+ if !has("vartabs")
+ return
+ endif
+ " breakindent set and shift by 1, Number set sbr=? and briopt:sbr
+ call s:test_windows('setl briopt=shift:1,sbr,min:0 nu sbr=? nuw=4 vts=4')
+ let lines = s:screen_lines(line('.'),10)
+ let expect = [
+ \ " 2 ab",
+ \ " ? m",
+ \ " ? x",
+ \ ]
+ call s:compare_lines(expect, lines)
+ " clean up
+ call s:close_windows('set sbr= vts&')
+endfunc
-function Test_breakindent08()
+func Test_breakindent08()
" breakindent set and shift by 1, Number and list set sbr=# and briopt:sbr
call s:test_windows('setl briopt=shift:1,sbr,min:0 nu nuw=4 sbr=# list cpo+=n ts=4')
" make sure, cache is invalidated!
@@ -148,43 +280,96 @@ function Test_breakindent08()
redraw!
set ts=4
redraw!
- let lines=s:screen_lines(line('.'),10)
- let expect=[
-\ " 2 ^Iabcd",
-\ "# opq",
-\ "# BCD",
-\ ]
+ let lines = s:screen_lines(line('.'),10)
+ let expect = [
+ \ " 2 ^Iabcd",
+ \ "# opq",
+ \ "# BCD",
+ \ ]
call s:compare_lines(expect, lines)
call s:close_windows('set sbr= cpo-=n')
-endfunction
+endfunc
-function Test_breakindent08a()
+func Test_breakindent08_vartabs()
+ if !has("vartabs")
+ return
+ endif
+ " breakindent set and shift by 1, Number and list set sbr=# and briopt:sbr
+ call s:test_windows('setl briopt=shift:1,sbr,min:0 nu nuw=4 sbr=# list cpo+=n ts=4 vts=4')
+ " make sure, cache is invalidated!
+ set ts=8
+ redraw!
+ set ts=4
+ redraw!
+ let lines = s:screen_lines(line('.'),10)
+ let expect = [
+ \ " 2 ^Iabcd",
+ \ "# opq",
+ \ "# BCD",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set sbr= cpo-=n vts&')
+endfunc
+
+func Test_breakindent08a()
" breakindent set and shift by 1, Number and list set sbr=# and briopt:sbr
call s:test_windows('setl briopt=shift:1,sbr,min:0 nu nuw=4 sbr=# list')
- let lines=s:screen_lines(line('.'),10)
- let expect=[
-\ " 2 ^Iabcd",
-\ " # opq",
-\ " # BCD",
-\ ]
+ let lines = s:screen_lines(line('.'),10)
+ let expect = [
+ \ " 2 ^Iabcd",
+ \ " # opq",
+ \ " # BCD",
+ \ ]
call s:compare_lines(expect, lines)
call s:close_windows('set sbr=')
-endfunction
+endfunc
+
+func Test_breakindent08a_vartabs()
+ if !has("vartabs")
+ return
+ endif
+ " breakindent set and shift by 1, Number and list set sbr=# and briopt:sbr
+ call s:test_windows('setl briopt=shift:1,sbr,min:0 nu nuw=4 sbr=# list vts=4')
+ let lines = s:screen_lines(line('.'),10)
+ let expect = [
+ \ " 2 ^Iabcd",
+ \ " # opq",
+ \ " # BCD",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set sbr= vts&')
+endfunc
-function Test_breakindent09()
+func Test_breakindent09()
" breakindent set and shift by 1, Number and list set sbr=#
call s:test_windows('setl briopt=shift:1,min:0 nu nuw=4 sbr=# list')
- let lines=s:screen_lines(line('.'),10)
- let expect=[
-\ " 2 ^Iabcd",
-\ " #op",
-\ " #AB",
-\ ]
+ let lines = s:screen_lines(line('.'),10)
+ let expect = [
+ \ " 2 ^Iabcd",
+ \ " #op",
+ \ " #AB",
+ \ ]
call s:compare_lines(expect, lines)
call s:close_windows('set sbr=')
-endfunction
+endfunc
+
+func Test_breakindent09_vartabs()
+ if !has("vartabs")
+ return
+ endif
+ " breakindent set and shift by 1, Number and list set sbr=#
+ call s:test_windows('setl briopt=shift:1,min:0 nu nuw=4 sbr=# list vts=4')
+ let lines = s:screen_lines(line('.'),10)
+ let expect = [
+ \ " 2 ^Iabcd",
+ \ " #op",
+ \ " #AB",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set sbr= vts&')
+endfunc
-function Test_breakindent10()
+func Test_breakindent10()
" breakindent set, Number set sbr=~
call s:test_windows('setl cpo+=n sbr=~ nu nuw=4 nolist briopt=sbr,min:0')
" make sure, cache is invalidated!
@@ -192,41 +377,91 @@ function Test_breakindent10()
redraw!
set ts=4
redraw!
- let lines=s:screen_lines(line('.'),10)
- let expect=[
-\ " 2 ab",
-\ "~ mn",
-\ "~ yz",
-\ ]
+ let lines = s:screen_lines(line('.'),10)
+ let expect = [
+ \ " 2 ab",
+ \ "~ mn",
+ \ "~ yz",
+ \ ]
call s:compare_lines(expect, lines)
call s:close_windows('set sbr= cpo-=n')
-endfunction
+endfunc
+
+func Test_breakindent10_vartabs()
+ if !has("vartabs")
+ return
+ endif
+ " breakindent set, Number set sbr=~
+ call s:test_windows('setl cpo+=n sbr=~ nu nuw=4 nolist briopt=sbr,min:0 vts=4')
+ " make sure, cache is invalidated!
+ set ts=8
+ redraw!
+ set ts=4
+ redraw!
+ let lines = s:screen_lines(line('.'),10)
+ let expect = [
+ \ " 2 ab",
+ \ "~ mn",
+ \ "~ yz",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set sbr= cpo-=n vts&')
+endfunc
-function Test_breakindent11()
+func Test_breakindent11()
" test strdisplaywidth()
call s:test_windows('setl cpo-=n sbr=>> nu nuw=4 nolist briopt= ts=4')
let text=getline(2)
let width = strlen(text[1:])+indent(2)+strlen(&sbr)*3 " text wraps 3 times
call assert_equal(width, strdisplaywidth(text))
call s:close_windows('set sbr=')
-endfunction
+endfunc
+
+func Test_breakindent11_vartabs()
+ if !has("vartabs")
+ return
+ endif
+ " test strdisplaywidth()
+ call s:test_windows('setl cpo-=n sbr=>> nu nuw=4 nolist briopt= ts=4 vts=4')
+ let text = getline(2)
+ let width = strlen(text[1:])+indent(2)+strlen(&sbr)*3 " text wraps 3 times
+ call assert_equal(width, strdisplaywidth(text))
+ call s:close_windows('set sbr= vts&')
+endfunc
-function Test_breakindent12()
+func Test_breakindent12()
" test breakindent with long indent
let s:input="\t\t\t\t\t{"
call s:test_windows('setl breakindent linebreak briopt=min:10 nu numberwidth=3 ts=4 list listchars=tab:>-')
- let lines=s:screen_lines(2,16)
- let expect=[
-\ " 2 >--->--->--->",
-\ " ---{ ",
-\ "~ ",
-\ ]
+ let lines = s:screen_lines(2,16)
+ let expect = [
+ \ " 2 >--->--->--->",
+ \ " ---{ ",
+ \ "~ ",
+ \ ]
call s:compare_lines(expect, lines)
call s:close_windows('set nuw=4 listchars=')
-endfunction
+endfunc
-function Test_breakindent13()
- let s:input=""
+func Test_breakindent12_vartabs()
+ if !has("vartabs")
+ return
+ endif
+ " test breakindent with long indent
+ let s:input = "\t\t\t\t\t{"
+ call s:test_windows('setl breakindent linebreak briopt=min:10 nu numberwidth=3 ts=4 list listchars=tab:>- vts=4')
+ let lines = s:screen_lines(2,16)
+ let expect = [
+ \ " 2 >--->--->--->",
+ \ " ---{ ",
+ \ "~ ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set nuw=4 listchars= vts&')
+endfunc
+
+func Test_breakindent13()
+ let s:input = ""
call s:test_windows('setl breakindent briopt=min:10 ts=8')
vert resize 20
call setline(1, [" a\tb\tc\td\te", " z y x w v"])
@@ -237,65 +472,149 @@ function Test_breakindent13()
call assert_equal('d', @a)
call assert_equal('w', @b)
call s:close_windows()
-endfunction
+endfunc
+
+func Test_breakindent13_vartabs()
+ if !has("vartabs")
+ return
+ endif
+ let s:input = ""
+ call s:test_windows('setl breakindent briopt=min:10 ts=8 vts=8')
+ vert resize 20
+ call setline(1, [" a\tb\tc\td\te", " z y x w v"])
+ 1
+ norm! fbgj"ayl
+ 2
+ norm! fygj"byl
+ call assert_equal('d', @a)
+ call assert_equal('w', @b)
+ call s:close_windows('set vts&')
+endfunc
-function Test_breakindent14()
- let s:input=""
+func Test_breakindent14()
+ let s:input = ""
call s:test_windows('setl breakindent briopt= ts=8')
vert resize 30
norm! 3a1234567890
norm! a abcde
exec "norm! 0\<C-V>tex"
- let lines=s:screen_lines(line('.'),8)
- let expect=[
-\ "e ",
-\ "~ ",
-\ "~ ",
-\ ]
+ let lines = s:screen_lines(line('.'),8)
+ let expect = [
+ \ "e ",
+ \ "~ ",
+ \ "~ ",
+ \ ]
call s:compare_lines(expect, lines)
call s:close_windows()
-endfunction
+endfunc
-function Test_breakindent15()
- let s:input=""
+func Test_breakindent14_vartabs()
+ if !has("vartabs")
+ return
+ endif
+ let s:input = ""
+ call s:test_windows('setl breakindent briopt= ts=8 vts=8')
+ vert resize 30
+ norm! 3a1234567890
+ norm! a abcde
+ exec "norm! 0\<C-V>tex"
+ let lines = s:screen_lines(line('.'),8)
+ let expect = [
+ \ "e ",
+ \ "~ ",
+ \ "~ ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set vts&')
+endfunc
+
+func Test_breakindent15()
+ let s:input = ""
call s:test_windows('setl breakindent briopt= ts=8 sw=8')
vert resize 30
norm! 4a1234567890
exe "normal! >>\<C-V>3f0x"
- let lines=s:screen_lines(line('.'),20)
- let expect=[
-\ " 1234567890 ",
-\ "~ ",
-\ "~ ",
-\ ]
+ let lines = s:screen_lines(line('.'),20)
+ let expect = [
+ \ " 1234567890 ",
+ \ "~ ",
+ \ "~ ",
+ \ ]
call s:compare_lines(expect, lines)
call s:close_windows()
-endfunction
+endfunc
+
+func Test_breakindent15_vartabs()
+ if !has("vartabs")
+ return
+ endif
+ let s:input = ""
+ call s:test_windows('setl breakindent briopt= ts=8 sw=8 vts=8')
+ vert resize 30
+ norm! 4a1234567890
+ exe "normal! >>\<C-V>3f0x"
+ let lines = s:screen_lines(line('.'),20)
+ let expect = [
+ \ " 1234567890 ",
+ \ "~ ",
+ \ "~ ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set vts&')
+endfunc
-function Test_breakindent16()
+func Test_breakindent16()
" Check that overlong lines are indented correctly.
- let s:input=""
+ let s:input = ""
call s:test_windows('setl breakindent briopt=min:0 ts=4')
call setline(1, "\t".repeat("1234567890", 10))
resize 6
norm! 1gg$
redraw!
- let lines=s:screen_lines(1,10)
- let expect=[
-\ " 789012",
-\ " 345678",
-\ " 901234",
-\ ]
+ let lines = s:screen_lines(1,10)
+ let expect = [
+ \ " 789012",
+ \ " 345678",
+ \ " 901234",
+ \ ]
call s:compare_lines(expect, lines)
- let lines=s:screen_lines(4,10)
- let expect=[
-\ " 567890",
-\ " 123456",
-\ " 7890 ",
-\ ]
+ let lines = s:screen_lines(4,10)
+ let expect = [
+ \ " 567890",
+ \ " 123456",
+ \ " 7890 ",
+ \ ]
call s:compare_lines(expect, lines)
call s:close_windows()
-endfunction
+endfunc
+
+func Test_breakindent16_vartabs()
+ if !has("vartabs")
+ return
+ endif
+ " Check that overlong lines are indented correctly.
+ let s:input = ""
+ call s:test_windows('setl breakindent briopt=min:0 ts=4 vts=4')
+ call setline(1, "\t".repeat("1234567890", 10))
+ resize 6
+ norm! 1gg$
+ redraw!
+ let lines = s:screen_lines(1,10)
+ let expect = [
+ \ " 789012",
+ \ " 345678",
+ \ " 901234",
+ \ ]
+ call s:compare_lines(expect, lines)
+ let lines = s:screen_lines(4,10)
+ let expect = [
+ \ " 567890",
+ \ " 123456",
+ \ " 7890 ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set vts&')
+endfunc
func Test_breakindent17_vartabs()
if !has("vartabs")
@@ -353,14 +672,19 @@ func Test_breakindent19_sbr_nextpage()
" Scroll down one screen line
setl scrolloff=5
norm! 5gj
- redraw!
let lines = s:screen_lines(1, 20)
let expect = [
- \ "> aaaaaaaaaaaaaaaaaa",
+ \ "aaaaaaaaaaaaaaaaaaaa",
\ "> aaaaaaaaaaaaaaaaaa",
\ "> aaaaaaaaaaaaaaaaaa",
\ ]
call s:compare_lines(expect, lines)
+ redraw!
+ " moving the cursor doesn't change the text offset
+ norm! l
+ redraw!
+ let lines = s:screen_lines(1, 20)
+ call s:compare_lines(expect, lines)
setl breakindent briopt=min:18 sbr=>
norm! 5gj
diff --git a/src/nvim/testdir/test_buffer.vim b/src/nvim/testdir/test_buffer.vim
new file mode 100644
index 0000000000..40111fdf06
--- /dev/null
+++ b/src/nvim/testdir/test_buffer.vim
@@ -0,0 +1,33 @@
+" Tests for Vim buffer
+
+func Test_buffer_error()
+ new foo1
+ new foo2
+
+ call assert_fails('buffer foo', 'E93:')
+ call assert_fails('buffer bar', 'E94:')
+ call assert_fails('buffer 0', 'E939:')
+
+ %bwipe
+endfunc
+
+func Test_badd_options()
+ new SomeNewBuffer
+ setlocal numberwidth=3
+ wincmd p
+ badd +1 SomeNewBuffer
+ new SomeNewBuffer
+ call assert_equal(3, &numberwidth)
+ close
+ close
+ bwipe! SomeNewBuffer
+endfunc
+
+func Test_balt()
+ new SomeNewBuffer
+ balt +3 OtherBuffer
+ e #
+ call assert_equal('OtherBuffer', bufname())
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_bufline.vim b/src/nvim/testdir/test_bufline.vim
index 076f03fdd8..e038bce08e 100644
--- a/src/nvim/testdir/test_bufline.vim
+++ b/src/nvim/testdir/test_bufline.vim
@@ -112,6 +112,17 @@ func Test_deletebufline()
call assert_equal(0, deletebufline(b, 1))
call assert_equal(['b', 'c'], getbufline(b, 1, 2))
exe "bwipe! " . b
+
+ edit XbufOne
+ let one = bufnr()
+ call setline(1, ['a', 'b', 'c'])
+ setlocal nomodifiable
+ split XbufTwo
+ let two = bufnr()
+ call assert_fails('call deletebufline(one, 1)', 'E21:')
+ call assert_equal(two, bufnr())
+ bwipe! XbufTwo
+ bwipe! XbufOne
endfunc
func Test_appendbufline_redraw()
diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim
index 39f865144a..489b2477e6 100644
--- a/src/nvim/testdir/test_cmdline.vim
+++ b/src/nvim/testdir/test_cmdline.vim
@@ -442,6 +442,7 @@ func Test_getcompletion()
set tags&
call assert_fails('call getcompletion("", "burp")', 'E475:')
+ call assert_fails('call getcompletion("abc", [])', 'E475:')
endfunc
func Test_shellcmd_completion()
@@ -568,6 +569,21 @@ func Test_cmdline_complete_user_cmd()
delcommand Foo
endfunc
+func s:ScriptLocalFunction()
+ echo 'yes'
+endfunc
+
+func Test_cmdline_complete_user_func()
+ call feedkeys(":func Test_cmdline_complete_user\<Tab>\<Home>\"\<cr>", 'tx')
+ call assert_match('"func Test_cmdline_complete_user', @:)
+ call feedkeys(":func s:ScriptL\<Tab>\<Home>\"\<cr>", 'tx')
+ call assert_match('"func <SNR>\d\+_ScriptLocalFunction', @:)
+
+ " g: prefix also works
+ call feedkeys(":echo g:Test_cmdline_complete_user_f\<Tab>\<Home>\"\<cr>", 'tx')
+ call assert_match('"echo g:Test_cmdline_complete_user_func', @:)
+endfunc
+
func Test_cmdline_complete_user_names()
if has('unix') && executable('whoami')
let whoami = systemlist('whoami')[0]
diff --git a/src/nvim/testdir/test_command_count.vim b/src/nvim/testdir/test_command_count.vim
index 7262789ab4..55b230373f 100644
--- a/src/nvim/testdir/test_command_count.vim
+++ b/src/nvim/testdir/test_command_count.vim
@@ -158,7 +158,9 @@ endfunc
func Test_command_count_4()
%argd
let bufnr = bufnr('$')
- arga aa bb cc dd ee ff
+ next aa bb cc dd ee ff
+ call assert_equal(bufnr, bufnr('%'))
+
3argu
let args = []
.,$-argdo call add(args, expand('%'))
diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim
index 9101f8cfa0..c3de7d0050 100644
--- a/src/nvim/testdir/test_compiler.vim
+++ b/src/nvim/testdir/test_compiler.vim
@@ -37,23 +37,33 @@ func Test_compiler()
bw!
endfunc
+func GetCompilerNames()
+ " return glob('$VIMRUNTIME/compiler/*.vim', 0, 1)
+ " \ ->map({i, v -> substitute(v, '.*[\\/]\([a-zA-Z0-9_\-]*\).vim', '\1', '')})
+ " \ ->sort()
+ return sort(map(glob('$VIMRUNTIME/compiler/*.vim', 0, 1), {i, v -> substitute(v, '.*[\\/]\([a-zA-Z0-9_\-]*\).vim', '\1', '')}))
+endfunc
+
func Test_compiler_without_arg()
let runtime = substitute($VIMRUNTIME, '\\', '/', 'g')
let a = split(execute('compiler'))
- call assert_match(runtime .. '/compiler/ant.vim$', a[0])
- call assert_match(runtime .. '/compiler/bcc.vim$', a[1])
- call assert_match(runtime .. '/compiler/xo.vim$', a[-1])
+ let exp = GetCompilerNames()
+ call assert_match(runtime .. '/compiler/' .. exp[0] .. '.vim$', a[0])
+ call assert_match(runtime .. '/compiler/' .. exp[1] .. '.vim$', a[1])
+ call assert_match(runtime .. '/compiler/' .. exp[-1] .. '.vim$', a[-1])
endfunc
func Test_compiler_completion()
+ " let clist = GetCompilerNames()->join(' ')
+ let clist = join(GetCompilerNames(), ' ')
call feedkeys(":compiler \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_match('^"compiler ant bcc .* xmlwf xo$', @:)
+ call assert_match('^"compiler ' .. clist .. '$', @:)
call feedkeys(":compiler p\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"compiler pbx perl php pylint pyunit', @:)
+ call assert_match('"compiler pbx perl\( p[a-z]\+\)\+ pylint pyunit', @:)
call feedkeys(":compiler! p\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"compiler! pbx perl php pylint pyunit', @:)
+ call assert_match('"compiler! pbx perl\( p[a-z]\+\)\+ pylint pyunit', @:)
endfunc
func Test_compiler_error()
diff --git a/src/nvim/testdir/test_conceal.vim b/src/nvim/testdir/test_conceal.vim
new file mode 100644
index 0000000000..1306dbe5cf
--- /dev/null
+++ b/src/nvim/testdir/test_conceal.vim
@@ -0,0 +1,282 @@
+" Tests for 'conceal'.
+
+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)
+ syntax match test /|hidden|/ conceal
+ set conceallevel=2
+ set concealcursor=
+ exe "normal /here\r"
+ new
+ call setline(1, lines)
+ call setline(4, "Second window")
+ syntax match test /|hidden|/ conceal
+ set conceallevel=2
+ set concealcursor=nc
+ exe "normal /here\r"
+ [CODE]
+
+ call writefile(code, 'XTest_conceal')
+ " Check that cursor line is concealed
+ let buf = RunVimInTerminal('-S XTest_conceal', {})
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_01', {})
+
+ " Check that with concealed text vertical cursor movement is correct.
+ call term_sendkeys(buf, "k")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_02', {})
+
+ " Check that with cursor line is not concealed
+ call term_sendkeys(buf, "j")
+ call term_sendkeys(buf, ":set concealcursor=\r")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_03', {})
+
+ " Check that with cursor line is not concealed when moving cursor down
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_04', {})
+
+ " Check that with cursor line is not concealed when switching windows
+ call term_sendkeys(buf, "\<C-W>\<C-W>")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_05', {})
+
+ " Check that with cursor line is only concealed in Normal mode
+ call term_sendkeys(buf, ":set concealcursor=n\r")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_06n', {})
+ call term_sendkeys(buf, "a")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_06i', {})
+ call term_sendkeys(buf, "\<Esc>/e")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_06c', {})
+ call term_sendkeys(buf, "\<Esc>v")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_06v', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Check that with cursor line is only concealed in Insert mode
+ call term_sendkeys(buf, ":set concealcursor=i\r")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_07n', {})
+ call term_sendkeys(buf, "a")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_07i', {})
+ call term_sendkeys(buf, "\<Esc>/e")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_07c', {})
+ call term_sendkeys(buf, "\<Esc>v")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_07v', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Check that with cursor line is only concealed in Command mode
+ call term_sendkeys(buf, ":set concealcursor=c\r")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_08n', {})
+ call term_sendkeys(buf, "a")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_08i', {})
+ call term_sendkeys(buf, "\<Esc>/e")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_08c', {})
+ call term_sendkeys(buf, "\<Esc>v")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_08v', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Check that with cursor line is only concealed in Visual mode
+ call term_sendkeys(buf, ":set concealcursor=v\r")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_09n', {})
+ call term_sendkeys(buf, "a")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_09i', {})
+ call term_sendkeys(buf, "\<Esc>/e")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_09c', {})
+ call term_sendkeys(buf, "\<Esc>v")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_09v', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Check moving the cursor while in insert mode.
+ call term_sendkeys(buf, ":set concealcursor=\r")
+ call term_sendkeys(buf, "a")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_10', {})
+ call term_sendkeys(buf, "\<Down>")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_11', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Check the "o" command
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_12', {})
+ call term_sendkeys(buf, "o")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_13', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('XTest_conceal')
+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]
+ set cursorline
+ normal othis is a test
+ new
+ call setline(1, ["one", "two", "three", "four", "five"])
+ set ft=help
+ normal M
+ [CODE]
+
+ call writefile(code, 'XTest_conceal_cul')
+ let buf = RunVimInTerminal('-S XTest_conceal_cul', {})
+ call VerifyScreenDump(buf, 'Test_conceal_cul_01', {})
+
+ call term_sendkeys(buf, ":wincmd w\r")
+ call VerifyScreenDump(buf, 'Test_conceal_cul_02', {})
+
+ call term_sendkeys(buf, "k")
+ call VerifyScreenDump(buf, 'Test_conceal_cul_03', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('XTest_conceal_cul')
+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
+ syn region CommentCodeSpan matchgroup=Comment start=/`/ end=/`/ concealends
+ normal fb
+ [CODE]
+ call writefile(code, 'XTest_conceal_resize')
+ let buf = RunVimInTerminal('-S XTest_conceal_resize', {'rows': 6})
+ call VerifyScreenDump(buf, 'Test_conceal_resize_01', {})
+
+ call win_execute(buf->win_findbuf()[0], 'wincmd +')
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_conceal_resize_02', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('XTest_conceal_resize')
+endfunc
+
+" Tests for correct display (cursor column position) with +conceal and
+" tabulators. Need to run this test in a separate Vim instance. Otherwise the
+" screen is not updated (lazy redraw) and the cursor position is wrong.
+func Test_conceal_cursor_pos()
+ let code =<< trim [CODE]
+ :let l = ['start:', '.concealed. text', "|concealed|\ttext"]
+ :let l += ['', "\t.concealed.\ttext", "\t|concealed|\ttext", '']
+ :let l += [".a.\t.b.\t.c.\t.d.", "|a|\t|b|\t|c|\t|d|"]
+ :call append(0, l)
+ :call cursor(1, 1)
+ :" Conceal settings.
+ :set conceallevel=2
+ :set concealcursor=nc
+ :syntax match test /|/ conceal
+ :" Save current cursor position. Only works in <expr> mode, can't be used
+ :" with :normal because it moves the cursor to the command line. Thanks
+ :" to ZyX <zyx.vim@gmail.com> for the idea to use an <expr> mapping.
+ :let curpos = []
+ :nnoremap <expr> GG ":let curpos += ['".screenrow().":".screencol()."']\n"
+ :normal ztj
+ GGk
+ :" We should end up in the same column when running these commands on the
+ :" two lines.
+ :normal ft
+ GGk
+ :normal $
+ GGk
+ :normal 0j
+ GGk
+ :normal ft
+ GGk
+ :normal $
+ GGk
+ :normal 0j0j
+ GGk
+ :" Same for next test block.
+ :normal ft
+ GGk
+ :normal $
+ GGk
+ :normal 0j
+ GGk
+ :normal ft
+ GGk
+ :normal $
+ GGk
+ :normal 0j0j
+ GGk
+ :" And check W with multiple tabs and conceals in a line.
+ :normal W
+ GGk
+ :normal W
+ GGk
+ :normal W
+ GGk
+ :normal $
+ GGk
+ :normal 0j
+ GGk
+ :normal W
+ GGk
+ :normal W
+ GGk
+ :normal W
+ GGk
+ :normal $
+ GGk
+ :set lbr
+ :normal $
+ GGk
+ :set list listchars=tab:>-
+ :normal 0
+ GGk
+ :normal W
+ GGk
+ :normal W
+ GGk
+ :normal W
+ GGk
+ :normal $
+ GGk
+ :call writefile(curpos, 'Xconceal_curpos.out')
+ :q!
+
+ [CODE]
+ call writefile(code, 'XTest_conceal_curpos')
+
+ if RunVim([], [], '-s XTest_conceal_curpos')
+ call assert_equal([
+ \ '2:1', '2:17', '2:20', '3:1', '3:17', '3:20', '5:8', '5:25',
+ \ '5:28', '6:8', '6:25', '6:28', '8:1', '8:9', '8:17', '8:25',
+ \ '8:27', '9:1', '9:9', '9:17', '9:25', '9:26', '9:26', '9:1',
+ \ '9:9', '9:17', '9:25', '9:26'], readfile('Xconceal_curpos.out'))
+ endif
+
+ call delete('Xconceal_curpos.out')
+ call delete('XTest_conceal_curpos')
+endfunc
+
+func Test_conceal_eol()
+ new!
+ setlocal concealcursor=n conceallevel=1
+ call setline(1, ["x", ""])
+ call matchaddpos('Conceal', [[2, 1, 1]], 2, -1, {'conceal': 1})
+ redraw!
+
+ call assert_notequal(screenchar(1, 1), screenchar(2, 2))
+ call assert_equal(screenattr(1, 1), screenattr(1, 2))
+ call assert_equal(screenattr(1, 2), screenattr(2, 2))
+ call assert_equal(screenattr(2, 1), screenattr(2, 2))
+
+ set list
+ redraw!
+
+ call assert_equal(screenattr(1, 1), screenattr(2, 2))
+ call assert_notequal(screenattr(1, 1), screenattr(1, 2))
+ call assert_notequal(screenattr(1, 2), screenattr(2, 1))
+
+ set nolist
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_cursor_func.vim b/src/nvim/testdir/test_cursor_func.vim
index 2e190911b2..53b7da517e 100644
--- a/src/nvim/testdir/test_cursor_func.vim
+++ b/src/nvim/testdir/test_cursor_func.vim
@@ -92,6 +92,11 @@ func Test_screenpos()
\ 'endcol': wincol + 9}, screenpos(winid, 2, 22))
close
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
endfunc
func Test_screenpos_number()
diff --git a/src/nvim/testdir/test_debugger.vim b/src/nvim/testdir/test_debugger.vim
index 59d51b855b..d1464e9d3b 100644
--- a/src/nvim/testdir/test_debugger.vim
+++ b/src/nvim/testdir/test_debugger.vim
@@ -2,6 +2,31 @@
source shared.vim
source screendump.vim
+source check.vim
+
+func CheckCWD()
+ " Check that the longer lines don't wrap due to the length of the script name
+ " in cwd
+ let script_len = len( getcwd() .. '/Xtest1.vim' )
+ let longest_line = len( 'Breakpoint in "" line 1' )
+ if script_len > ( 75 - longest_line )
+ throw 'Skipped: Your CWD has too many characters'
+ endif
+endfunc
+command! -nargs=0 -bar CheckCWD call CheckCWD()
+
+func CheckDbgOutput(buf, lines, options = {})
+ " Verify the expected output
+ let lnum = 20 - len(a:lines)
+ for l in a:lines
+ if get(a:options, 'match', 'equal') ==# 'pattern'
+ call WaitForAssert({-> assert_match(l, term_getline(a:buf, lnum))}, 200)
+ else
+ call WaitForAssert({-> assert_equal(l, term_getline(a:buf, lnum))}, 200)
+ endif
+ let lnum += 1
+ endfor
+endfunc
" Run a Vim debugger command
" If the expected output argument is supplied, then check for it.
@@ -10,20 +35,17 @@ func RunDbgCmd(buf, cmd, ...)
call term_wait(a:buf)
if a:0 != 0
- " Verify the expected output
- let lnum = 20 - len(a:1)
- for l in a:1
- call WaitForAssert({-> assert_equal(l, term_getline(a:buf, lnum))})
- let lnum += 1
- endfor
+ let options = #{match: 'equal'}
+ if a:0 > 1
+ call extend(options, a:2)
+ endif
+ call CheckDbgOutput(a:buf, a:1, options)
endif
endfunc
" Debugger tests
func Test_Debugger()
- if !CanRunVimInTerminal()
- throw 'Skipped: cannot run Vim in a terminal window'
- endif
+ CheckRunVimInTerminal
" Create a Vim script with some functions
let lines =<< trim END
@@ -317,6 +339,785 @@ func Test_Debugger()
call delete('Xtest.vim')
endfunc
+func Test_Backtrace_Through_Source()
+ CheckRunVimInTerminal
+ CheckCWD
+ let file1 =<< trim END
+ func SourceAnotherFile()
+ source Xtest2.vim
+ endfunc
+
+ func CallAFunction()
+ call SourceAnotherFile()
+ call File2Function()
+ endfunc
+
+ func GlobalFunction()
+ call CallAFunction()
+ endfunc
+ END
+ call writefile(file1, 'Xtest1.vim')
+
+ let file2 =<< trim END
+ func DoAThing()
+ echo "DoAThing"
+ endfunc
+
+ func File2Function()
+ call DoAThing()
+ endfunc
+
+ call File2Function()
+ END
+ call writefile(file2, 'Xtest2.vim')
+
+ let buf = RunVimInTerminal('-S Xtest1.vim', {})
+
+ call RunDbgCmd(buf,
+ \ ':debug call GlobalFunction()',
+ \ ['cmd: call GlobalFunction()'])
+ call RunDbgCmd(buf, 'step', ['line 1: call CallAFunction()'])
+
+ call RunDbgCmd(buf, 'backtrace', ['>backtrace',
+ \ '->0 function GlobalFunction',
+ \ 'line 1: call CallAFunction()'])
+
+ call RunDbgCmd(buf, 'step', ['line 1: call SourceAnotherFile()'])
+ call RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim'])
+
+ call RunDbgCmd(buf, 'backtrace', ['>backtrace',
+ \ ' 2 function GlobalFunction[1]',
+ \ ' 1 CallAFunction[1]',
+ \ '->0 SourceAnotherFile',
+ \ 'line 1: source Xtest2.vim'])
+
+ " Step into the 'source' command. Note that we print the full trace all the
+ " way though the source command.
+ call RunDbgCmd(buf, 'step', ['line 1: func DoAThing()'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ '->0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()'])
+
+ call RunDbgCmd( buf, 'up' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ '->1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'up' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 3 function GlobalFunction[1]',
+ \ '->2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'up' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ '->3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'up', [ 'frame at highest level: 3' ] )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ '->3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'down' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 3 function GlobalFunction[1]',
+ \ '->2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'down' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ '->1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'down' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ '->0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'down', [ 'frame is zero' ] )
+
+ " step until we have another meaninfgul trace
+ call RunDbgCmd(buf, 'step', ['line 5: func File2Function()'])
+ call RunDbgCmd(buf, 'step', ['line 9: call File2Function()'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ '->0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 9: call File2Function()'])
+
+ call RunDbgCmd(buf, 'step', ['line 1: call DoAThing()'])
+ call RunDbgCmd(buf, 'step', ['line 1: echo "DoAThing"'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 5 function GlobalFunction[1]',
+ \ ' 4 CallAFunction[1]',
+ \ ' 3 SourceAnotherFile[1]',
+ \ ' 2 script ' .. getcwd() .. '/Xtest2.vim[9]',
+ \ ' 1 function File2Function[1]',
+ \ '->0 DoAThing',
+ \ 'line 1: echo "DoAThing"'])
+
+ " Now, step (back to Xfile1.vim), and call the function _in_ Xfile2.vim
+ call RunDbgCmd(buf, 'step', ['line 1: End of function'])
+ call RunDbgCmd(buf, 'step', ['line 1: End of function'])
+ call RunDbgCmd(buf, 'step', ['line 10: End of sourced file'])
+ call RunDbgCmd(buf, 'step', ['line 1: End of function'])
+ call RunDbgCmd(buf, 'step', ['line 2: call File2Function()'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 1 function GlobalFunction[1]',
+ \ '->0 CallAFunction',
+ \ 'line 2: call File2Function()'])
+
+ call RunDbgCmd(buf, 'step', ['line 1: call DoAThing()'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 2 function GlobalFunction[1]',
+ \ ' 1 CallAFunction[2]',
+ \ '->0 File2Function',
+ \ 'line 1: call DoAThing()'])
+
+ call StopVimInTerminal(buf)
+ call delete('Xtest1.vim')
+ call delete('Xtest2.vim')
+endfunc
+
+func Test_Backtrace_Autocmd()
+ CheckRunVimInTerminal
+ CheckCWD
+ let file1 =<< trim END
+ func SourceAnotherFile()
+ source Xtest2.vim
+ endfunc
+
+ func CallAFunction()
+ call SourceAnotherFile()
+ call File2Function()
+ endfunc
+
+ func GlobalFunction()
+ call CallAFunction()
+ endfunc
+
+ au User TestGlobalFunction :call GlobalFunction() | echo "Done"
+ END
+ call writefile(file1, 'Xtest1.vim')
+
+ let file2 =<< trim END
+ func DoAThing()
+ echo "DoAThing"
+ endfunc
+
+ func File2Function()
+ call DoAThing()
+ endfunc
+
+ call File2Function()
+ END
+ call writefile(file2, 'Xtest2.vim')
+
+ let buf = RunVimInTerminal('-S Xtest1.vim', {})
+
+ call RunDbgCmd(buf,
+ \ ':debug doautocmd User TestGlobalFunction',
+ \ ['cmd: doautocmd User TestGlobalFunction'])
+ call RunDbgCmd(buf, 'step', ['cmd: call GlobalFunction() | echo "Done"'])
+
+ " At this point the ontly thing in the stack is the autocommand
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ '->0 User Autocommands for "TestGlobalFunction"',
+ \ 'cmd: call GlobalFunction() | echo "Done"'])
+
+ " And now we're back into the call stack
+ call RunDbgCmd(buf, 'step', ['line 1: call CallAFunction()'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 1 User Autocommands for "TestGlobalFunction"',
+ \ '->0 function GlobalFunction',
+ \ 'line 1: call CallAFunction()'])
+
+ call RunDbgCmd(buf, 'step', ['line 1: call SourceAnotherFile()'])
+ call RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim'])
+
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 3 User Autocommands for "TestGlobalFunction"',
+ \ ' 2 function GlobalFunction[1]',
+ \ ' 1 CallAFunction[1]',
+ \ '->0 SourceAnotherFile',
+ \ 'line 1: source Xtest2.vim'])
+
+ " Step into the 'source' command. Note that we print the full trace all the
+ " way though the source command.
+ call RunDbgCmd(buf, 'step', ['line 1: func DoAThing()'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 4 User Autocommands for "TestGlobalFunction"',
+ \ ' 3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ '->0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()'])
+
+ call RunDbgCmd( buf, 'up' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 4 User Autocommands for "TestGlobalFunction"',
+ \ ' 3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ '->1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'up' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 4 User Autocommands for "TestGlobalFunction"',
+ \ ' 3 function GlobalFunction[1]',
+ \ '->2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'up' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 4 User Autocommands for "TestGlobalFunction"',
+ \ '->3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'up' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ '->4 User Autocommands for "TestGlobalFunction"',
+ \ ' 3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'up', [ 'frame at highest level: 4' ] )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ '->4 User Autocommands for "TestGlobalFunction"',
+ \ ' 3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'down' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 4 User Autocommands for "TestGlobalFunction"',
+ \ '->3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+
+ call RunDbgCmd( buf, 'down' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 4 User Autocommands for "TestGlobalFunction"',
+ \ ' 3 function GlobalFunction[1]',
+ \ '->2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'down' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 4 User Autocommands for "TestGlobalFunction"',
+ \ ' 3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ '->1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'down' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 4 User Autocommands for "TestGlobalFunction"',
+ \ ' 3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ '->0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'down', [ 'frame is zero' ] )
+
+ " step until we have another meaninfgul trace
+ call RunDbgCmd(buf, 'step', ['line 5: func File2Function()'])
+ call RunDbgCmd(buf, 'step', ['line 9: call File2Function()'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 4 User Autocommands for "TestGlobalFunction"',
+ \ ' 3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ '->0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 9: call File2Function()'])
+
+ call RunDbgCmd(buf, 'step', ['line 1: call DoAThing()'])
+ call RunDbgCmd(buf, 'step', ['line 1: echo "DoAThing"'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 6 User Autocommands for "TestGlobalFunction"',
+ \ ' 5 function GlobalFunction[1]',
+ \ ' 4 CallAFunction[1]',
+ \ ' 3 SourceAnotherFile[1]',
+ \ ' 2 script ' .. getcwd() .. '/Xtest2.vim[9]',
+ \ ' 1 function File2Function[1]',
+ \ '->0 DoAThing',
+ \ 'line 1: echo "DoAThing"'])
+
+ " Now, step (back to Xfile1.vim), and call the function _in_ Xfile2.vim
+ call RunDbgCmd(buf, 'step', ['line 1: End of function'])
+ call RunDbgCmd(buf, 'step', ['line 1: End of function'])
+ call RunDbgCmd(buf, 'step', ['line 10: End of sourced file'])
+ call RunDbgCmd(buf, 'step', ['line 1: End of function'])
+ call RunDbgCmd(buf, 'step', ['line 2: call File2Function()'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 2 User Autocommands for "TestGlobalFunction"',
+ \ ' 1 function GlobalFunction[1]',
+ \ '->0 CallAFunction',
+ \ 'line 2: call File2Function()'])
+
+ call RunDbgCmd(buf, 'step', ['line 1: call DoAThing()'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 3 User Autocommands for "TestGlobalFunction"',
+ \ ' 2 function GlobalFunction[1]',
+ \ ' 1 CallAFunction[2]',
+ \ '->0 File2Function',
+ \ 'line 1: call DoAThing()'])
+
+
+ " Now unwind so that we get back to the original autocommand (and the second
+ " cmd echo "Done")
+ call RunDbgCmd(buf, 'finish', ['line 1: End of function'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 3 User Autocommands for "TestGlobalFunction"',
+ \ ' 2 function GlobalFunction[1]',
+ \ ' 1 CallAFunction[2]',
+ \ '->0 File2Function',
+ \ 'line 1: End of function'])
+
+ call RunDbgCmd(buf, 'finish', ['line 2: End of function'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 2 User Autocommands for "TestGlobalFunction"',
+ \ ' 1 function GlobalFunction[1]',
+ \ '->0 CallAFunction',
+ \ 'line 2: End of function'])
+
+ call RunDbgCmd(buf, 'finish', ['line 1: End of function'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 1 User Autocommands for "TestGlobalFunction"',
+ \ '->0 function GlobalFunction',
+ \ 'line 1: End of function'])
+
+ call RunDbgCmd(buf, 'step', ['cmd: echo "Done"'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ '->0 User Autocommands for "TestGlobalFunction"',
+ \ 'cmd: echo "Done"'])
+
+ call StopVimInTerminal(buf)
+ call delete('Xtest1.vim')
+ call delete('Xtest2.vim')
+endfunc
+
+func Test_Backtrace_CmdLine()
+ CheckRunVimInTerminal
+ CheckCWD
+ let file1 =<< trim END
+ func SourceAnotherFile()
+ source Xtest2.vim
+ endfunc
+
+ func CallAFunction()
+ call SourceAnotherFile()
+ call File2Function()
+ endfunc
+
+ func GlobalFunction()
+ call CallAFunction()
+ endfunc
+
+ au User TestGlobalFunction :call GlobalFunction() | echo "Done"
+ END
+ call writefile(file1, 'Xtest1.vim')
+
+ let file2 =<< trim END
+ func DoAThing()
+ echo "DoAThing"
+ endfunc
+
+ func File2Function()
+ call DoAThing()
+ endfunc
+
+ call File2Function()
+ END
+ call writefile(file2, 'Xtest2.vim')
+
+ let buf = RunVimInTerminal(
+ \ '-S Xtest1.vim -c "debug call GlobalFunction()"',
+ \ {'wait_for_ruler': 0})
+
+ " Need to wait for the vim-in-terminal to be ready
+ call CheckDbgOutput(buf, ['command line',
+ \ 'cmd: call GlobalFunction()'])
+
+ " At this point the ontly thing in the stack is the cmdline
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ '->0 command line',
+ \ 'cmd: call GlobalFunction()'])
+
+ " And now we're back into the call stack
+ call RunDbgCmd(buf, 'step', ['line 1: call CallAFunction()'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 1 command line',
+ \ '->0 function GlobalFunction',
+ \ 'line 1: call CallAFunction()'])
+
+ call StopVimInTerminal(buf)
+ call delete('Xtest1.vim')
+ call delete('Xtest2.vim')
+endfunc
+
+func Test_Backtrace_DefFunction()
+ CheckRunVimInTerminal
+ CheckCWD
+ let file1 =<< trim END
+ vim9script
+ import File2Function from './Xtest2.vim'
+
+ def SourceAnotherFile()
+ source Xtest2.vim
+ enddef
+
+ def CallAFunction()
+ SourceAnotherFile()
+ File2Function()
+ enddef
+
+ def g:GlobalFunction()
+ CallAFunction()
+ enddef
+
+ defcompile
+ END
+ call writefile(file1, 'Xtest1.vim')
+
+ let file2 =<< trim END
+ vim9script
+
+ def DoAThing(): number
+ var a = 100 * 2
+ a += 3
+ return a
+ enddef
+
+ export def File2Function()
+ DoAThing()
+ enddef
+
+ defcompile
+ File2Function()
+ END
+ call writefile(file2, 'Xtest2.vim')
+
+ let buf = RunVimInTerminal('-S Xtest1.vim', {})
+
+ call RunDbgCmd(buf,
+ \ ':debug call GlobalFunction()',
+ \ ['cmd: call GlobalFunction()'])
+
+ " FIXME: Vim9 lines are not debugged!
+ call RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim'])
+
+ " But they do appear in the backtrace
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '\V>backtrace',
+ \ '\V 2 function GlobalFunction[1]',
+ \ '\V 1 <SNR>\.\*_CallAFunction[1]',
+ \ '\V->0 <SNR>\.\*_SourceAnotherFile',
+ \ '\Vline 1: source Xtest2.vim'],
+ \ #{match: 'pattern'})
+
+
+ call RunDbgCmd(buf, 'step', ['line 1: vim9script'])
+ call RunDbgCmd(buf, 'step', ['line 3: def DoAThing(): number'])
+ call RunDbgCmd(buf, 'step', ['line 9: export def File2Function()'])
+ call RunDbgCmd(buf, 'step', ['line 9: def File2Function()'])
+ call RunDbgCmd(buf, 'step', ['line 13: defcompile'])
+ call RunDbgCmd(buf, 'step', ['line 14: File2Function()'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '\V>backtrace',
+ \ '\V 3 function GlobalFunction[1]',
+ \ '\V 2 <SNR>\.\*_CallAFunction[1]',
+ \ '\V 1 <SNR>\.\*_SourceAnotherFile[1]',
+ \ '\V->0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ '\Vline 14: File2Function()'],
+ \ #{match: 'pattern'})
+
+ " Don't step into compiled functions...
+ call RunDbgCmd(buf, 'step', ['line 15: End of sourced file'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '\V>backtrace',
+ \ '\V 3 function GlobalFunction[1]',
+ \ '\V 2 <SNR>\.\*_CallAFunction[1]',
+ \ '\V 1 <SNR>\.\*_SourceAnotherFile[1]',
+ \ '\V->0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ '\Vline 15: End of sourced file'],
+ \ #{match: 'pattern'})
+
+
+ call StopVimInTerminal(buf)
+ call delete('Xtest1.vim')
+ call delete('Xtest2.vim')
+endfunc
+
+func Test_debug_backtrace_level()
+ CheckRunVimInTerminal
+ CheckCWD
+ let lines =<< trim END
+ let s:file1_var = 'file1'
+ let g:global_var = 'global'
+
+ func s:File1Func( arg )
+ let s:file1_var .= a:arg
+ let local_var = s:file1_var .. ' test1'
+ let g:global_var .= local_var
+ source Xtest2.vim
+ endfunc
+
+ call s:File1Func( 'arg1' )
+ END
+ call writefile(lines, 'Xtest1.vim')
+
+ let lines =<< trim END
+ let s:file2_var = 'file2'
+
+ func s:File2Func( arg )
+ let s:file2_var .= a:arg
+ let local_var = s:file2_var .. ' test2'
+ let g:global_var .= local_var
+ endfunc
+
+ call s:File2Func( 'arg2' )
+ END
+ call writefile(lines, 'Xtest2.vim')
+
+ let file1 = getcwd() .. '/Xtest1.vim'
+ let file2 = getcwd() .. '/Xtest2.vim'
+
+ " set a breakpoint and source file1.vim
+ let buf = RunVimInTerminal(
+ \ '-c "breakadd file 1 Xtest1.vim" -S Xtest1.vim',
+ \ #{ wait_for_ruler: 0 } )
+
+ call CheckDbgOutput(buf, [
+ \ 'Breakpoint in "' .. file1 .. '" line 1',
+ \ 'Entering Debug mode. Type "cont" to continue.',
+ \ 'command line..script ' .. file1,
+ \ 'line 1: let s:file1_var = ''file1'''
+ \ ])
+
+ " step throught the initial declarations
+ call RunDbgCmd(buf, 'step', [ 'line 2: let g:global_var = ''global''' ] )
+ call RunDbgCmd(buf, 'step', [ 'line 4: func s:File1Func( arg )' ] )
+ call RunDbgCmd(buf, 'echo s:file1_var', [ 'file1' ] )
+ call RunDbgCmd(buf, 'echo g:global_var', [ 'global' ] )
+ call RunDbgCmd(buf, 'echo global_var', [ 'global' ] )
+
+ " step in to the first function
+ call RunDbgCmd(buf, 'step', [ 'line 11: call s:File1Func( ''arg1'' )' ] )
+ call RunDbgCmd(buf, 'step', [ 'line 1: let s:file1_var .= a:arg' ] )
+ call RunDbgCmd(buf, 'echo a:arg', [ 'arg1' ] )
+ call RunDbgCmd(buf, 'echo s:file1_var', [ 'file1' ] )
+ call RunDbgCmd(buf, 'echo g:global_var', [ 'global' ] )
+ call RunDbgCmd(buf,
+ \'echo global_var',
+ \[ 'E121: Undefined variable: global_var' ] )
+ call RunDbgCmd(buf,
+ \'echo local_var',
+ \[ 'E121: Undefined variable: local_var' ] )
+ call RunDbgCmd(buf,
+ \'echo l:local_var',
+ \[ 'E121: Undefined variable: l:local_var' ] )
+
+ " backtrace up
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '\V>backtrace',
+ \ '\V 2 command line',
+ \ '\V 1 script ' .. file1 .. '[11]',
+ \ '\V->0 function <SNR>\.\*_File1Func',
+ \ '\Vline 1: let s:file1_var .= a:arg',
+ \ ],
+ \ #{ match: 'pattern' } )
+ call RunDbgCmd(buf, 'up', [ '>up' ] )
+
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '\V>backtrace',
+ \ '\V 2 command line',
+ \ '\V->1 script ' .. file1 .. '[11]',
+ \ '\V 0 function <SNR>\.\*_File1Func',
+ \ '\Vline 1: let s:file1_var .= a:arg',
+ \ ],
+ \ #{ match: 'pattern' } )
+
+ " Expression evaluation in the script frame (not the function frame)
+ " FIXME: Unexpected in this scope (a: should not be visibnle)
+ call RunDbgCmd(buf, 'echo a:arg', [ 'arg1' ] )
+ call RunDbgCmd(buf, 'echo s:file1_var', [ 'file1' ] )
+ call RunDbgCmd(buf, 'echo g:global_var', [ 'global' ] )
+ " FIXME: Unexpected in this scope (global should be found)
+ call RunDbgCmd(buf,
+ \'echo global_var',
+ \[ 'E121: Undefined variable: global_var' ] )
+ call RunDbgCmd(buf,
+ \'echo local_var',
+ \[ 'E121: Undefined variable: local_var' ] )
+ call RunDbgCmd(buf,
+ \'echo l:local_var',
+ \[ 'E121: Undefined variable: l:local_var' ] )
+
+
+ " step while backtraced jumps to the latest frame
+ call RunDbgCmd(buf, 'step', [
+ \ 'line 2: let local_var = s:file1_var .. '' test1''' ] )
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '\V>backtrace',
+ \ '\V 2 command line',
+ \ '\V 1 script ' .. file1 .. '[11]',
+ \ '\V->0 function <SNR>\.\*_File1Func',
+ \ '\Vline 2: let local_var = s:file1_var .. '' test1''',
+ \ ],
+ \ #{ match: 'pattern' } )
+
+ call RunDbgCmd(buf, 'step', [ 'line 3: let g:global_var .= local_var' ] )
+ call RunDbgCmd(buf, 'echo local_var', [ 'file1arg1 test1' ] )
+ call RunDbgCmd(buf, 'echo l:local_var', [ 'file1arg1 test1' ] )
+
+ call RunDbgCmd(buf, 'step', [ 'line 4: source Xtest2.vim' ] )
+ call RunDbgCmd(buf, 'step', [ 'line 1: let s:file2_var = ''file2''' ] )
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '\V>backtrace',
+ \ '\V 3 command line',
+ \ '\V 2 script ' .. file1 .. '[11]',
+ \ '\V 1 function <SNR>\.\*_File1Func[4]',
+ \ '\V->0 script ' .. file2,
+ \ '\Vline 1: let s:file2_var = ''file2''',
+ \ ],
+ \ #{ match: 'pattern' } )
+
+ " Expression evaluation in the script frame file2 (not the function frame)
+ call RunDbgCmd(buf, 'echo a:arg', [ 'E121: Undefined variable: a:arg' ] )
+ call RunDbgCmd(buf,
+ \ 'echo s:file1_var',
+ \ [ 'E121: Undefined variable: s:file1_var' ] )
+ call RunDbgCmd(buf, 'echo g:global_var', [ 'globalfile1arg1 test1' ] )
+ call RunDbgCmd(buf, 'echo global_var', [ 'globalfile1arg1 test1' ] )
+ call RunDbgCmd(buf,
+ \'echo local_var',
+ \[ 'E121: Undefined variable: local_var' ] )
+ call RunDbgCmd(buf,
+ \'echo l:local_var',
+ \[ 'E121: Undefined variable: l:local_var' ] )
+ call RunDbgCmd(buf,
+ \ 'echo s:file2_var',
+ \ [ 'E121: Undefined variable: s:file2_var' ] )
+
+ call RunDbgCmd(buf, 'step', [ 'line 3: func s:File2Func( arg )' ] )
+ call RunDbgCmd(buf, 'echo s:file2_var', [ 'file2' ] )
+
+ " Up the stack to the other script context
+ call RunDbgCmd(buf, 'up')
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '\V>backtrace',
+ \ '\V 3 command line',
+ \ '\V 2 script ' .. file1 .. '[11]',
+ \ '\V->1 function <SNR>\.\*_File1Func[4]',
+ \ '\V 0 script ' .. file2,
+ \ '\Vline 3: func s:File2Func( arg )',
+ \ ],
+ \ #{ match: 'pattern' } )
+ " FIXME: Unexpected. Should see the a: and l: dicts from File1Func
+ call RunDbgCmd(buf, 'echo a:arg', [ 'E121: Undefined variable: a:arg' ] )
+ call RunDbgCmd(buf,
+ \ 'echo l:local_var',
+ \ [ 'E121: Undefined variable: l:local_var' ] )
+
+ call RunDbgCmd(buf, 'up')
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '\V>backtrace',
+ \ '\V 3 command line',
+ \ '\V->2 script ' .. file1 .. '[11]',
+ \ '\V 1 function <SNR>\.\*_File1Func[4]',
+ \ '\V 0 script ' .. file2,
+ \ '\Vline 3: func s:File2Func( arg )',
+ \ ],
+ \ #{ match: 'pattern' } )
+
+ " FIXME: Unexpected (wrong script vars are used)
+ call RunDbgCmd(buf,
+ \ 'echo s:file1_var',
+ \ [ 'E121: Undefined variable: s:file1_var' ] )
+ call RunDbgCmd(buf, 'echo s:file2_var', [ 'file2' ] )
+
+ call StopVimInTerminal(buf)
+ call delete('Xtest1.vim')
+ call delete('Xtest2.vim')
+endfunc
+
" Test for setting a breakpoint on a :endif where the :if condition is false
" and then quit the script. This should generate an interrupt.
func Test_breakpt_endif_intr()
diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim
index f09a64c329..21c1f98283 100644
--- a/src/nvim/testdir/test_diffmode.vim
+++ b/src/nvim/testdir/test_diffmode.vim
@@ -242,6 +242,63 @@ func Test_diffput_two()
bwipe! b
endfunc
+" :diffput and :diffget completes names of buffers which
+" are in diff mode and which are different then current buffer.
+" No completion when the current window is not in diff mode.
+func Test_diffget_diffput_completion()
+ e Xdiff1 | diffthis
+ botright new Xdiff2
+ botright new Xdiff3 | split | diffthis
+ botright new Xdiff4 | diffthis
+
+ wincmd t
+ call assert_equal('Xdiff1', bufname('%'))
+ call feedkeys(":diffput \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"diffput Xdiff3 Xdiff4', @:)
+ call feedkeys(":diffget \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"diffget Xdiff3 Xdiff4', @:)
+ call assert_equal(['Xdiff3', 'Xdiff4'], getcompletion('', 'diff_buffer'))
+
+ " Xdiff2 is not in diff mode, so no completion for :diffput, :diffget
+ wincmd j
+ call assert_equal('Xdiff2', bufname('%'))
+ call feedkeys(":diffput \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"diffput ', @:)
+ call feedkeys(":diffget \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"diffget ', @:)
+ call assert_equal([], getcompletion('', 'diff_buffer'))
+
+ " Xdiff3 is split in 2 windows, only the top one is in diff mode.
+ " So completion of :diffput :diffget only happens in the top window.
+ wincmd j
+ call assert_equal('Xdiff3', bufname('%'))
+ call assert_equal(1, &diff)
+ call feedkeys(":diffput \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"diffput Xdiff1 Xdiff4', @:)
+ call feedkeys(":diffget \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"diffget Xdiff1 Xdiff4', @:)
+ call assert_equal(['Xdiff1', 'Xdiff4'], getcompletion('', 'diff_buffer'))
+
+ wincmd j
+ call assert_equal('Xdiff3', bufname('%'))
+ call assert_equal(0, &diff)
+ call feedkeys(":diffput \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"diffput ', @:)
+ call feedkeys(":diffget \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"diffget ', @:)
+ call assert_equal([], getcompletion('', 'diff_buffer'))
+
+ wincmd j
+ call assert_equal('Xdiff4', bufname('%'))
+ call feedkeys(":diffput \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"diffput Xdiff1 Xdiff3', @:)
+ call feedkeys(":diffget \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"diffget Xdiff1 Xdiff3', @:)
+ call assert_equal(['Xdiff1', 'Xdiff3'], getcompletion('', 'diff_buffer'))
+
+ %bwipe
+endfunc
+
func Test_dp_do_buffer()
e! one
let bn1=bufnr('%')
@@ -964,6 +1021,21 @@ func Test_diff_closeoff()
enew!
endfunc
+func Test_diff_followwrap()
+ new
+ set diffopt+=followwrap
+ set wrap
+ diffthis
+ call assert_equal(1, &wrap)
+ diffoff
+ set nowrap
+ diffthis
+ call assert_equal(0, &wrap)
+ diffoff
+ set diffopt&
+ bwipe!
+endfunc
+
func Test_diff_rnu()
CheckScreendump
@@ -992,6 +1064,18 @@ func Test_diff_rnu()
call delete('Xtest_diff_rnu')
endfunc
+func Test_diff_multilineconceal()
+ new
+ diffthis
+
+ new
+ call matchadd('Conceal', 'a\nb', 9, -1, {'conceal': 'Y'})
+ set cole=2 cocu=n
+ call setline(1, ["a", "b"])
+ diffthis
+ redraw
+endfunc
+
func Test_diff_and_scroll()
" this was causing an ml_get error
set ls=2
diff --git a/src/nvim/testdir/test_digraph.vim b/src/nvim/testdir/test_digraph.vim
index b6d9687560..d23748a3e3 100644
--- a/src/nvim/testdir/test_digraph.vim
+++ b/src/nvim/testdir/test_digraph.vim
@@ -211,6 +211,8 @@ func Test_digraphs()
call Put_Dig("00")
call Put_Dig("el")
call assert_equal(['␀', 'ü', '∞', 'l'], getline(line('.')-3,line('.')))
+ call assert_fails('digraph xy z', 'E39:')
+ call assert_fails('digraph x', 'E474:')
bw!
endfunc
diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim
index 061364fb73..73b57f302e 100644
--- a/src/nvim/testdir/test_eval_stuff.vim
+++ b/src/nvim/testdir/test_eval_stuff.vim
@@ -22,6 +22,17 @@ func Test_E963()
call assert_equal(v_o, v:oldfiles)
endfunc
+func Test_for_invalid()
+ call assert_fails("for x in 99", 'E714:')
+ call assert_fails("for x in function('winnr')", 'E714:')
+ call assert_fails("for x in {'a': 9}", 'E714:')
+
+ if 0
+ /1/5/2/s/\n
+ endif
+ redraw
+endfunc
+
func Test_mkdir_p()
call mkdir('Xmkdir/nested', 'p')
call assert_true(isdirectory('Xmkdir/nested'))
diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim
index 20508b12d3..98a3e60368 100644
--- a/src/nvim/testdir/test_excmd.vim
+++ b/src/nvim/testdir/test_excmd.vim
@@ -131,3 +131,11 @@ func Test_confirm_cmd_cancel()
\ term_getline(buf, 20))}, 1000)
call StopVimInTerminal(buf)
endfunc
+
+" Test for the :winsize command
+func Test_winsize_cmd()
+ call assert_fails('winsize 1', 'E465:')
+ call assert_fails('winsize 1 x', 'E465:')
+ call assert_fails('win_getid(1)', 'E475: Invalid argument: _getid(1)')
+ " Actually changing the window size would be flaky.
+endfunc
diff --git a/src/nvim/testdir/test_exit.vim b/src/nvim/testdir/test_exit.vim
index 99a401d4a4..bd3e9eb4d4 100644
--- a/src/nvim/testdir/test_exit.vim
+++ b/src/nvim/testdir/test_exit.vim
@@ -81,3 +81,32 @@ func Test_exiting()
endif
call delete('Xtestout')
endfunc
+
+" Test for getting the Vim exit code from v:exiting
+func Test_exit_code()
+ call assert_equal(v:null, v:exiting)
+
+ let before =<< trim [CODE]
+ au QuitPre * call writefile(['qp = ' .. v:exiting], 'Xtestout', 'a')
+ au ExitPre * call writefile(['ep = ' .. v:exiting], 'Xtestout', 'a')
+ au VimLeavePre * call writefile(['lp = ' .. v:exiting], 'Xtestout', 'a')
+ au VimLeave * call writefile(['l = ' .. v:exiting], 'Xtestout', 'a')
+ [CODE]
+
+ if RunVim(before, ['quit'], '')
+ call assert_equal(['qp = null', 'ep = null', 'lp = 0', 'l = 0'], readfile('Xtestout'))
+ endif
+ call delete('Xtestout')
+
+ if RunVim(before, ['cquit'], '')
+ call assert_equal(['lp = 1', 'l = 1'], readfile('Xtestout'))
+ endif
+ call delete('Xtestout')
+
+ if RunVim(before, ['cquit 4'], '')
+ call assert_equal(['lp = 4', 'l = 4'], readfile('Xtestout'))
+ endif
+ call delete('Xtestout')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim
index 7b90ba56e0..0b41a1127a 100644
--- a/src/nvim/testdir/test_expr.vim
+++ b/src/nvim/testdir/test_expr.vim
@@ -399,7 +399,11 @@ function Test_printf_errors()
call assert_fails('echo printf("%d", [])', 'E745:')
call assert_fails('echo printf("%d", 1, 2)', 'E767:')
call assert_fails('echo printf("%*d", 1)', 'E766:')
- call assert_fails('echo printf("%d", 1.2)', 'E805:')
+ call assert_fails('echo printf("%s")', 'E766:')
+ if has('float')
+ call assert_fails('echo printf("%d", 1.2)', 'E805:')
+ call assert_fails('echo printf("%f")')
+ endif
endfunc
function Test_max_min_errors()
@@ -497,3 +501,12 @@ func Test_empty_concatenate()
call assert_equal('b', 'a'[4:0] . 'b')
call assert_equal('b', 'b' . 'a'[4:0])
endfunc
+
+func Test_eval_after_if()
+ let s:val = ''
+ func SetVal(x)
+ let s:val ..= a:x
+ endfunc
+ if 0 | eval SetVal('a') | endif | call SetVal('b')
+ call assert_equal('b', s:val)
+endfunc
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index f9f0ade1f6..3cfc964f0a 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -79,6 +79,7 @@ let s:filename_checks = {
\ 'bc': ['file.bc'],
\ 'bdf': ['file.bdf'],
\ 'bib': ['file.bib'],
+ \ 'beancount': ['file.beancount'],
\ 'bindzone': ['named.root'],
\ 'blank': ['file.bl'],
\ 'bsdl': ['file.bsd', 'file.bsdl'],
@@ -274,6 +275,8 @@ let s:filename_checks = {
\ 'lss': ['file.lss'],
\ 'lua': ['file.lua', 'file.rockspec', 'file.nse'],
\ 'lynx': ['lynx.cfg'],
+ \ 'm3build': ['m3makefile', 'm3overrides'],
+ \ 'm3quake': ['file.quake', 'cm3.cfg'],
\ 'm4': ['file.at'],
\ 'mail': ['snd.123', '.letter', '.letter.123', '.followup', '.article', '.article.123', 'pico.123', 'mutt-xx-xxx', 'muttng-xx-xxx', 'ae123.txt', 'file.eml'],
\ 'mailaliases': ['/etc/mail/aliases', '/etc/aliases'],
@@ -357,6 +360,7 @@ let s:filename_checks = {
\ 'po': ['file.po', 'file.pot'],
\ 'pod': ['file.pod'],
\ 'pod6': ['file.pod6'],
+ \ 'poke': ['file.pk'],
\ 'postscr': ['file.ps', 'file.pfa', 'file.afm', 'file.eps', 'file.epsf', 'file.epsi', 'file.ai'],
\ 'pov': ['file.pov'],
\ 'povini': ['.povrayrc'],
@@ -369,7 +373,10 @@ let s:filename_checks = {
\ 'promela': ['file.pml'],
\ 'proto': ['file.proto'],
\ 'protocols': ['/etc/protocols'],
+ \ 'ps1': ['file.ps1', 'file.psd1', 'file.psm1', 'file.pssc'],
+ \ 'ps1xml': ['file.ps1xml'],
\ 'psf': ['file.psf'],
+ \ 'psl': ['file.psl'],
\ 'puppet': ['file.pp'],
\ 'pyrex': ['file.pyx', 'file.pxd'],
\ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', 'file.ptl', 'file.pyi', 'SConstruct'],
@@ -413,7 +420,7 @@ let s:filename_checks = {
\ 'sensors': ['/etc/sensors.conf', '/etc/sensors3.conf'],
\ 'services': ['/etc/services'],
\ 'setserial': ['/etc/serial.conf'],
- \ 'sh': ['/etc/udev/cdsymlinks.conf'],
+ \ 'sh': ['.bashrc', 'file.bash', '/usr/share/doc/bash-completion/filter.sh','/etc/udev/cdsymlinks.conf', 'any/etc/udev/cdsymlinks.conf'],
\ 'sieve': ['file.siv', 'file.sieve'],
\ 'simula': ['file.sim'],
\ 'sinda': ['file.sin', 'file.s85'],
@@ -427,6 +434,7 @@ let s:filename_checks = {
\ 'slrnrc': ['.slrnrc'],
\ 'slrnsc': ['file.score'],
\ 'sm': ['sendmail.cf'],
+ \ 'svelte': ['file.svelte'],
\ 'smarty': ['file.tpl'],
\ 'smcl': ['file.hlp', 'file.ihlp', 'file.smcl'],
\ 'smith': ['file.smt', 'file.smith'],
@@ -466,7 +474,7 @@ let s:filename_checks = {
\ 'tex': ['file.latex', 'file.sty', 'file.dtx', 'file.ltx', 'file.bbl'],
\ 'texinfo': ['file.texinfo', 'file.texi', 'file.txi'],
\ 'texmf': ['texmf.cnf'],
- \ 'text': ['file.text', 'README'],
+ \ 'text': ['file.text', 'README', '/usr/share/doc/bash-completion/AUTHORS'],
\ 'tf': ['file.tf', '.tfrc', 'tfrc'],
\ 'tidy': ['.tidyrc', 'tidyrc', 'tidy.conf'],
\ 'tilde': ['file.t.html'],
@@ -518,7 +526,7 @@ let s:filename_checks = {
\ 'xhtml': ['file.xhtml', 'file.xht'],
\ 'xinetd': ['/etc/xinetd.conf'],
\ 'xmath': ['file.msc', 'file.msf'],
- \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.ui', 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl'],
+ \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.ui', 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl', 'file.wpl', 'any/etc/blkid.tab', 'any/etc/blkid.tab.old', 'any/etc/xdg/menus/file.menu', 'file.atom', 'file.rss', 'file.cdxml', 'file.psc1'],
\ 'xmodmap': ['anyXmodmap'],
\ 'xf86conf': ['xorg.conf', 'xorg.conf-4'],
\ 'xpm2': ['file.xpm2'],
diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim
index 2d058e8e32..fcdf888b96 100644
--- a/src/nvim/testdir/test_fold.vim
+++ b/src/nvim/testdir/test_fold.vim
@@ -823,31 +823,36 @@ func Test_fold_create_delete()
endfunc
func Test_fold_relative_move()
- enew!
+ new
set fdm=indent sw=2 wrap tw=80
- let content = [ ' foo', ' bar', ' baz',
- \ repeat('x', &columns + 1),
- \ ' foo', ' bar', ' baz'
+ let longtext = repeat('x', &columns + 1)
+ let content = [ ' foo', ' ' .. longtext, ' baz',
+ \ longtext,
+ \ ' foo', ' ' .. longtext, ' baz'
\ ]
call append(0, content)
normal zM
- call cursor(3, 1)
- call assert_true(foldclosed(line('.')))
- normal gj
- call assert_equal(2, winline())
+ for lnum in range(1, 3)
+ call cursor(lnum, 1)
+ call assert_true(foldclosed(line('.')))
+ normal gj
+ call assert_equal(2, winline())
+ endfor
call cursor(2, 1)
call assert_true(foldclosed(line('.')))
normal 2gj
call assert_equal(3, winline())
- call cursor(5, 1)
- call assert_true(foldclosed(line('.')))
- normal gk
- call assert_equal(3, winline())
+ for lnum in range(5, 7)
+ call cursor(lnum, 1)
+ call assert_true(foldclosed(line('.')))
+ normal gk
+ call assert_equal(3, winline())
+ endfor
call cursor(6, 1)
call assert_true(foldclosed(line('.')))
diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
index 917a5e8eca..93f567b3a0 100644
--- a/src/nvim/testdir/test_functions.vim
+++ b/src/nvim/testdir/test_functions.vim
@@ -1,5 +1,7 @@
" Tests for various functions.
+
source shared.vim
+source check.vim
" Must be done first, since the alternate buffer must be unset.
func Test_00_bufexists()
@@ -171,9 +173,8 @@ func Test_str2nr()
endfunc
func Test_strftime()
- if !exists('*strftime')
- return
- endif
+ CheckFunction strftime
+
" Format of strftime() depends on system. We assume
" that basic formats tested here are available and
" identical on all systems which support strftime().
@@ -214,6 +215,33 @@ func Test_strftime()
endif
endfunc
+func Test_strptime()
+ CheckFunction strptime
+ CheckNotMSWindows
+
+ if exists('$TZ')
+ let tz = $TZ
+ endif
+ let $TZ = 'UTC'
+
+ call assert_equal(1484653763, strptime('%Y-%m-%d %T', '2017-01-17 11:49:23'))
+
+ " Force DST and check that it's considered
+ let $TZ = 'WINTER0SUMMER,J1,J365'
+ call assert_equal(1484653763 - 3600, strptime('%Y-%m-%d %T', '2017-01-17 11:49:23'))
+
+ call assert_fails('call strptime()', 'E119:')
+ call assert_fails('call strptime("xxx")', 'E119:')
+ call assert_equal(0, strptime("%Y", ''))
+ call assert_equal(0, strptime("%Y", "xxx"))
+
+ if exists('tz')
+ let $TZ = tz
+ else
+ unlet $TZ
+ endif
+endfunc
+
func Test_resolve_unix()
if !has('unix')
return
@@ -833,6 +861,31 @@ func Test_byte2line_line2byte()
bw!
endfunc
+" Test for charidx()
+func Test_charidx()
+ let a = 'xáb́y'
+ call assert_equal(0, charidx(a, 0))
+ call assert_equal(1, charidx(a, 3))
+ call assert_equal(2, charidx(a, 4))
+ call assert_equal(3, charidx(a, 7))
+ call assert_equal(-1, charidx(a, 8))
+ call assert_equal(-1, charidx('', 0))
+
+ " count composing characters
+ call assert_equal(0, charidx(a, 0, 1))
+ call assert_equal(2, charidx(a, 2, 1))
+ call assert_equal(3, charidx(a, 4, 1))
+ call assert_equal(5, charidx(a, 7, 1))
+ call assert_equal(-1, charidx(a, 8, 1))
+ call assert_equal(-1, charidx('', 0, 1))
+
+ call assert_fails('let x = charidx([], 1)', 'E474:')
+ call assert_fails('let x = charidx("abc", [])', 'E474:')
+ call assert_fails('let x = charidx("abc", 1, [])', 'E474:')
+ call assert_fails('let x = charidx("abc", 1, -1)', 'E474:')
+ call assert_fails('let x = charidx("abc", 1, 2)', 'E474:')
+endfunc
+
func Test_count()
let l = ['a', 'a', 'A', 'b']
call assert_equal(2, count(l, 'a'))
@@ -930,10 +983,20 @@ func Test_Executable()
" get "cat" path and remove the leading /
let catcmd = exepath('cat')[1:]
new
+ " check that the relative path works in /
lcd /
call assert_equal(1, executable(catcmd))
- call assert_equal('/' .. catcmd, exepath(catcmd))
+ " let result = catcmd->exepath()
+ let result = exepath(catcmd)
+ " when using chroot looking for sbin/cat can return bin/cat, that is OK
+ if catcmd =~ '\<sbin\>' && result =~ '\<bin\>'
+ call assert_equal('/' .. substitute(catcmd, '\<sbin\>', 'bin', ''), result)
+ else
+ call assert_equal('/' .. catcmd, result)
+ endif
bwipe
+ else
+ throw 'Skipped: does not work on this platform'
endif
endfunc
@@ -1008,10 +1071,10 @@ func Test_inputlist()
endfunc
func Test_balloon_show()
- if has('balloon_eval')
- " This won't do anything but must not crash either.
- call balloon_show('hi!')
- endif
+ CheckFeature balloon_eval
+
+ " This won't do anything but must not crash either.
+ call balloon_show('hi!')
endfunc
func Test_shellescape()
@@ -1385,4 +1448,12 @@ 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
+func HasDefault(msg = 'msg')
+ return a:msg
+endfunc
+
+func Test_default_arg_value()
+ call assert_equal('msg', HasDefault())
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_gn.vim b/src/nvim/testdir/test_gn.vim
index 9acec51913..d09b25b0e7 100644
--- a/src/nvim/testdir/test_gn.vim
+++ b/src/nvim/testdir/test_gn.vim
@@ -1,9 +1,8 @@
" Test for gn command
func Test_gn_command()
- set belloff=all
noautocmd new
- " replace a single char by itsself quoted:
+ " replace a single char by itself quoted:
call setline('.', 'abc x def x ghi x jkl')
let @/ = 'x'
exe "norm! cgn'x'\<esc>.."
@@ -157,7 +156,6 @@ func Test_gn_command()
sil! %d _
set wrapscan&vim
- set belloff&vim
endfunc
func Test_gN_repeat()
diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim
index 4cc4d775d1..ce22de09ca 100644
--- a/src/nvim/testdir/test_highlight.vim
+++ b/src/nvim/testdir/test_highlight.vim
@@ -3,6 +3,7 @@
source view_util.vim
source screendump.vim
source check.vim
+source script_util.vim
func Test_highlight()
" basic test if ":highlight" doesn't crash
@@ -623,4 +624,103 @@ func Test_xxlast_highlight_RGB_color()
hi clear
endfunc
+func Test_highlight_clear_restores_links()
+ let aaa_id = hlID('aaa')
+ call assert_equal(aaa_id, 0)
+
+ " create default link aaa --> bbb
+ hi def link aaa bbb
+ let id_aaa = hlID('aaa')
+ let hl_aaa_bbb = HighlightArgs('aaa')
+
+ " try to redefine default link aaa --> ccc; check aaa --> bbb
+ hi def link aaa ccc
+ call assert_equal(HighlightArgs('aaa'), hl_aaa_bbb)
+
+ " clear aaa; check aaa --> bbb
+ hi clear aaa
+ call assert_equal(HighlightArgs('aaa'), hl_aaa_bbb)
+
+ " link aaa --> ccc; clear aaa; check aaa --> bbb
+ hi link aaa ccc
+ let id_ccc = hlID('ccc')
+ call assert_equal(synIDtrans(id_aaa), id_ccc)
+ hi clear aaa
+ call assert_equal(HighlightArgs('aaa'), hl_aaa_bbb)
+
+ " forcibly set default link aaa --> ddd
+ hi! def link aaa ddd
+ let id_ddd = hlID('ddd')
+ let hl_aaa_ddd = HighlightArgs('aaa')
+ call assert_equal(synIDtrans(id_aaa), id_ddd)
+
+ " link aaa --> eee; clear aaa; check aaa --> ddd
+ hi link aaa eee
+ let eee_id = hlID('eee')
+ call assert_equal(synIDtrans(id_aaa), eee_id)
+ hi clear aaa
+ call assert_equal(HighlightArgs('aaa'), hl_aaa_ddd)
+endfunc
+
+func Test_highlight_clear_restores_context()
+ func FuncContextDefault()
+ hi def link Context ContextDefault
+ endfun
+
+ func FuncContextRelink()
+ " Dummy line
+ hi link Context ContextRelink
+ endfunc
+
+ let scriptContextDefault = MakeScript("FuncContextDefault")
+ let scriptContextRelink = MakeScript("FuncContextRelink")
+ let patContextDefault = fnamemodify(scriptContextDefault, ':t') .. ' line 1'
+ let patContextRelink = fnamemodify(scriptContextRelink, ':t') .. ' line 2'
+
+ exec "source" scriptContextDefault
+ let hlContextDefault = execute("verbose hi Context")
+ call assert_match(patContextDefault, hlContextDefault)
+
+ exec "source" scriptContextRelink
+ let hlContextRelink = execute("verbose hi Context")
+ call assert_match(patContextRelink, hlContextRelink)
+
+ hi clear
+ let hlContextAfterClear = execute("verbose hi Context")
+ call assert_match(patContextDefault, hlContextAfterClear)
+
+ delfunc FuncContextDefault
+ delfunc FuncContextRelink
+ call delete(scriptContextDefault)
+ call delete(scriptContextRelink)
+endfunc
+
+func Test_highlight_default_colorscheme_restores_links()
+ hi link TestLink Identifier
+ hi TestHi ctermbg=red
+
+ let hlTestLinkPre = HighlightArgs('TestLink')
+ let hlTestHiPre = HighlightArgs('TestHi')
+
+ " Test colorscheme
+ hi clear
+ if exists('syntax_on')
+ syntax reset
+ endif
+ let g:colors_name = 'test'
+ hi link TestLink ErrorMsg
+ hi TestHi ctermbg=green
+
+ " Restore default highlighting
+ colorscheme default
+ " 'default' should work no matter if highlight group was cleared
+ hi def link TestLink Identifier
+ hi def TestHi ctermbg=red
+ let hlTestLinkPost = HighlightArgs('TestLink')
+ let hlTestHiPost = HighlightArgs('TestHi')
+ call assert_equal(hlTestLinkPre, hlTestLinkPost)
+ call assert_equal(hlTestHiPre, hlTestHiPost)
+ hi clear
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim
index 9435931d41..3da3648fec 100644
--- a/src/nvim/testdir/test_ins_complete.vim
+++ b/src/nvim/testdir/test_ins_complete.vim
@@ -469,6 +469,34 @@ func Test_pum_with_folds_two_tabs()
call delete('Xpumscript')
endfunc
+" Test for inserting the tag search pattern in insert mode
+func Test_ins_compl_tag_sft()
+ call writefile([
+ \ "!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "first\tXfoo\t/^int first() {}$/",
+ \ "second\tXfoo\t/^int second() {}$/",
+ \ "third\tXfoo\t/^int third() {}$/"],
+ \ 'Xtags')
+ set tags=Xtags
+ let code =<< trim [CODE]
+ int first() {}
+ int second() {}
+ int third() {}
+ [CODE]
+ call writefile(code, 'Xfoo')
+
+ enew
+ set showfulltag
+ exe "normal isec\<C-X>\<C-]>\<C-N>\<CR>"
+ call assert_equal('int second() {}', getline(1))
+ set noshowfulltag
+
+ call delete('Xtags')
+ call delete('Xfoo')
+ set tags&
+ %bwipe!
+endfunc
+
" Test to ensure 'Scanning...' messages are not recorded in messages history
func Test_z1_complete_no_history()
new
diff --git a/src/nvim/testdir/test_listchars.vim b/src/nvim/testdir/test_listchars.vim
index dcc588120c..4cb609aaf0 100644
--- a/src/nvim/testdir/test_listchars.vim
+++ b/src/nvim/testdir/test_listchars.vim
@@ -110,6 +110,35 @@ func Test_listchars()
\ '.....h>-$',
\ 'iii<<<<><<$', '$'], l)
+ " Test lead and trail
+ normal ggdG
+ set listchars=eol:$
+ set listchars+=lead:>,trail:<,space:x
+ set list
+
+ call append(0, [
+ \ ' ffff ',
+ \ ' gg',
+ \ 'h ',
+ \ ' ',
+ \ ' 0 0 ',
+ \ ])
+
+ let expected = [
+ \ '>>>>ffff<<<<$',
+ \ '>>>>>>>>>>gg$',
+ \ 'h<<<<<<<<<<<$',
+ \ '<<<<<<<<<<<<$',
+ \ '>>>>0xx0<<<<$',
+ \ '$'
+ \ ]
+ redraw!
+ for i in range(1, 5)
+ call cursor(i, 1)
+ call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
+ endfor
+
+ call assert_equal(expected, split(execute("%list"), "\n"))
" test nbsp
normal ggdG
diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim
index 8e2a987e74..affb141a26 100644
--- a/src/nvim/testdir/test_listdict.vim
+++ b/src/nvim/testdir/test_listdict.vim
@@ -506,6 +506,15 @@ func Test_dict_lock_extend()
call assert_equal({'a': 99, 'b': 100}, d)
endfunc
+" Cannot use += with a locked dict
+func Test_dict_lock_operator()
+ unlet! d
+ let d = {}
+ lockvar d
+ call assert_fails("let d += {'k' : 10}", 'E741:')
+ unlockvar d
+endfunc
+
" No remove() of write-protected scope-level variable
func! Tfunc(this_is_a_long_parameter_name)
call assert_fails("call remove(a:, 'this_is_a_long_parameter_name')", 'E742')
@@ -709,6 +718,23 @@ func Test_listdict_extend()
call assert_fails("call extend([1, 2], 1)", 'E712:')
call assert_fails("call extend([1, 2], {})", 'E712:')
+
+ " Extend g: dictionary with an invalid variable name
+ call assert_fails("call extend(g:, {'-!' : 10})", 'E461:')
+
+ " Extend a list with itself.
+ let l = [1, 5, 7]
+ call extend(l, l, 0)
+ call assert_equal([1, 5, 7, 1, 5, 7], l)
+ let l = [1, 5, 7]
+ call extend(l, l, 1)
+ call assert_equal([1, 1, 5, 7, 5, 7], l)
+ let l = [1, 5, 7]
+ call extend(l, l, 2)
+ call assert_equal([1, 5, 1, 5, 7, 7], l)
+ let l = [1, 5, 7]
+ call extend(l, l, 3)
+ call assert_equal([1, 5, 7, 1, 5, 7], l)
endfunc
func s:check_scope_dict(x, fixed)
@@ -782,3 +808,40 @@ func Test_scope_dict()
" Test for v:
call s:check_scope_dict('v', v:true)
endfunc
+
+" Test for a null list
+func Test_null_list()
+ let l = v:_null_list
+ call assert_equal('', join(l))
+ call assert_equal(0, len(l))
+ call assert_equal(1, empty(l))
+ call assert_fails('let s = join([1, 2], [])', 'E730:')
+ call assert_equal([], split(v:_null_string))
+ call assert_equal([], l[:2])
+ call assert_true([] == l)
+ call assert_equal('[]', string(l))
+ " call assert_equal(0, sort(l))
+ " call assert_equal(0, sort(l))
+ " call assert_equal(0, uniq(l))
+ let k = [] + l
+ call assert_equal([], k)
+ let k = l + []
+ call assert_equal([], k)
+ call assert_equal(0, len(copy(l)))
+ call assert_equal(0, count(l, 5))
+ call assert_equal([], deepcopy(l))
+ call assert_equal(5, get(l, 2, 5))
+ call assert_equal(-1, index(l, 2, 5))
+ " call assert_equal(0, insert(l, 2, -1))
+ call assert_equal(0, min(l))
+ call assert_equal(0, max(l))
+ " call assert_equal(0, remove(l, 0, 2))
+ call assert_equal([], repeat(l, 2))
+ " call assert_equal(0, reverse(l))
+ " call assert_equal(0, sort(l))
+ call assert_equal('[]', string(l))
+ " call assert_equal(0, extend(l, l, 0))
+ lockvar l
+ call assert_equal(1, islocked('l'))
+ unlockvar l
+endfunc
diff --git a/src/nvim/testdir/test_listlbr.vim b/src/nvim/testdir/test_listlbr.vim
index d619ac0eb5..e0518de3c2 100644
--- a/src/nvim/testdir/test_listlbr.vim
+++ b/src/nvim/testdir/test_listlbr.vim
@@ -1,9 +1,5 @@
" Test for linebreak and list option (non-utf8)
-" Nvim does not allow setting 'encoding', so skip this test.
-finish
-
-set encoding=latin1
scriptencoding latin1
if !exists("+linebreak") || !has("conceal")
@@ -46,6 +42,7 @@ func Test_set_linebreak()
endfunc
func Test_linebreak_with_list()
+ throw 'skipped: Nvim does not support enc=latin1'
call s:test_windows('setl ts=4 sbr=+ list listchars=')
call setline(1, "\tabcdef hijklmn\tpqrstuvwxyz_1060ABCDEFGHIJKLMNOP ")
let lines = s:screen_lines([1, 4], winwidth(0))
@@ -217,6 +214,7 @@ func Test_norm_after_block_visual()
endfunc
func Test_block_replace_after_wrapping()
+ throw 'skipped: Nvim does not support enc=latin1'
call s:test_windows()
call setline(1, repeat("a", 150))
exe "norm! 0yypk147|\<C-V>jr0"
diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim
index c4807797ff..0191dbf33e 100644
--- a/src/nvim/testdir/test_mapping.vim
+++ b/src/nvim/testdir/test_mapping.vim
@@ -427,6 +427,30 @@ func Test_error_in_map_expr()
exe buf .. 'bwipe!'
endfunc
+func Test_expr_map_gets_cursor()
+ new
+ call setline(1, ['one', 'some w!rd'])
+ func StoreColumn()
+ let g:exprLine = line('.')
+ let g:exprCol = col('.')
+ return 'x'
+ endfunc
+ nnoremap <expr> x StoreColumn()
+ 2
+ nmap ! f!<Ignore>x
+ call feedkeys("!", 'xt')
+ call assert_equal('some wrd', getline(2))
+ call assert_equal(2, g:exprLine)
+ call assert_equal(7, g:exprCol)
+
+ bwipe!
+ unlet g:exprLine
+ unlet g:exprCol
+ delfunc StoreColumn
+ nunmap x
+ nunmap !
+endfunc
+
" Test for mapping errors
func Test_map_error()
call assert_fails('unmap', 'E474:')
diff --git a/src/nvim/testdir/test_matchadd_conceal.vim b/src/nvim/testdir/test_matchadd_conceal.vim
index f9e40a9b43..2cbaf5cb76 100644
--- a/src/nvim/testdir/test_matchadd_conceal.vim
+++ b/src/nvim/testdir/test_matchadd_conceal.vim
@@ -59,9 +59,9 @@ func Test_matchadd_and_conceallevel_3()
setlocal filetype=conf
syntax on
- 1put='# This is a Test'
- " 1234567890123456
- let expect = '#ThisisaTest'
+ 1put='# This is a Test $'
+ " 1234567890123
+ let expect = '#ThisisaTest$'
call cursor(1, 1)
call matchadd('Conceal', '\%2l ', 10, -1, {'conceal': 'X'})
@@ -69,22 +69,25 @@ func Test_matchadd_and_conceallevel_3()
let lnum = 2
call assert_equal(expect, Screenline(lnum))
call assert_equal(screenattr(lnum, 1), screenattr(lnum, 2))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
+ call assert_equal(screenattr(lnum, 1), screenattr(lnum, 7))
+ call assert_equal(screenattr(lnum, 1), screenattr(lnum, 10))
+ call assert_equal(screenattr(lnum, 1), screenattr(lnum, 12))
+ call assert_equal(screenattr(lnum, 1), screenattr(lnum, 13))
+ call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 14))
call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 16))
" more matchadd()
- " 1234567890123456
- let expect = '#Thisisa Test'
+ " 12345678901234
+ let expect = '#Thisisa Test$'
call matchadd('ErrorMsg', '\%2l Test', 20, -1, {'conceal': 'X'})
redraw!
call assert_equal(expect, Screenline(lnum))
call assert_equal(screenattr(lnum, 1) , screenattr(lnum, 2))
- call assert_equal(screenattr(lnum, 2) , screenattr(lnum, 7))
+ call assert_equal(screenattr(lnum, 1) , screenattr(lnum, 7))
call assert_notequal(screenattr(lnum, 1) , screenattr(lnum, 10))
- call assert_equal(screenattr(lnum, 10), screenattr(lnum, 12))
+ call assert_equal(screenattr(lnum, 10), screenattr(lnum, 13))
+ call assert_equal(screenattr(lnum, 1), screenattr(lnum, 14))
call assert_notequal(screenattr(lnum, 1) , screenattr(lnum, 16))
call assert_notequal(screenattr(lnum, 10), screenattr(lnum, 16))
@@ -132,15 +135,29 @@ func Test_syn_and_match_conceal()
new
setlocal concealcursor=n conceallevel=1
- 1put='# This is a Test'
- " 1234567890123456
- let expect = '#ZThisZisZaZTest'
+ 1put='# This is a Test '
+ let lnum = 2
call cursor(1, 1)
+
+ " 123456789012345678
+ let expect = '#ZThisZisZaZTestZZ'
call matchadd('Conceal', '\%2l ', 10, -1, {'conceal': 'Z'})
+ syntax match MyConceal /\%2l / conceal containedin=ALL
+ hi MyConceal ctermbg=4 ctermfg=2
+ redraw!
+
+ call assert_equal(expect, Screenline(lnum))
+ call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
+ call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
+
+ syntax clear MyConceal
syntax match MyConceal /\%2l / conceal containedin=ALL cchar=*
redraw!
- let lnum = 2
+
call assert_equal(expect, Screenline(lnum))
call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
@@ -148,8 +165,8 @@ func Test_syn_and_match_conceal()
call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
- " 1234567890123456
- let expect = '#*This*is*a*Test'
+ " 123456789012345678
+ let expect = '#*This*is*a*Test**'
call clearmatches()
redraw!
@@ -160,6 +177,48 @@ func Test_syn_and_match_conceal()
call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
+ " 123456789012345678
+ let expect = '#*ThisXis*a*Test**'
+ call matchadd('Conceal', '\%2l\%7c ', 10, -1, {'conceal': 'X'})
+ redraw!
+
+ call assert_equal(expect, Screenline(lnum))
+ call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
+ call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
+
+ " 123456789012345678
+ let expect = '#*ThisXis*a*Test**'
+ call matchadd('ErrorMsg', '\%2l Test', 20, -1)
+ redraw!
+
+ call assert_equal(expect, Screenline(lnum))
+ call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
+ call assert_notequal(screenattr(lnum, 12), screenattr(lnum, 13))
+ call assert_equal(screenattr(lnum, 13), screenattr(lnum, 16))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 17))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 18))
+ call assert_notequal(screenattr(lnum, 18), screenattr(lnum, 19))
+
+ " 123456789012345678
+ let expect = '# ThisXis a Test'
+ syntax clear MyConceal
+ syntax match MyConceal /\%2l / conceal containedin=ALL
+ redraw!
+
+ call assert_equal(expect, Screenline(lnum))
+ call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
+ call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 12))
+ call assert_notequal(screenattr(lnum, 12), screenattr(lnum, 13))
+ call assert_equal(screenattr(lnum, 13), screenattr(lnum, 16))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 17))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 18))
+ call assert_notequal(screenattr(lnum, 18), screenattr(lnum, 19))
+
syntax off
quit!
endfunc
diff --git a/src/nvim/testdir/test_menu.vim b/src/nvim/testdir/test_menu.vim
index 055d944b15..de6d4aa359 100644
--- a/src/nvim/testdir/test_menu.vim
+++ b/src/nvim/testdir/test_menu.vim
@@ -11,7 +11,13 @@ func Test_load_menu()
call assert_report('error while loading menus: ' . v:exception)
endtry
call assert_match('browse confirm w', execute(':menu File.Save'))
+
+ let v:errmsg = ''
+ doautocmd LoadBufferMenu VimEnter
+ call assert_equal('', v:errmsg)
+
source $VIMRUNTIME/delmenu.vim
+ call assert_equal('', v:errmsg)
endfunc
func Test_translate_menu()
diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim
index 30239a90c2..08586dffe1 100644
--- a/src/nvim/testdir/test_messages.vim
+++ b/src/nvim/testdir/test_messages.vim
@@ -1,5 +1,6 @@
" Tests for :messages, :echomsg, :echoerr
+source check.vim
source shared.vim
func Test_messages()
@@ -31,6 +32,8 @@ func Test_messages()
finally
let &more = oldmore
endtry
+
+ call assert_fails('message 1', 'E474:')
endfunc
" Patch 7.4.1696 defined the "clearmode()" command for clearing the mode
@@ -75,7 +78,7 @@ func Test_echomsg()
endfunc
func Test_echoerr()
- throw 'skipped: Nvim does not support test_ignore_error()'
+ CheckFunction test_ignore_error
call test_ignore_error('IgNoRe')
call assert_equal("\nIgNoRe hello", execute(':echoerr "IgNoRe hello"'))
call assert_equal("\n12345 IgNoRe", execute(':echoerr 12345 "IgNoRe"'))
diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim
index f71da92bf8..8486f3ff68 100644
--- a/src/nvim/testdir/test_mksession.vim
+++ b/src/nvim/testdir/test_mksession.vim
@@ -291,6 +291,60 @@ endfunc
endif
+func Test_mkview_open_folds()
+ enew!
+
+ call append(0, ['a', 'b', 'c'])
+ 1,3fold
+ " zR affects 'foldlevel', make sure the option is applied after the folds
+ " have been recreated.
+ normal zR
+ write! Xtestfile
+
+ call assert_equal(-1, foldclosed(1))
+ call assert_equal(-1, foldclosed(2))
+ call assert_equal(-1, foldclosed(3))
+
+ mkview! Xtestview
+ source Xtestview
+
+ call assert_equal(-1, foldclosed(1))
+ call assert_equal(-1, foldclosed(2))
+ call assert_equal(-1, foldclosed(3))
+
+ call delete('Xtestview')
+ call delete('Xtestfile')
+ %bwipe
+endfunc
+
+func Test_mkview_no_balt()
+ edit Xtestfile1
+ edit Xtestfile2
+
+ mkview! Xtestview
+ bdelete Xtestfile1
+
+ source Xtestview
+ call assert_equal(0, buflisted('Xtestfile1'))
+
+ call delete('Xtestview')
+ %bwipe
+endfunc
+
+func Test_mksession_no_balt()
+ edit Xtestfile1
+ edit Xtestfile2
+
+ bdelete Xtestfile1
+ mksession! Xtestview
+
+ source Xtestview
+ call assert_equal(0, buflisted('Xtestfile1'))
+
+ call delete('Xtestview')
+ %bwipe
+endfunc
+
" Test :mkview with a file argument.
func Test_mkview_file()
" Create a view with line number and a fold.
@@ -373,6 +427,58 @@ func Test_mkview_no_file_name()
%bwipe
endfunc
+func Test_mkview_loadview_jumplist()
+ set viewdir=Xviewdir
+ au BufWinLeave * silent mkview
+ " au BufWinEnter * silent loadview
+
+ edit Xfile1
+ call setline(1, ['a', 'bbbbbbb', 'c'])
+ normal j3l
+ call assert_equal([2, 4], getcurpos()[1:2])
+ write
+
+ edit Xfile2
+ call setline(1, ['d', 'eeeeeee', 'f'])
+ normal j5l
+ call assert_equal([2, 6], getcurpos()[1:2])
+ write
+
+ edit Xfile3
+ call setline(1, ['g', 'h', 'iiiii'])
+ normal jj3l
+ call assert_equal([3, 4], getcurpos()[1:2])
+ write
+
+ " The commented :au above was moved here so that :mkview (on BufWinLeave) can
+ " run before :loadview. This is needed because Nvim's :loadview raises E484 if
+ " the view can't be opened, while Vim's silently fails instead.
+ au BufWinEnter * silent loadview
+
+ edit Xfile1
+ call assert_equal([2, 4], getcurpos()[1:2])
+ edit Xfile2
+ call assert_equal([2, 6], getcurpos()[1:2])
+ edit Xfile3
+ call assert_equal([3, 4], getcurpos()[1:2])
+
+ exe "normal \<C-O>"
+ call assert_equal('Xfile2', expand('%'))
+ call assert_equal([2, 6], getcurpos()[1:2])
+ exe "normal \<C-O>"
+ call assert_equal('Xfile1', expand('%'))
+ call assert_equal([2, 4], getcurpos()[1:2])
+
+ au! BufWinLeave
+ au! BufWinEnter
+ bwipe!
+ call delete('Xviewdir', 'rf')
+ call delete('Xfile1')
+ call delete('Xfile2')
+ call delete('Xfile3')
+ set viewdir&
+endfunc
+
" A clean session (one empty buffer, one window, and one tab) should not
" set any error messages when sourced because no commands should fail.
func Test_mksession_no_errmsg()
@@ -663,4 +769,27 @@ func Test_scrolloff()
set sessionoptions&
endfunc
+func Test_altfile()
+ edit Xone
+ split Xtwo
+ edit Xtwoalt
+ edit #
+ wincmd w
+ edit Xonealt
+ edit #
+ mksession! Xtest_altfile
+ only
+ bwipe Xonealt
+ bwipe Xtwoalt
+ bwipe!
+ source Xtest_altfile
+ call assert_equal('Xone', bufname())
+ call assert_equal('Xonealt', bufname('#'))
+ wincmd w
+ call assert_equal('Xtwo', bufname())
+ call assert_equal('Xtwoalt', bufname('#'))
+ only
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim
index 84d99ebb74..5aef33cb09 100644
--- a/src/nvim/testdir/test_options.vim
+++ b/src/nvim/testdir/test_options.vim
@@ -1,5 +1,7 @@
" Test for options
+source check.vim
+
func Test_whichwrap()
set whichwrap=b,s
call assert_equal('b,s', &whichwrap)
@@ -223,11 +225,18 @@ func Test_set_completion()
" Expand files and directories.
call feedkeys(":set tags=./\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_match('./samples/ ./sautest/ ./screendump.vim ./setup.vim ./shared.vim', @:)
+ call assert_match('./samples/ ./sautest/ ./screendump.vim ./script_util.vim ./setup.vim ./shared.vim', @:)
call feedkeys(":set tags=./\\\\ dif\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"set tags=./\\ diff diffexpr diffopt', @:)
set tags&
+
+ " Expand values for 'filetype'
+ call feedkeys(":set filetype=sshdconfi\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set filetype=sshdconfig', @:)
+ call feedkeys(":set filetype=a\<C-A>\<C-B>\"\<CR>", 'xt')
+ " call assert_equal('"set filetype=' .. getcompletion('a*', 'filetype')->join(), @:)
+ call assert_equal('"set filetype=' .. join(getcompletion('a*', 'filetype')), @:)
endfunc
func Test_set_errors()
@@ -330,12 +339,10 @@ func Test_set_ttytype()
set ttytype=xterm
call assert_equal('xterm', &ttytype)
call assert_equal(&ttytype, &term)
- " "set ttytype=" gives E522 instead of E529
- " in travis on some builds. Why? Catch both for now
try
set ttytype=
call assert_report('set ttytype= did not fail')
- catch /E529\|E522/
+ catch /E529/
endtry
" Some systems accept any terminal name and return dumb settings,
@@ -441,6 +448,36 @@ func Test_backupskip()
endif
endfor
+ " Duplicates from environment variables should be filtered out (option has
+ " P_NODUP). Run this in a separate instance and write v:errors in a file,
+ " so that we see what happens on startup.
+ let after =<< trim [CODE]
+ let bsklist = split(&backupskip, ',')
+ call assert_equal(uniq(copy(bsklist)), bsklist)
+ call writefile(['errors:'] + v:errors, 'Xtestout')
+ qall
+ [CODE]
+ call writefile(after, 'Xafter')
+ " let cmd = GetVimProg() . ' --not-a-term -S Xafter --cmd "set enc=utf8"'
+ let cmd = GetVimProg() . ' -S Xafter --cmd "set enc=utf8"'
+
+ let saveenv = {}
+ for var in ['TMPDIR', 'TMP', 'TEMP']
+ let saveenv[var] = getenv(var)
+ call setenv(var, '/duplicate/path')
+ endfor
+
+ exe 'silent !' . cmd
+ call assert_equal(['errors:'], readfile('Xtestout'))
+
+ " restore environment variables
+ for var in ['TMPDIR', 'TMP', 'TEMP']
+ call setenv(var, saveenv[var])
+ endfor
+
+ call delete('Xtestout')
+ call delete('Xafter')
+
" Duplicates should be filtered out (option has P_NODUP)
let backupskip = &backupskip
set backupskip=
@@ -606,6 +643,49 @@ func Test_opt_boolean()
set number&
endfunc
+func Test_opt_winminheight_term()
+ " See test/functional/legacy/options_spec.lua
+ CheckRunVimInTerminal
+
+ " The tabline should be taken into account.
+ let lines =<< trim END
+ set wmh=0 stal=2
+ below sp | wincmd _
+ below sp | wincmd _
+ below sp | wincmd _
+ below sp
+ END
+ call writefile(lines, 'Xwinminheight')
+ let buf = RunVimInTerminal('-S Xwinminheight', #{rows: 11})
+ call term_sendkeys(buf, ":set wmh=1\n")
+ call WaitForAssert({-> assert_match('E36: Not enough room', term_getline(buf, 11))})
+
+ call StopVimInTerminal(buf)
+ call delete('Xwinminheight')
+endfunc
+
+func Test_opt_winminheight_term_tabs()
+ " See test/functional/legacy/options_spec.lua
+ CheckRunVimInTerminal
+
+ " The tabline should be taken into account.
+ let lines =<< trim END
+ set wmh=0 stal=2
+ split
+ split
+ split
+ split
+ tabnew
+ END
+ call writefile(lines, 'Xwinminheight')
+ let buf = RunVimInTerminal('-S Xwinminheight', #{rows: 11})
+ call term_sendkeys(buf, ":set wmh=1\n")
+ call WaitForAssert({-> assert_match('E36: Not enough room', term_getline(buf, 11))})
+
+ call StopVimInTerminal(buf)
+ call delete('Xwinminheight')
+endfunc
+
" Test for setting option value containing spaces with isfname+=32
func Test_isfname_with_options()
set isfname+=32
@@ -615,4 +695,23 @@ func Test_isfname_with_options()
setlocal keywordprg&
endfunc
+" Test that resetting laststatus does change scroll option
+func Test_opt_reset_scroll()
+ " See test/functional/legacy/options_spec.lua
+ CheckRunVimInTerminal
+ let vimrc =<< trim [CODE]
+ set scroll=2
+ set laststatus=2
+ [CODE]
+ call writefile(vimrc, 'Xscroll')
+ let buf = RunVimInTerminal('-S Xscroll', {'rows': 16, 'cols': 45})
+ call term_sendkeys(buf, ":verbose set scroll?\n")
+ call WaitForAssert({-> assert_match('Last set.*window size', term_getline(buf, 15))})
+ call assert_match('^\s*scroll=7$', term_getline(buf, 14))
+ call StopVimInTerminal(buf)
+
+ " clean up
+ call delete('Xscroll')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim
index 4ee16558a0..9443958984 100644
--- a/src/nvim/testdir/test_popup.vim
+++ b/src/nvim/testdir/test_popup.vim
@@ -871,7 +871,7 @@ func Test_popup_complete_backwards_ctrl_p()
endfunc
fun! Test_complete_o_tab()
- throw 'skipped: Nvim does not support test_override()'
+ CheckFunction test_override
let s:o_char_pressed = 0
fun! s:act_on_text_changed()
diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index 48c0a83053..da949f5940 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -2650,10 +2650,17 @@ func Test_vimgrep()
call XvimgrepTests('l')
endfunc
+func Test_vimgrep_wildcards_expanded_once()
+ new X[id-01] file.txt
+ call setline(1, 'some text to search for')
+ vimgrep text %
+ bwipe!
+endfunc
+
" Test for incsearch highlighting of the :vimgrep pattern
" This test used to cause "E315: ml_get: invalid lnum" errors.
func Test_vimgrep_incsearch()
- throw 'skipped: Nvim does not support test_override()'
+ CheckFunction test_override
enew
set incsearch
call test_override("char_avail", 1)
@@ -3540,7 +3547,7 @@ func Test_lbuffer_crash()
sv Xtest
augroup QF_Test
au!
- au * * bw
+ au QuickFixCmdPre,QuickFixCmdPost,BufEnter,BufLeave * bw
augroup END
lbuffer
augroup QF_Test
@@ -3552,7 +3559,7 @@ endfunc
func Test_lexpr_crash()
augroup QF_Test
au!
- au * * call setloclist(0, [], 'f')
+ au QuickFixCmdPre,QuickFixCmdPost,BufEnter,BufLeave * call setloclist(0, [], 'f')
augroup END
lexpr ""
augroup QF_Test
@@ -3587,7 +3594,7 @@ func Test_lvimgrep_crash()
sv Xtest
augroup QF_Test
au!
- au * * call setloclist(0, [], 'f')
+ au QuickFixCmdPre,QuickFixCmdPost,BufEnter,BufLeave * call setloclist(0, [], 'f')
augroup END
lvimgrep quickfix test_quickfix.vim
augroup QF_Test
@@ -3889,7 +3896,7 @@ func Test_lbuffer_with_bwipe()
new
new
augroup nasty
- au * * bwipe
+ au QuickFixCmdPre,QuickFixCmdPost,BufEnter,BufLeave * bwipe
augroup END
lbuffer
augroup nasty
@@ -3902,9 +3909,9 @@ endfunc
func Xexpr_acmd_freelist(cchar)
call s:setup_commands(a:cchar)
- " This was using freed memory.
+ " This was using freed memory (but with what events?)
augroup nasty
- au * * call g:Xsetlist([], 'f')
+ au QuickFixCmdPre,QuickFixCmdPost,BufEnter,BufLeave * call g:Xsetlist([], 'f')
augroup END
Xexpr "x"
augroup nasty
diff --git a/src/nvim/testdir/test_quotestar.vim b/src/nvim/testdir/test_quotestar.vim
index 77a5153a81..6e6f91362b 100644
--- a/src/nvim/testdir/test_quotestar.vim
+++ b/src/nvim/testdir/test_quotestar.vim
@@ -97,7 +97,7 @@ func Do_test_quotestar_for_x11()
if has('unix') && has('gui') && !has('gui_running')
let @* = ''
- " Running in a terminal and the GUI is avaiable: Tell the server to open
+ " Running in a terminal and the GUI is available: Tell the server to open
" the GUI and check that the remote command still works.
" Need to wait for the GUI to start up, otherwise the send hangs in trying
" to send to the terminal window.
diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim
index 1bb2ee53de..cacdd68d10 100644
--- a/src/nvim/testdir/test_regexp_latin.vim
+++ b/src/nvim/testdir/test_regexp_latin.vim
@@ -1,5 +1,5 @@
" Tests for regexp in latin1 encoding
-set encoding=latin1
+" set encoding=latin1
scriptencoding latin1
func s:equivalence_test()
@@ -22,11 +22,13 @@ func s:equivalence_test()
endfunc
func Test_equivalence_re1()
+ throw 'skipped: Nvim does not support enc=latin1'
set re=1
call s:equivalence_test()
endfunc
func Test_equivalence_re2()
+ throw 'skipped: Nvim does not support enc=latin1'
set re=2
call s:equivalence_test()
endfunc
@@ -39,6 +41,17 @@ func Test_range_with_newline()
bwipe!
endfunc
+func Test_pattern_compile_speed()
+ if !exists('+spellcapcheck') || !has('reltime')
+ return
+ endif
+ let start = reltime()
+ " this used to be very slow, not it should be about a second
+ set spc=\\v(((((Nxxxxxxx&&xxxx){179})+)+)+){179}
+ call assert_inrange(0.01, 10.0, reltimefloat(reltime(start)))
+ set spc=
+endfunc
+
func Test_get_equi_class()
new
" Incomplete equivalence class caused invalid memory access
@@ -87,6 +100,7 @@ func Test_multi_failure()
endfunc
func Test_recursive_addstate()
+ throw 'skipped: TODO: '
" This will call addstate() recursively until it runs into the limit.
let lnum = search('\v((){328}){389}')
call assert_equal(0, lnum)
diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim
index 8d2a768ba0..53069b3d31 100644
--- a/src/nvim/testdir/test_registers.vim
+++ b/src/nvim/testdir/test_registers.vim
@@ -109,6 +109,8 @@ func Test_recording_esc_sequence()
bwipe!
if exists('save_F2')
let &t_F2 = save_F2
+ else
+ set t_F2=
endif
endfunc
diff --git a/src/nvim/testdir/test_rename.vim b/src/nvim/testdir/test_rename.vim
new file mode 100644
index 0000000000..e4228188bd
--- /dev/null
+++ b/src/nvim/testdir/test_rename.vim
@@ -0,0 +1,119 @@
+" Test rename()
+
+func Test_rename_file_to_file()
+ call writefile(['foo'], 'Xrename1')
+
+ call assert_equal(0, rename('Xrename1', 'Xrename2'))
+
+ call assert_equal('', glob('Xrename1'))
+ call assert_equal(['foo'], readfile('Xrename2'))
+
+ " When the destination file already exists, it should be overwritten.
+ call writefile(['foo'], 'Xrename1')
+ call writefile(['bar'], 'Xrename2')
+
+ call assert_equal(0, rename('Xrename1', 'Xrename2'))
+ call assert_equal('', glob('Xrename1'))
+ call assert_equal(['foo'], readfile('Xrename2'))
+
+ call delete('Xrename2')
+endfunc
+
+func Test_rename_file_ignore_case()
+ " With 'fileignorecase', renaming file will go through a temp file
+ " when the source and destination file only differ by case.
+ set fileignorecase
+ call writefile(['foo'], 'Xrename')
+
+ call assert_equal(0, rename('Xrename', 'XRENAME'))
+
+ call assert_equal(['foo'], readfile('XRENAME'))
+
+ set fileignorecase&
+ call delete('XRENAME')
+endfunc
+
+func Test_rename_same_file()
+ call writefile(['foo'], 'Xrename')
+
+ " When the source and destination are the same file, nothing
+ " should be done. The source file should not be deleted.
+ call assert_equal(0, rename('Xrename', 'Xrename'))
+ call assert_equal(['foo'], readfile('Xrename'))
+
+ call assert_equal(0, rename('./Xrename', 'Xrename'))
+ call assert_equal(['foo'], readfile('Xrename'))
+
+ call delete('Xrename')
+endfunc
+
+func Test_rename_dir_to_dir()
+ call mkdir('Xrenamedir1')
+ call writefile(['foo'], 'Xrenamedir1/Xrenamefile')
+
+ call assert_equal(0, rename('Xrenamedir1', 'Xrenamedir2'))
+
+ call assert_equal('', glob('Xrenamedir1'))
+ call assert_equal(['foo'], readfile('Xrenamedir2/Xrenamefile'))
+
+ call delete('Xrenamedir2/Xrenamefile')
+ call delete('Xrenamedir2', 'd')
+endfunc
+
+func Test_rename_same_dir()
+ call mkdir('Xrenamedir')
+ call writefile(['foo'], 'Xrenamedir/Xrenamefile')
+
+ call assert_equal(0, rename('Xrenamedir', 'Xrenamedir'))
+
+ call assert_equal(['foo'], readfile('Xrenamedir/Xrenamefile'))
+
+ call delete('Xrenamedir/Xrenamefile')
+ call delete('Xrenamedir', 'd')
+endfunc
+
+func Test_rename_copy()
+ " Check that when original file can't be deleted, rename()
+ " still succeeds but copies the file.
+ call mkdir('Xrenamedir')
+ call writefile(['foo'], 'Xrenamedir/Xrenamefile')
+ call setfperm('Xrenamedir', 'r-xr-xr-x')
+
+ call assert_equal(0, rename('Xrenamedir/Xrenamefile', 'Xrenamefile'))
+
+ if !has('win32')
+ " On Windows, the source file is removed despite
+ " its directory being made not writable.
+ call assert_equal(['foo'], readfile('Xrenamedir/Xrenamefile'))
+ endif
+ call assert_equal(['foo'], readfile('Xrenamefile'))
+
+ call setfperm('Xrenamedir', 'rwxrwxrwx')
+ call delete('Xrenamedir/Xrenamefile')
+ call delete('Xrenamedir', 'd')
+ call delete('Xrenamefile')
+endfunc
+
+func Test_rename_fails()
+ throw 'skipped: TODO: '
+ call writefile(['foo'], 'Xrenamefile')
+
+ " Can't rename into a non-existing directory.
+ call assert_notequal(0, rename('Xrenamefile', 'Xdoesnotexist/Xrenamefile'))
+
+ " Can't rename a non-existing file.
+ call assert_notequal(0, rename('Xdoesnotexist', 'Xrenamefile2'))
+ call assert_equal('', glob('Xrenamefile2'))
+
+ " When rename() fails, the destination file should not be deleted.
+ call assert_notequal(0, rename('Xdoesnotexist', 'Xrenamefile'))
+ call assert_equal(['foo'], readfile('Xrenamefile'))
+
+ " Can't rename to en empty file name.
+ call assert_notequal(0, rename('Xrenamefile', ''))
+
+ call assert_fails('call rename("Xrenamefile", [])', 'E730')
+ call assert_fails('call rename(0z, "Xrenamefile")', 'E976')
+
+ call delete('Xrenamefile')
+endfunc
diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim
index 0703a6b141..75d42b986b 100644
--- a/src/nvim/testdir/test_search.vim
+++ b/src/nvim/testdir/test_search.vim
@@ -2,13 +2,13 @@
source shared.vim
source screendump.vim
+source check.vim
+" See test/functional/legacy/search_spec.lua
func Test_search_cmdline()
- " See test/functional/legacy/search_spec.lua
- throw 'skipped: Nvim does not support test_override()'
- if !exists('+incsearch')
- return
- endif
+ CheckFunction test_override
+ CheckOption incsearch
+
" need to disable char_avail,
" so that expansion of commandline works
call test_override("char_avail", 1)
@@ -202,12 +202,11 @@ func Test_search_cmdline()
bw!
endfunc
+" See test/functional/legacy/search_spec.lua
func Test_search_cmdline2()
- " See test/functional/legacy/search_spec.lua
- throw 'skipped: Nvim does not support test_override()'
- if !exists('+incsearch')
- return
- endif
+ CheckFunction test_override
+ CheckOption incsearch
+
" need to disable char_avail,
" so that expansion of commandline works
call test_override("char_avail", 1)
@@ -351,7 +350,7 @@ func Test_searchc()
endfunc
func Cmdline3_prep()
- throw 'skipped: Nvim does not support test_override()'
+ CheckFunction test_override
" need to disable char_avail,
" so that expansion of commandline works
call test_override("char_avail", 1)
@@ -361,17 +360,15 @@ func Cmdline3_prep()
endfunc
func Incsearch_cleanup()
- throw 'skipped: Nvim does not support test_override()'
+ CheckFunction test_override
set noincsearch
call test_override("char_avail", 0)
bw!
endfunc
func Test_search_cmdline3()
- throw 'skipped: Nvim does not support test_override()'
- if !exists('+incsearch')
- return
- endif
+ CheckOption incsearch
+
call Cmdline3_prep()
1
" first match
@@ -382,10 +379,8 @@ func Test_search_cmdline3()
endfunc
func Test_search_cmdline3s()
- throw 'skipped: Nvim does not support test_override()'
- if !exists('+incsearch')
- return
- endif
+ CheckOption incsearch
+
call Cmdline3_prep()
1
call feedkeys(":%s/the\<c-l>/xxx\<cr>", 'tx')
@@ -409,10 +404,8 @@ func Test_search_cmdline3s()
endfunc
func Test_search_cmdline3g()
- throw 'skipped: Nvim does not support test_override()'
- if !exists('+incsearch')
- return
- endif
+ CheckOption incsearch
+
call Cmdline3_prep()
1
call feedkeys(":g/the\<c-l>/d\<cr>", 'tx')
@@ -433,10 +426,8 @@ func Test_search_cmdline3g()
endfunc
func Test_search_cmdline3v()
- throw 'skipped: Nvim does not support test_override()'
- if !exists('+incsearch')
- return
- endif
+ CheckOption incsearch
+
call Cmdline3_prep()
1
call feedkeys(":v/the\<c-l>/d\<cr>", 'tx')
@@ -450,12 +441,11 @@ func Test_search_cmdline3v()
call Incsearch_cleanup()
endfunc
+" See test/functional/legacy/search_spec.lua
func Test_search_cmdline4()
- " See test/functional/legacy/search_spec.lua
- throw 'skipped: Nvim does not support test_override()'
- if !exists('+incsearch')
- return
- endif
+ CheckFunction test_override
+ CheckOption incsearch
+
" need to disable char_avail,
" so that expansion of commandline works
call test_override("char_avail", 1)
@@ -487,9 +477,8 @@ func Test_search_cmdline4()
endfunc
func Test_search_cmdline5()
- if !exists('+incsearch')
- return
- endif
+ CheckOption incsearch
+
" Do not call test_override("char_avail", 1) so that <C-g> and <C-t> work
" regardless char_avail.
new
@@ -506,8 +495,48 @@ func Test_search_cmdline5()
bw!
endfunc
+func Test_search_cmdline6()
+ " Test that consecutive matches
+ " are caught by <c-g>/<c-t>
+ CheckFunction test_override
+ CheckOption incsearch
+
+ " need to disable char_avail,
+ " so that expansion of commandline works
+ call test_override("char_avail", 1)
+ new
+ call setline(1, [' bbvimb', ''])
+ set incsearch
+ " first match
+ norm! gg0
+ call feedkeys("/b\<cr>", 'tx')
+ call assert_equal([0,1,2,0], getpos('.'))
+ " second match
+ norm! gg0
+ call feedkeys("/b\<c-g>\<cr>", 'tx')
+ call assert_equal([0,1,3,0], getpos('.'))
+ " third match
+ norm! gg0
+ call feedkeys("/b\<c-g>\<c-g>\<cr>", 'tx')
+ call assert_equal([0,1,7,0], getpos('.'))
+ " first match again
+ norm! gg0
+ call feedkeys("/b\<c-g>\<c-g>\<c-g>\<cr>", 'tx')
+ call assert_equal([0,1,2,0], getpos('.'))
+ set nowrapscan
+ " last match
+ norm! gg0
+ call feedkeys("/b\<c-g>\<c-g>\<c-g>\<cr>", 'tx')
+ call assert_equal([0,1,7,0], getpos('.'))
+ " clean up
+ set wrapscan&vim
+ set noincsearch
+ call test_override("char_avail", 0)
+ bw!
+endfunc
+
func Test_search_cmdline7()
- throw 'skipped: Nvim does not support test_override()'
+ CheckFunction test_override
" Test that pressing <c-g> in an empty command line
" does not move the cursor
if !exists('+incsearch')
@@ -601,26 +630,226 @@ func Test_search_regexp()
enew!
endfunc
-" Test for search('multi-byte char', 'bce')
-func Test_search_multibyte()
- let save_enc = &encoding
- set encoding=utf8
- enew!
- call append('$', 'A')
- call cursor(2, 1)
- call assert_equal(2, search('A', 'bce', line('.')))
- enew!
- let &encoding = save_enc
+func Test_search_cmdline_incsearch_highlight()
+ CheckFunction test_override
+ CheckOption incsearch
+
+ set incsearch hlsearch
+ " need to disable char_avail,
+ " so that expansion of commandline works
+ call test_override("char_avail", 1)
+ new
+ call setline(1, ['aaa 1 the first', ' 2 the second', ' 3 the third'])
+
+ 1
+ call feedkeys("/second\<cr>", 'tx')
+ call assert_equal('second', @/)
+ call assert_equal(' 2 the second', getline('.'))
+
+ " Canceling search won't change @/
+ 1
+ let @/ = 'last pattern'
+ call feedkeys("/third\<C-c>", 'tx')
+ call assert_equal('last pattern', @/)
+ call feedkeys("/third\<Esc>", 'tx')
+ call assert_equal('last pattern', @/)
+ call feedkeys("/3\<bs>\<bs>", 'tx')
+ call assert_equal('last pattern', @/)
+ call feedkeys("/third\<c-g>\<c-t>\<Esc>", 'tx')
+ call assert_equal('last pattern', @/)
+
+ " clean up
+ set noincsearch nohlsearch
+ bw!
endfunc
-" Similar to Test_incsearch_substitute() but with a screendump halfway.
-func Test_incsearch_substitute_dump()
- if !exists('+incsearch')
+func Test_search_cmdline_incsearch_highlight_attr()
+ CheckOption incsearch
+ CheckFeature terminal
+ CheckNotGui
+
+ let h = winheight(0)
+ if h < 3
return
endif
+
+ " Prepare buffer text
+ let lines = ['abb vim vim vi', 'vimvivim']
+ call writefile(lines, 'Xsearch.txt')
+ let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile', 'Xsearch.txt'], {'term_rows': 3})
+
+ call WaitForAssert({-> assert_equal(lines, [term_getline(buf, 1), term_getline(buf, 2)])})
+ " wait for vim to complete initialization
+ call term_wait(buf)
+
+ " Get attr of normal(a0), incsearch(a1), hlsearch(a2) highlight
+ call term_sendkeys(buf, ":set incsearch hlsearch\<cr>")
+ call term_sendkeys(buf, '/b')
+ call term_wait(buf, 200)
+ let screen_line1 = term_scrape(buf, 1)
+ call assert_true(len(screen_line1) > 2)
+ " a0: attr_normal
+ let a0 = screen_line1[0].attr
+ " a1: attr_incsearch
+ let a1 = screen_line1[1].attr
+ " a2: attr_hlsearch
+ let a2 = screen_line1[2].attr
+ call assert_notequal(a0, a1)
+ call assert_notequal(a0, a2)
+ call assert_notequal(a1, a2)
+ call term_sendkeys(buf, "\<cr>gg0")
+
+ " Test incremental highlight search
+ call term_sendkeys(buf, "/vim")
+ call term_wait(buf, 200)
+ " Buffer:
+ " abb vim vim vi
+ " vimvivim
+ " Search: /vim
+ let attr_line1 = [a0,a0,a0,a0,a1,a1,a1,a0,a2,a2,a2,a0,a0,a0]
+ let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2]
+ call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
+ call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr'))
+
+ " Test <C-g>
+ call term_sendkeys(buf, "\<C-g>\<C-g>")
+ call term_wait(buf, 200)
+ let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a2,a2,a2,a0,a0,a0]
+ let attr_line2 = [a1,a1,a1,a0,a0,a2,a2,a2]
+ call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
+ call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr'))
+
+ " Test <C-t>
+ call term_sendkeys(buf, "\<C-t>")
+ call term_wait(buf, 200)
+ let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a1,a1,a1,a0,a0,a0]
+ let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2]
+ call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
+ call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr'))
+
+ " Type Enter and a1(incsearch highlight) should become a2(hlsearch highlight)
+ call term_sendkeys(buf, "\<cr>")
+ call term_wait(buf, 200)
+ let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a2,a2,a2,a0,a0,a0]
+ let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2]
+ call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
+ call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr'))
+
+ " Test nohlsearch. a2(hlsearch highlight) should become a0(normal highlight)
+ call term_sendkeys(buf, ":1\<cr>")
+ call term_sendkeys(buf, ":set nohlsearch\<cr>")
+ call term_sendkeys(buf, "/vim")
+ call term_wait(buf, 200)
+ let attr_line1 = [a0,a0,a0,a0,a1,a1,a1,a0,a0,a0,a0,a0,a0,a0]
+ let attr_line2 = [a0,a0,a0,a0,a0,a0,a0,a0]
+ call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
+ call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr'))
+ call delete('Xsearch.txt')
+
+ call delete('Xsearch.txt')
+ bwipe!
+endfunc
+
+func Test_incsearch_cmdline_modifier()
+ CheckFunction test_override
+ CheckOption incsearch
+
+ call test_override("char_avail", 1)
+ new
+ call setline(1, ['foo'])
+ set incsearch
+ " Test that error E14 does not occur in parsing command modifier.
+ call feedkeys("V:tab", 'tx')
+
+ call Incsearch_cleanup()
+endfunc
+
+func Test_incsearch_scrolling()
if !CanRunVimInTerminal()
throw 'Skipped: cannot make screendumps'
endif
+ call assert_equal(0, &scrolloff)
+ call writefile([
+ \ 'let dots = repeat(".", 120)',
+ \ 'set incsearch cmdheight=2 scrolloff=0',
+ \ 'call setline(1, [dots, dots, dots, "", "target", dots, dots])',
+ \ 'normal gg',
+ \ 'redraw',
+ \ ], 'Xscript')
+ let buf = RunVimInTerminal('-S Xscript', {'rows': 9, 'cols': 70})
+ " Need to send one key at a time to force a redraw
+ call term_sendkeys(buf, '/')
+ sleep 100m
+ call term_sendkeys(buf, 't')
+ sleep 100m
+ call term_sendkeys(buf, 'a')
+ sleep 100m
+ call term_sendkeys(buf, 'r')
+ sleep 100m
+ call term_sendkeys(buf, 'g')
+ call VerifyScreenDump(buf, 'Test_incsearch_scrolling_01', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+ call delete('Xscript')
+endfunc
+
+func Test_incsearch_search_dump()
+ CheckOption incsearch
+ CheckScreendump
+
+ call writefile([
+ \ 'set incsearch hlsearch scrolloff=0',
+ \ 'for n in range(1, 8)',
+ \ ' call setline(n, "foo " . n)',
+ \ 'endfor',
+ \ '3',
+ \ ], 'Xis_search_script')
+ let buf = RunVimInTerminal('-S Xis_search_script', {'rows': 9, 'cols': 70})
+ " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
+ " the 'ambiwidth' check.
+ sleep 100m
+
+ " Need to send one key at a time to force a redraw.
+ call term_sendkeys(buf, '/fo')
+ call VerifyScreenDump(buf, 'Test_incsearch_search_01', {})
+ call term_sendkeys(buf, "\<Esc>")
+ sleep 100m
+
+ call term_sendkeys(buf, '/\v')
+ call VerifyScreenDump(buf, 'Test_incsearch_search_02', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call StopVimInTerminal(buf)
+ call delete('Xis_search_script')
+endfunc
+
+func Test_incsearch_substitute()
+ CheckFunction test_override
+ CheckOption incsearch
+
+ call test_override("char_avail", 1)
+ new
+ set incsearch
+ for n in range(1, 10)
+ call setline(n, 'foo ' . n)
+ endfor
+ 4
+ call feedkeys(":.,.+2s/foo\<BS>o\<BS>o/xxx\<cr>", 'tx')
+ call assert_equal('foo 3', getline(3))
+ call assert_equal('xxx 4', getline(4))
+ call assert_equal('xxx 5', getline(5))
+ call assert_equal('xxx 6', getline(6))
+ call assert_equal('foo 7', getline(7))
+
+ call Incsearch_cleanup()
+endfunc
+
+" Similar to Test_incsearch_substitute() but with a screendump halfway.
+func Test_incsearch_substitute_dump()
+ CheckOption incsearch
+ CheckScreendump
+
call writefile([
\ 'set incsearch hlsearch scrolloff=0',
\ 'for n in range(1, 10)',
@@ -726,14 +955,58 @@ func Test_incsearch_substitute_dump()
call delete('Xis_subst_script')
endfunc
+func Test_incsearch_highlighting()
+ CheckOption incsearch
+ CheckScreendump
+
+ call writefile([
+ \ 'set incsearch hlsearch',
+ \ 'call setline(1, "hello/there")',
+ \ ], 'Xis_subst_hl_script')
+ let buf = RunVimInTerminal('-S Xis_subst_hl_script', {'rows': 4, 'cols': 20})
+ " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
+ " the 'ambiwidth' check.
+ sleep 300m
+
+ " Using a different search delimiter should still highlight matches
+ " that contain a '/'.
+ call term_sendkeys(buf, ":%s;ello/the")
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_15', {})
+ call term_sendkeys(buf, "<Esc>")
+
+ call StopVimInTerminal(buf)
+ call delete('Xis_subst_hl_script')
+endfunc
+
+func Test_incsearch_with_change()
+ CheckFeature timers
+ CheckOption incsearch
+ CheckScreendump
+
+ call writefile([
+ \ 'set incsearch hlsearch scrolloff=0',
+ \ 'call setline(1, ["one", "two ------ X", "three"])',
+ \ 'call timer_start(200, { _ -> setline(2, "x")})',
+ \ ], 'Xis_change_script')
+ let buf = RunVimInTerminal('-S Xis_change_script', {'rows': 9, 'cols': 70})
+ " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
+ " the 'ambiwidth' check.
+ sleep 300m
+
+ " Highlight X, it will be deleted by the timer callback.
+ call term_sendkeys(buf, ':%s/X')
+ call VerifyScreenDump(buf, 'Test_incsearch_change_01', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call StopVimInTerminal(buf)
+ call delete('Xis_change_script')
+endfunc
+
" Similar to Test_incsearch_substitute_dump() for :sort
func Test_incsearch_sort_dump()
- if !exists('+incsearch')
- return
- endif
- if !CanRunVimInTerminal()
- throw 'Skipped: cannot make screendumps'
- endif
+ CheckOption incsearch
+ CheckScreendump
+
call writefile([
\ 'set incsearch hlsearch scrolloff=0',
\ 'call setline(1, ["another one 2", "that one 3", "the one 1"])',
@@ -757,12 +1030,9 @@ endfunc
" Similar to Test_incsearch_substitute_dump() for :vimgrep famiry
func Test_incsearch_vimgrep_dump()
- if !exists('+incsearch')
- return
- endif
- if !CanRunVimInTerminal()
- throw 'Skipped: cannot make screendumps'
- endif
+ CheckOption incsearch
+ CheckScreendump
+
call writefile([
\ 'set incsearch hlsearch scrolloff=0',
\ 'call setline(1, ["another one 2", "that one 3", "the one 1"])',
@@ -798,10 +1068,9 @@ func Test_incsearch_vimgrep_dump()
endfunc
func Test_keep_last_search_pattern()
- throw 'skipped: Nvim does not support test_override()'
- if !exists('+incsearch')
- return
- endif
+ CheckFunction test_override
+ CheckOption incsearch
+
new
call setline(1, ['foo', 'foo', 'foo'])
set incsearch
@@ -820,10 +1089,9 @@ func Test_keep_last_search_pattern()
endfunc
func Test_word_under_cursor_after_match()
- throw 'skipped: Nvim does not support test_override()'
- if !exists('+incsearch')
- return
- endif
+ CheckFunction test_override
+ CheckOption incsearch
+
new
call setline(1, 'foo bar')
set incsearch
@@ -840,10 +1108,9 @@ func Test_word_under_cursor_after_match()
endfunc
func Test_subst_word_under_cursor()
- throw 'skipped: Nvim does not support test_override()'
- if !exists('+incsearch')
- return
- endif
+ CheckFunction test_override
+ CheckOption incsearch
+
new
call setline(1, ['int SomeLongName;', 'for (xxx = 1; xxx < len; ++xxx)'])
set incsearch
@@ -857,132 +1124,8 @@ func Test_subst_word_under_cursor()
set noincsearch
endfunc
-func Test_incsearch_with_change()
- if !has('timers') || !exists('+incsearch') || !CanRunVimInTerminal()
- throw 'Skipped: cannot make screendumps and/or timers feature and/or incsearch option missing'
- endif
-
- call writefile([
- \ 'set incsearch hlsearch scrolloff=0',
- \ 'call setline(1, ["one", "two ------ X", "three"])',
- \ 'call timer_start(200, { _ -> setline(2, "x")})',
- \ ], 'Xis_change_script')
- let buf = RunVimInTerminal('-S Xis_change_script', {'rows': 9, 'cols': 70})
- " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
- " the 'ambiwidth' check.
- sleep 300m
-
- " Highlight X, it will be deleted by the timer callback.
- call term_sendkeys(buf, ':%s/X')
- call VerifyScreenDump(buf, 'Test_incsearch_change_01', {})
- call term_sendkeys(buf, "\<Esc>")
-
- call StopVimInTerminal(buf)
- call delete('Xis_change_script')
-endfunc
-
-func Test_incsearch_cmdline_modifier()
- throw 'skipped: Nvim does not support test_override()'
- if !exists('+incsearch')
- return
- endif
- call test_override("char_avail", 1)
- new
- call setline(1, ['foo'])
- set incsearch
- " Test that error E14 does not occur in parsing command modifier.
- call feedkeys("V:tab", 'tx')
-
- call Incsearch_cleanup()
-endfunc
-
-func Test_incsearch_scrolling()
- if !CanRunVimInTerminal()
- return
- endif
- call assert_equal(0, &scrolloff)
- call writefile([
- \ 'let dots = repeat(".", 120)',
- \ 'set incsearch cmdheight=2 scrolloff=0',
- \ 'call setline(1, [dots, dots, dots, "", "target", dots, dots])',
- \ 'normal gg',
- \ 'redraw',
- \ ], 'Xscript')
- let buf = RunVimInTerminal('-S Xscript', {'rows': 9, 'cols': 70})
- " Need to send one key at a time to force a redraw
- call term_sendkeys(buf, '/')
- sleep 100m
- call term_sendkeys(buf, 't')
- sleep 100m
- call term_sendkeys(buf, 'a')
- sleep 100m
- call term_sendkeys(buf, 'r')
- sleep 100m
- call term_sendkeys(buf, 'g')
- call VerifyScreenDump(buf, 'Test_incsearch_scrolling_01', {})
-
- call term_sendkeys(buf, "\<Esc>")
- call StopVimInTerminal(buf)
- call delete('Xscript')
-endfunc
-
-func Test_incsearch_search_dump()
- if !exists('+incsearch')
- return
- endif
- if !CanRunVimInTerminal()
- return
- endif
- call writefile([
- \ 'set incsearch hlsearch scrolloff=0',
- \ 'for n in range(1, 8)',
- \ ' call setline(n, "foo " . n)',
- \ 'endfor',
- \ '3',
- \ ], 'Xis_search_script')
- let buf = RunVimInTerminal('-S Xis_search_script', {'rows': 9, 'cols': 70})
- " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
- " the 'ambiwidth' check.
- sleep 100m
-
- " Need to send one key at a time to force a redraw.
- call term_sendkeys(buf, '/fo')
- call VerifyScreenDump(buf, 'Test_incsearch_search_01', {})
- call term_sendkeys(buf, "\<Esc>")
- sleep 100m
-
- call term_sendkeys(buf, '/\v')
- call VerifyScreenDump(buf, 'Test_incsearch_search_02', {})
- call term_sendkeys(buf, "\<Esc>")
-
- call StopVimInTerminal(buf)
- call delete('Xis_search_script')
-endfunc
-
-func Test_incsearch_substitute()
- throw 'skipped: Nvim does not support test_override()'
- if !exists('+incsearch')
- return
- endif
- call test_override("char_avail", 1)
- new
- set incsearch
- for n in range(1, 10)
- call setline(n, 'foo ' . n)
- endfor
- 4
- call feedkeys(":.,.+2s/foo\<BS>o\<BS>o/xxx\<cr>", 'tx')
- call assert_equal('foo 3', getline(3))
- call assert_equal('xxx 4', getline(4))
- call assert_equal('xxx 5', getline(5))
- call assert_equal('xxx 6', getline(6))
- call assert_equal('foo 7', getline(7))
-
- call Incsearch_cleanup()
-endfunc
-
func Test_incsearch_substitute_long_line()
- throw 'skipped: Nvim does not support test_override()'
+ CheckFunction test_override
new
call test_override("char_avail", 1)
set incsearch
@@ -997,9 +1140,8 @@ func Test_incsearch_substitute_long_line()
endfunc
func Test_search_undefined_behaviour()
- if !has("terminal")
- return
- endif
+ CheckFeature terminal
+
let h = winheight(0)
if h < 3
return
@@ -1015,6 +1157,18 @@ func Test_search_undefined_behaviour2()
call search("\%UC0000000")
endfunc
+" Test for search('multi-byte char', 'bce')
+func Test_search_multibyte()
+ let save_enc = &encoding
+ set encoding=utf8
+ enew!
+ call append('$', 'A')
+ call cursor(2, 1)
+ call assert_equal(2, search('A', 'bce', line('.')))
+ enew!
+ let &encoding = save_enc
+endfunc
+
" This was causing E874. Also causes an invalid read?
func Test_look_behind()
new
@@ -1053,9 +1207,8 @@ func Test_search_Ctrl_L_combining()
" ' ̇' U+0307 Dec:775 COMBINING DOT ABOVE &#x307; /\%u307\Z "\u0307"
" ' ̣' U+0323 Dec:803 COMBINING DOT BELOW &#x323; /\%u323 "\u0323"
" Those should also appear on the commandline
- if !exists('+incsearch')
- return
- endif
+ CheckOption incsearch
+
call Cmdline3_prep()
1
let bufcontent = ['', 'Miạ̀́̇m']
@@ -1104,10 +1257,9 @@ func Test_one_error_msg()
endfunc
func Test_incsearch_add_char_under_cursor()
- throw 'skipped: Nvim does not support test_override()'
- if !exists('+incsearch')
- return
- endif
+ CheckFunction test_override
+ CheckOption incsearch
+
set incsearch
new
call setline(1, ['find match', 'anything'])
@@ -1192,4 +1344,36 @@ func Test_search_smartcase_utf8()
close!
endfunc
+func Test_incsearch_highlighting_newline()
+ CheckRunVimInTerminal
+ CheckOption incsearch
+ CheckScreendump
+ new
+ call test_override("char_avail", 1)
+
+ let commands =<< trim [CODE]
+ set incsearch nohls
+ call setline(1, ['test', 'xxx'])
+ [CODE]
+ call writefile(commands, 'Xincsearch_nl')
+ let buf = RunVimInTerminal('-S Xincsearch_nl', {'rows': 5, 'cols': 10})
+ call term_sendkeys(buf, '/test')
+ call VerifyScreenDump(buf, 'Test_incsearch_newline1', {})
+ " Need to send one key at a time to force a redraw
+ call term_sendkeys(buf, '\n')
+ call VerifyScreenDump(buf, 'Test_incsearch_newline2', {})
+ call term_sendkeys(buf, 'x')
+ call VerifyScreenDump(buf, 'Test_incsearch_newline3', {})
+ call term_sendkeys(buf, 'x')
+ call VerifyScreenDump(buf, 'Test_incsearch_newline4', {})
+ call term_sendkeys(buf, "\<CR>")
+ call VerifyScreenDump(buf, 'Test_incsearch_newline5', {})
+ call StopVimInTerminal(buf)
+
+ " clean up
+ call delete('Xincsearch_nl')
+ call test_override("char_avail", 0)
+ bw
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_shift.vim b/src/nvim/testdir/test_shift.vim
new file mode 100644
index 0000000000..ec357dac88
--- /dev/null
+++ b/src/nvim/testdir/test_shift.vim
@@ -0,0 +1,117 @@
+" Test shifting lines with :> and :<
+
+source check.vim
+
+func Test_ex_shift_right()
+ set shiftwidth=2
+
+ " shift right current line.
+ call setline(1, range(1, 5))
+ 2
+ >
+ 3
+ >>
+ call assert_equal(['1',
+ \ ' 2',
+ \ ' 3',
+ \ '4',
+ \ '5'], getline(1, '$'))
+
+ " shift right with range.
+ call setline(1, range(1, 4))
+ 2,3>>
+ call assert_equal(['1',
+ \ ' 2',
+ \ ' 3',
+ \ '4',
+ \ '5'], getline(1, '$'))
+
+ " shift right with range and count.
+ call setline(1, range(1, 4))
+ 2>3
+ call assert_equal(['1',
+ \ ' 2',
+ \ ' 3',
+ \ ' 4',
+ \ '5'], getline(1, '$'))
+
+ bw!
+ set shiftwidth&
+endfunc
+
+func Test_ex_shift_left()
+ set shiftwidth=2
+
+ call setline(1, range(1, 5))
+ %>>>
+
+ " left shift current line.
+ 2<
+ 3<<
+ 4<<<<<
+ call assert_equal([' 1',
+ \ ' 2',
+ \ ' 3',
+ \ '4',
+ \ ' 5'], getline(1, '$'))
+
+ " shift right with range.
+ call setline(1, range(1, 5))
+ %>>>
+ 2,3<<
+ call assert_equal([' 1',
+ \ ' 2',
+ \ ' 3',
+ \ ' 4',
+ \ ' 5'], getline(1, '$'))
+
+ " shift right with range and count.
+ call setline(1, range(1, 5))
+ %>>>
+ 2<<3
+ call assert_equal([' 1',
+ \ ' 2',
+ \ ' 3',
+ \ ' 4',
+ \ ' 5'], getline(1, '$'))
+
+ bw!
+ set shiftwidth&
+endfunc
+
+func Test_ex_shift_rightleft()
+ CheckFeature rightleft
+
+ set shiftwidth=2 rightleft
+
+ call setline(1, range(1, 4))
+ 2,3<<
+ call assert_equal(['1',
+ \ ' 2',
+ \ ' 3',
+ \ '4'], getline(1, '$'))
+
+ 3,4>
+ call assert_equal(['1',
+ \ ' 2',
+ \ ' 3',
+ \ '4'], getline(1, '$'))
+
+ bw!
+ set rightleft& shiftwidth&
+endfunc
+
+func Test_ex_shift_errors()
+ call assert_fails('><', 'E488:')
+ call assert_fails('<>', 'E488:')
+
+ call assert_fails('>!', 'E477:')
+ call assert_fails('<!', 'E477:')
+
+ " call assert_fails('2,1>', 'E493:')
+ call assert_fails('execute "2,1>"', 'E493:')
+ " call assert_fails('2,1<', 'E493:')
+ call assert_fails('execute "2,1<"', 'E493:')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim
index 4bbd722fdb..9753100375 100644
--- a/src/nvim/testdir/test_signs.vim
+++ b/src/nvim/testdir/test_signs.vim
@@ -1,8 +1,7 @@
" Test for signs
-if !has('signs')
- finish
-endif
+source check.vim
+CheckFeature signs
source screendump.vim
@@ -135,7 +134,7 @@ func Test_sign()
sign define Sign5 text=X\ linehl=Comment
sign undefine Sign5
- sign define Sign5 linehl=Comment text=X\
+ sign define Sign5 linehl=Comment text=X\
sign undefine Sign5
" define sign with backslash
@@ -416,7 +415,7 @@ func Test_sign_funcs()
" Tests for invalid arguments to sign_define()
call assert_fails('call sign_define("sign4", {"text" : "===>"})', 'E239:')
" call assert_fails('call sign_define("sign5", {"text" : ""})', 'E239:')
- call assert_fails('call sign_define([])', 'E730:')
+ call assert_fails('call sign_define({})', 'E731:')
call assert_fails('call sign_define("sign6", [])', 'E715:')
" Tests for sign_getdefined()
@@ -445,7 +444,7 @@ func Test_sign_funcs()
call assert_fails('call sign_place([], "", "mySign", 1)', 'E745:')
call assert_fails('call sign_place(5, "", "mySign", -1)', 'E158:')
call assert_fails('call sign_place(-1, "", "sign1", "Xsign", [])',
- \ 'E474:')
+ \ 'E715:')
call assert_fails('call sign_place(-1, "", "sign1", "Xsign",
\ {"lnum" : 30})', 'E474:')
call assert_fails('call sign_place(10, "", "xsign1x", "Xsign",
@@ -461,11 +460,11 @@ func Test_sign_funcs()
call assert_fails('call sign_place(5, "", "sign1", [], {"lnum" : 10})',
\ 'E158:')
call assert_fails('call sign_place(21, "", "sign1", "Xsign",
- \ {"lnum" : -1})', 'E885:')
+ \ {"lnum" : -1})', 'E474:')
call assert_fails('call sign_place(22, "", "sign1", "Xsign",
- \ {"lnum" : 0})', 'E885:')
+ \ {"lnum" : 0})', 'E474:')
call assert_fails('call sign_place(22, "", "sign1", "Xsign",
- \ {"lnum" : []})', 'E745:')
+ \ {"lnum" : []})', 'E474:')
call assert_equal(-1, sign_place(1, "*", "sign1", "Xsign", {"lnum" : 10}))
" Tests for sign_getplaced()
@@ -505,11 +504,21 @@ func Test_sign_funcs()
\ {'id' : 20, 'buffer' : 200})", 'E158:')
call assert_fails("call sign_unplace('g1', 'mySign')", 'E715:')
+ call sign_unplace('*')
+
+ " Test for modifying a placed sign
+ call assert_equal(15, sign_place(15, '', 'sign1', 'Xsign', {'lnum' : 20}))
+ call assert_equal(15, sign_place(15, '', 'sign2', 'Xsign'))
+ call assert_equal([{'bufnr' : bufnr(''), 'signs' :
+ \ [{'id' : 15, 'group' : '', 'lnum' : 20, 'name' : 'sign2',
+ \ 'priority' : 10}]}],
+ \ sign_getplaced())
+
" Tests for sign_undefine()
call assert_equal(0, sign_undefine("sign1"))
call assert_equal([], sign_getdefined("sign1"))
call assert_fails('call sign_undefine("none")', 'E155:')
- call assert_fails('call sign_undefine([])', 'E730:')
+ call assert_fails('call sign_undefine({})', 'E731:')
" Test for using '.' as the line number for sign_place()
call Sign_define_ignore_error("sign1", attr)
@@ -645,7 +654,7 @@ func Test_sign_group()
call assert_equal([], sign_getplaced(bnum, {'group' : '*'})[0].signs)
" Error case
- call assert_fails("call sign_unplace([])", 'E474:')
+ call assert_fails("call sign_unplace({})", 'E474:')
" Place a sign in the global group and try to delete it using a group
call assert_equal(5, sign_place(5, '', 'sign1', bnum, {'lnum' : 10}))
@@ -1132,7 +1141,7 @@ func Test_sign_unplace()
endfunc
" Tests for auto-generating the sign identifier
-func Test_sign_id_autogen()
+func Test_aaa_sign_id_autogen()
enew | only
call sign_unplace('*')
call sign_undefine()
@@ -1541,7 +1550,7 @@ endfunc
" Tests for memory allocation failures in sign functions
func Test_sign_memfailures()
- throw 'skipped: Nvim does not support test_alloc_fail()'
+ CheckFunction test_alloc_fail
call writefile(repeat(["Sun is shining"], 30), "Xsign")
edit Xsign
@@ -1619,26 +1628,7 @@ func Test_sign_lnum_adjust()
" Delete the line with the sign
call deletebufline('', 4)
let l = sign_getplaced(bufnr(''))
- call assert_equal(4, l[0].signs[0].lnum)
-
- " Undo the delete operation
- undo
- let l = sign_getplaced(bufnr(''))
- call assert_equal(5, l[0].signs[0].lnum)
-
- " Break the undo
- let &undolevels=&undolevels
-
- " Delete few lines at the end of the buffer including the line with the sign
- " Sign line number should not change (as it is placed outside of the buffer)
- call deletebufline('', 3, 6)
- let l = sign_getplaced(bufnr(''))
- call assert_equal(5, l[0].signs[0].lnum)
-
- " Undo the delete operation. Sign should be restored to the previous line
- undo
- let l = sign_getplaced(bufnr(''))
- call assert_equal(5, l[0].signs[0].lnum)
+ call assert_equal(0, len(l[0].signs))
sign unplace * group=*
sign undefine sign1
@@ -1869,3 +1859,121 @@ func Test_sign_numcol()
set number&
enew! | close
endfunc
+
+" Test for managing multiple signs using the sign functions
+func Test_sign_funcs_multi()
+ call writefile(repeat(["Sun is shining"], 30), "Xsign")
+ edit Xsign
+ let bnum = bufnr('')
+
+ " Define multiple signs at once
+ call assert_equal([0, 0, 0, 0], sign_define([
+ \ {'name' : 'sign1', 'text' : '=>', 'linehl' : 'Search',
+ \ 'texthl' : 'Search'},
+ \ {'name' : 'sign2', 'text' : '=>', 'linehl' : 'Search',
+ \ 'texthl' : 'Search'},
+ \ {'name' : 'sign3', 'text' : '=>', 'linehl' : 'Search',
+ \ 'texthl' : 'Search'},
+ \ {'name' : 'sign4', 'text' : '=>', 'linehl' : 'Search',
+ \ 'texthl' : 'Search'}]))
+
+ " Negative cases for sign_define()
+ call assert_equal([], sign_define([]))
+ call assert_equal([-1], sign_define([{}]))
+ call assert_fails('call sign_define([6])', 'E715:')
+ call assert_fails('call sign_define(["abc"])', 'E715:')
+ call assert_fails('call sign_define([[]])', 'E715:')
+
+ " Place multiple signs at once with specific sign identifier
+ let l = sign_placelist([{'id' : 1, 'group' : 'g1', 'name' : 'sign1',
+ \ 'buffer' : 'Xsign', 'lnum' : 11, 'priority' : 50},
+ \ {'id' : 2, 'group' : 'g2', 'name' : 'sign2',
+ \ 'buffer' : 'Xsign', 'lnum' : 11, 'priority' : 100},
+ \ {'id' : 3, 'group' : '', 'name' : 'sign3',
+ \ 'buffer' : 'Xsign', 'lnum' : 11}])
+ call assert_equal([1, 2, 3], l)
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 11,
+ \ 'group' : 'g2', 'priority' : 100},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 11,
+ \ 'group' : 'g1', 'priority' : 50},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 11,
+ \ 'group' : '', 'priority' : 10}], s[0].signs)
+
+ call sign_unplace('*')
+
+ " Place multiple signs at once with auto-generated sign identifier
+ call assert_equal([1, 1, 5], sign_placelist([
+ \ {'group' : 'g1', 'name' : 'sign1',
+ \ 'buffer' : 'Xsign', 'lnum' : 11},
+ \ {'group' : 'g2', 'name' : 'sign2',
+ \ 'buffer' : 'Xsign', 'lnum' : 11},
+ \ {'group' : '', 'name' : 'sign3',
+ \ 'buffer' : 'Xsign', 'lnum' : 11}]))
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 5, 'name' : 'sign3', 'lnum' : 11,
+ \ 'group' : '', 'priority' : 10},
+ \ {'id' : 1, 'name' : 'sign2', 'lnum' : 11,
+ \ 'group' : 'g2', 'priority' : 10},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 11,
+ \ 'group' : 'g1', 'priority' : 10}], s[0].signs)
+
+ " Change an existing sign without specifying the group
+ call assert_equal([5], sign_placelist([
+ \ {'id' : 5, 'name' : 'sign1', 'buffer' : 'Xsign'}]))
+ let s = sign_getplaced('Xsign', {'id' : 5, 'group' : ''})
+ call assert_equal([{'id' : 5, 'name' : 'sign1', 'lnum' : 11,
+ \ 'group' : '', 'priority' : 10}], s[0].signs)
+
+ " Place a sign using '.' as the line number
+ call cursor(23, 1)
+ call assert_equal([7], sign_placelist([
+ \ {'id' : 7, 'name' : 'sign1', 'buffer' : '%', 'lnum' : '.'}]))
+ let s = sign_getplaced('%', {'lnum' : '.'})
+ call assert_equal([{'id' : 7, 'name' : 'sign1', 'lnum' : 23,
+ \ 'group' : '', 'priority' : 10}], s[0].signs)
+
+ " Place sign without a sign name
+ call assert_equal([-1], sign_placelist([{'id' : 10, 'buffer' : 'Xsign',
+ \ 'lnum' : 12, 'group' : ''}]))
+
+ " Place sign without a buffer
+ call assert_equal([-1], sign_placelist([{'id' : 10, 'name' : 'sign1',
+ \ 'lnum' : 12, 'group' : ''}]))
+
+ " Invalid arguments
+ call assert_equal([], sign_placelist([]))
+ call assert_fails('call sign_placelist({})', "E714:")
+ call assert_fails('call sign_placelist([[]])', "E715:")
+ call assert_fails('call sign_placelist(["abc"])', "E715:")
+ call assert_fails('call sign_placelist([100])', "E715:")
+
+ " Unplace multiple signs
+ call assert_equal([0, 0, 0], sign_unplacelist([{'id' : 5},
+ \ {'id' : 1, 'group' : 'g1'}, {'id' : 1, 'group' : 'g2'}]))
+
+ " Invalid arguments
+ call assert_equal([], sign_unplacelist([]))
+ call assert_fails('call sign_unplacelist({})', "E714:")
+ call assert_fails('call sign_unplacelist([[]])', "E715:")
+ call assert_fails('call sign_unplacelist(["abc"])', "E715:")
+ call assert_fails('call sign_unplacelist([100])', "E715:")
+ call assert_fails("call sign_unplacelist([{'id' : -1}])", 'E474')
+
+ call assert_equal([0, 0, 0, 0],
+ \ sign_undefine(['sign1', 'sign2', 'sign3', 'sign4']))
+ call assert_equal([], sign_getdefined())
+
+ " Invalid arguments
+ call assert_equal([], sign_undefine([]))
+ call assert_fails('call sign_undefine([[]])', 'E730:')
+ call assert_fails('call sign_undefine([{}])', 'E731:')
+ call assert_fails('call sign_undefine(["1abc2"])', 'E155:')
+
+ call sign_unplace('*')
+ call sign_undefine()
+ enew!
+ call delete("Xsign")
+endfunc
diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim
index e6ad92f483..e0dc0e0075 100644
--- a/src/nvim/testdir/test_startup.vim
+++ b/src/nvim/testdir/test_startup.vim
@@ -111,10 +111,9 @@ func Test_pack_in_rtp_when_plugins_run()
endfunc
func Test_help_arg()
- if !has('unix') && has('gui')
- " this doesn't work with gvim on MS-Windows
- return
- endif
+ " This does not work with a GUI-only binary, such as on MS-Windows.
+ CheckAnyOf Unix NotGui
+
if RunVim([], [], '--help >Xtestout')
let lines = readfile('Xtestout')
call assert_true(len(lines) > 20)
@@ -412,6 +411,134 @@ func Test_A_F_H_arg()
call delete('Xtestout')
endfunc
+" Test the --echo-wid argument (for GTK GUI only).
+func Test_echo_wid()
+ CheckCanRunGui
+ CheckFeature gui_gtk
+
+ if RunVim([], [], '-g --echo-wid -cq >Xtest_echo_wid')
+ let lines = readfile('Xtest_echo_wid')
+ call assert_equal(1, len(lines))
+ call assert_match('^WID: \d\+$', lines[0])
+ endif
+
+ call delete('Xtest_echo_wid')
+endfunction
+
+" Test the -reverse and +reverse arguments (for GUI only).
+func Test_reverse()
+ CheckCanRunGui
+ CheckAnyOf Feature:gui_gtk Feature:gui_motif Feature:gui_athena
+
+ let after =<< trim [CODE]
+ call writefile([&background], "Xtest_reverse")
+ qall
+ [CODE]
+ if RunVim([], after, '-f -g -reverse')
+ let lines = readfile('Xtest_reverse')
+ call assert_equal(['dark'], lines)
+ endif
+ if RunVim([], after, '-f -g +reverse')
+ let lines = readfile('Xtest_reverse')
+ call assert_equal(['light'], lines)
+ endif
+
+ call delete('Xtest_reverse')
+endfunc
+
+" Test the -background and -foreground arguments (for GUI only).
+func Test_background_foreground()
+ CheckCanRunGui
+ CheckAnyOf Feature:gui_gtk Feature:gui_motif Feature:gui_athena
+
+ " Is there a better way to check the effect of -background & -foreground
+ " other than merely looking at &background (dark or light)?
+ let after =<< trim [CODE]
+ call writefile([&background], "Xtest_fg_bg")
+ qall
+ [CODE]
+ if RunVim([], after, '-f -g -background darkred -foreground yellow')
+ let lines = readfile('Xtest_fg_bg')
+ call assert_equal(['dark'], lines)
+ endif
+ if RunVim([], after, '-f -g -background ivory -foreground darkgreen')
+ let lines = readfile('Xtest_fg_bg')
+ call assert_equal(['light'], lines)
+ endif
+
+ call delete('Xtest_fg_bg')
+endfunc
+
+" Test the -font argument (for GUI only).
+func Test_font()
+ CheckCanRunGui
+ CheckNotMSWindows
+
+ if has('gui_gtk')
+ let font = 'Courier 14'
+ elseif has('gui_motif') || has('gui_athena')
+ let font = '-misc-fixed-bold-*'
+ else
+ throw 'Skipped: test does not set a valid font for this GUI'
+ endif
+
+ let after =<< trim [CODE]
+ call writefile([&guifont], "Xtest_font")
+ qall
+ [CODE]
+
+ if RunVim([], after, '--nofork -g -font "' .. font .. '"')
+ let lines = readfile('Xtest_font')
+ call assert_equal([font], lines)
+ endif
+
+ call delete('Xtest_font')
+endfunc
+
+" Test the -geometry argument (for GUI only).
+func Test_geometry()
+ CheckCanRunGui
+ CheckAnyOf Feature:gui_gtk Feature:gui_motif Feature:gui_athena
+
+ if has('gui_motif') || has('gui_athena')
+ " FIXME: With GUI Athena or Motif, the value of getwinposx(),
+ " getwinposy() and getwinpos() do not match exactly the
+ " value given in -geometry. Why?
+ " So only check &columns and &lines for those GUIs.
+ let after =<< trim [CODE]
+ call writefile([&columns, &lines], "Xtest_geometry")
+ qall
+ [CODE]
+ if RunVim([], after, '-f -g -geometry 31x13+41+43')
+ let lines = readfile('Xtest_geometry')
+ call assert_equal(['31', '13'], lines)
+ endif
+ else
+ let after =<< trim [CODE]
+ call writefile([&columns, &lines, getwinposx(), getwinposy(), string(getwinpos())], "Xtest_geometry")
+ qall
+ [CODE]
+ if RunVim([], after, '-f -g -geometry 31x13+41+43')
+ let lines = readfile('Xtest_geometry')
+ call assert_equal(['31', '13', '41', '43', '[41, 43]'], lines)
+ endif
+ endif
+
+ call delete('Xtest_geometry')
+endfunc
+
+" Test the -iconic argument (for GUI only).
+func Test_iconic()
+ CheckCanRunGui
+ CheckAnyOf Feature:gui_gtk Feature:gui_motif Feature:gui_athena
+
+ call RunVim([], [], '-f -g -iconic -cq')
+
+ " TODO: currently only start vim iconified, but does not
+ " check that vim is iconified. How could this be checked?
+endfunc
+
+
func Test_invalid_args()
if !has('unix') || has('gui_running')
" can't get output of Vim.
@@ -687,6 +814,34 @@ func Test_v_argv()
call assert_equal(['arg1', '--cmd', 'echo v:argv', '--cmd', 'q'']'], list[idx:])
endfunc
+" Test for the '-t' option to jump to a tag
+func Test_t_arg()
+ let before =<< trim [CODE]
+ set tags=Xtags
+ [CODE]
+ let after =<< trim [CODE]
+ let s = bufname('') .. ':L' .. line('.') .. 'C' .. col('.')
+ call writefile([s], "Xtestout")
+ qall
+ [CODE]
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "first\tXfile1\t/^ \\zsfirst$/",
+ \ "second\tXfile1\t/^ \\zssecond$/",
+ \ "third\tXfile1\t/^ \\zsthird$/"],
+ \ 'Xtags')
+ call writefile([' first', ' second', ' third'], 'Xfile1')
+
+ for t_arg in ['-t second', '-tsecond']
+ if RunVim(before, after, '-t second')
+ call assert_equal(['Xfile1:L2C5'], readfile('Xtestout'), t_arg)
+ call delete('Xtestout')
+ endif
+ endfor
+
+ call delete('Xtags')
+ call delete('Xfile1')
+endfunc
+
" Test the '-T' argument which sets the 'term' option.
func Test_T_arg()
throw 'skipped: Nvim does not support "-T" argument'
@@ -735,6 +890,66 @@ func Test_x_arg()
call delete('Xtest_x_arg')
endfunc
+" Test for --not-a-term avoiding escape codes.
+func Test_not_a_term()
+ CheckUnix
+ CheckNotGui
+
+ if &shellredir =~ '%s'
+ let redir = printf(&shellredir, 'Xvimout')
+ else
+ let redir = &shellredir .. ' Xvimout'
+ endif
+
+ " Without --not-a-term there are a few escape sequences.
+ " This will take 2 seconds because of the missing --not-a-term
+ let cmd = GetVimProg() .. ' --cmd quit ' .. redir
+ exe "silent !" . cmd
+ " call assert_match("\<Esc>", readfile('Xvimout')->join())
+ call assert_match("\<Esc>", join(readfile('Xvimout')))
+ call delete('Xvimout')
+
+ " With --not-a-term there are no escape sequences.
+ let cmd = GetVimProg() .. ' --not-a-term --cmd quit ' .. redir
+ exe "silent !" . cmd
+ " call assert_notmatch("\<Esc>", readfile('Xvimout')->join())
+ call assert_notmatch("\<Esc>", join(readfile('Xvimout')))
+ call delete('Xvimout')
+endfunc
+
+
+" Test for the "-w scriptout" argument
+func Test_w_arg()
+ " Can't catch the output of gvim.
+ CheckNotGui
+
+ call writefile(["iVim Editor\<Esc>:q!\<CR>"], 'Xscriptin', 'b')
+ if RunVim([], [], '-s Xscriptin -w Xscriptout')
+ call assert_equal(["iVim Editor\e:q!\r"], readfile('Xscriptout'))
+ call delete('Xscriptout')
+ endif
+ call delete('Xscriptin')
+
+ " Test for failing to open the script output file. This test works only when
+ " the language is English.
+ if !has('win32') && (v:lang == "C" || v:lang =~ '^[Ee]n')
+ call mkdir("Xdir")
+ let m = system(GetVimCommand() .. " -w Xdir")
+ call assert_equal("Cannot open for script output: \"Xdir\"\n", m)
+ call delete("Xdir", 'rf')
+ endif
+
+ " A number argument sets the 'window' option
+ call writefile(["iwindow \<C-R>=&window\<CR>\<Esc>:wq! Xresult\<CR>"], 'Xscriptin', 'b')
+ for w_arg in ['-w 17', '-w17']
+ if RunVim([], [], '-s Xscriptin ' .. w_arg)
+ call assert_equal(["window 17"], readfile('Xresult'), w_arg)
+ call delete('Xresult')
+ endif
+ endfor
+ call delete('Xscriptin')
+endfunc
+
" Test starting vim with various names: vim, ex, view, evim, etc.
func Test_progname()
CheckUnix
@@ -777,17 +992,12 @@ func Test_progname()
let prognames = ['nvim']
for progname in prognames
- if empty($DISPLAY)
- if progname =~# 'g'
- " Can't run gvim, gview (etc.) if $DISPLAY is not setup.
- continue
- endif
- if has('gui') && (progname ==# 'evim' || progname ==# 'eview')
- " evim or eview will start the GUI if there is gui support.
- " So don't try to start them either if $DISPLAY is not setup.
- continue
- endif
- endif
+ let run_with_gui = (progname =~# 'g') || (has('gui') && (progname ==# 'evim' || progname ==# 'eview'))
+
+ if empty($DISPLAY) && run_with_gui
+ " Can't run gvim, gview (etc.) if $DISPLAY is not setup.
+ continue
+ endif
exe 'silent !ln -s -f ' ..exepath(GetVimProg()) .. ' Xprogname/' .. progname
@@ -801,7 +1011,15 @@ func Test_progname()
if progname =~# 'g' && !has('gui')
call assert_equal("E25: GUI cannot be used: Not enabled at compile time\n", stdout_stderr, progname)
else
- call assert_equal('', stdout_stderr, progname)
+ " GUI motif can output some warnings like this:
+ " Warning:
+ " Name: subMenu
+ " Class: XmCascadeButton
+ " Illegal mnemonic character; Could not convert X KEYSYM to a keycode
+ " So don't check that stderr is empty with GUI Motif.
+ if run_with_gui && !has('gui_motif')
+ call assert_equal('', stdout_stderr, progname)
+ endif
call assert_equal(expectations[progname], readfile('Xprogname_out'), progname)
endif
diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim
index 4e38f7ebd8..f5b6446108 100644
--- a/src/nvim/testdir/test_statusline.vim
+++ b/src/nvim/testdir/test_statusline.vim
@@ -1,12 +1,12 @@
" Test 'statusline'
"
" Not tested yet:
-" %a
" %N
" %T
" %X
source view_util.vim
+source check.vim
source term_util.vim
func s:get_statusline()
@@ -61,7 +61,19 @@ func Test_statusline_will_be_disabled_with_error()
endfunc
func Test_statusline()
- new Xstatusline
+ CheckFeature quickfix
+
+ " %a: Argument list ({current} of {max})
+ set statusline=%a
+ call assert_match('^\s*$', s:get_statusline())
+ arglocal a1 a2
+ rewind
+ call assert_match('^ (1 of 2)\s*$', s:get_statusline())
+ next
+ call assert_match('^ (2 of 2)\s*$', s:get_statusline())
+ e Xstatusline
+ call assert_match('^ ((2) of 2)\s*$', s:get_statusline())
+
only
set laststatus=2
set splitbelow
@@ -428,6 +440,28 @@ func Test_statusline_removed_group()
call delete('XTest_statusline')
endfunc
+func Test_statusline_using_mode()
+ CheckScreendump
+
+ let lines =<< trim END
+ setlocal statusline=-%{mode()}-
+ split
+ setlocal statusline=+%{mode()}+
+ END
+ call writefile(lines, 'XTest_statusline')
+
+ let buf = RunVimInTerminal('-S XTest_statusline', {'rows': 7, 'cols': 50})
+ call VerifyScreenDump(buf, 'Test_statusline_mode_1', {})
+
+ call term_sendkeys(buf, ":")
+ call VerifyScreenDump(buf, 'Test_statusline_mode_2', {})
+
+ " clean up
+ call term_sendkeys(buf, "close\<CR>")
+ call StopVimInTerminal(buf)
+ call delete('XTest_statusline')
+endfunc
+
func Test_statusline_after_split_vsplit()
only
diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim
index 2a27f7a3a1..cc3bfe9f7f 100644
--- a/src/nvim/testdir/test_substitute.vim
+++ b/src/nvim/testdir/test_substitute.vim
@@ -746,3 +746,12 @@ func Test_sub_beyond_end()
call assert_equal('#', getline(1))
bwipe!
endfunc
+
+func Test_submatch_list_concatenate()
+ let pat = 'A\(.\)'
+ let Rep = {-> string([submatch(0, 1)] + [[submatch(1)]])}
+ " call substitute('A1', pat, Rep, '')->assert_equal("[['A1'], ['1']]")
+ call assert_equal(substitute('A1', pat, Rep, ''), "[['A1'], ['1']]")
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim
index 4cf0e983b0..875e23894f 100644
--- a/src/nvim/testdir/test_syntax.vim
+++ b/src/nvim/testdir/test_syntax.vim
@@ -24,13 +24,39 @@ func GetSyntaxItem(pat)
return c
endfunc
+func AssertHighlightGroups(lnum, startcol, expected, trans = 1, msg = "")
+ " Assert that the characters starting at a given (line, col)
+ " sequentially match the expected highlight groups.
+ " If groups are provided as a string, each character is assumed to be a
+ " group and spaces represent no group, useful for visually describing tests.
+ let l:expectedGroups = type(a:expected) == v:t_string
+ "\ ? a:expected->split('\zs')->map({_, v -> trim(v)})
+ \ ? map(split(a:expected, '\zs'), {_, v -> trim(v)})
+ \ : a:expected
+ let l:errors = 0
+ " let l:msg = (a:msg->empty() ? "" : a:msg .. ": ")
+ let l:msg = (empty(a:msg) ? "" : a:msg .. ": ")
+ \ .. "Wrong highlight group at " .. a:lnum .. ","
+
+ " for l:i in range(a:startcol, a:startcol + l:expectedGroups->len() - 1)
+ " let l:errors += synID(a:lnum, l:i, a:trans)
+ " \ ->synIDattr("name")
+ " \ ->assert_equal(l:expectedGroups[l:i - 1],
+ for l:i in range(a:startcol, a:startcol + len(l:expectedGroups) - 1)
+ let l:errors +=
+ \ assert_equal(synIDattr(synID(a:lnum, l:i, a:trans), "name"),
+ \ l:expectedGroups[l:i - 1],
+ \ l:msg .. l:i)
+ endfor
+endfunc
+
func Test_syn_iskeyword()
new
call setline(1, [
\ 'CREATE TABLE FOOBAR(',
\ ' DLTD_BY VARCHAR2(100)',
\ ');',
- \ ''])
+ \ ''])
syntax on
set ft=sql
@@ -521,7 +547,7 @@ func Test_synstack_synIDtrans()
norm f/
call assert_equal(['cComment', 'cCommentStart'], map(synstack(line("."), col(".")), 'synIDattr(v:val, "name")'))
- call assert_equal(['Comment', 'Comment'], map(synstack(line("."), col(".")), 'synIDattr(synIDtrans(v:val), "name")'))
+ call assert_equal(['Comment', 'Comment'], map(synstack(line("."), col(".")), 'synIDattr(synIDtrans(v:val), "name")'))
norm fA
call assert_equal(['cComment'], map(synstack(line("."), col(".")), 'synIDattr(v:val, "name")'))
@@ -707,3 +733,22 @@ func Test_syntax_foldlevel()
quit!
endfunc
+
+func Test_syn_include_contains_TOP()
+ let l:case = "TOP in included syntax means its group list name"
+ new
+ syntax include @INCLUDED syntax/c.vim
+ syntax region FencedCodeBlockC start=/```c/ end=/```/ contains=@INCLUDED
+
+ call setline(1, ['```c', '#if 0', 'int', '#else', 'int', '#endif', '```' ])
+ let l:expected = ["cCppOutIf2"]
+ eval AssertHighlightGroups(3, 1, l:expected, 1)
+ " cCppOutElse has contains=TOP
+ let l:expected = ["cType"]
+ eval AssertHighlightGroups(5, 1, l:expected, 1, l:case)
+ syntax clear
+ bw!
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_system.vim b/src/nvim/testdir/test_system.vim
index 9cf8690d57..6bbe714d19 100644
--- a/src/nvim/testdir/test_system.vim
+++ b/src/nvim/testdir/test_system.vim
@@ -93,7 +93,6 @@ function! Test_system_exmode()
endfunc
func Test_system_with_shell_quote()
- throw 'skipped: enable after porting method patches'
CheckMSWindows
call mkdir('Xdir with spaces', 'p')
@@ -122,7 +121,8 @@ func Test_system_with_shell_quote()
let msg = printf('shell=%s shellxquote=%s', &shell, &shellxquote)
try
- let out = 'echo 123'->system()
+ " let out = 'echo 123'->system()
+ let out = system('echo 123')
catch
call assert_report(printf('%s: %s', msg, v:exception))
continue
diff --git a/src/nvim/testdir/test_tab.vim b/src/nvim/testdir/test_tab.vim
index b847dbd962..b8e8dfe062 100644
--- a/src/nvim/testdir/test_tab.vim
+++ b/src/nvim/testdir/test_tab.vim
@@ -1,3 +1,4 @@
+" Various tests for inserting a Tab.
" Tests for "r<Tab>" with 'smarttab' and 'expandtab' set/not set.
" Also test that dv_ works correctly
@@ -43,3 +44,47 @@ func Test_smarttab()
enew!
set expandtab& smartindent& copyindent& ts& sw& sts&
endfunc
+
+func Test_softtabstop()
+ new
+ set sts=0 sw=0
+ exe "normal ix\<Tab>x\<Esc>"
+ call assert_equal("x\tx", getline(1))
+
+ call setline(1, '')
+ set sts=4
+ exe "normal ix\<Tab>x\<Esc>"
+ call assert_equal("x x", getline(1))
+
+ call setline(1, '')
+ set sts=-1 sw=4
+ exe "normal ix\<Tab>x\<Esc>"
+ call assert_equal("x x", getline(1))
+
+ call setline(1, 'x ')
+ set sts=0 sw=0 backspace=start
+ exe "normal A\<BS>x\<Esc>"
+ call assert_equal("x x", getline(1))
+
+ call setline(1, 'x ')
+ set sts=4
+ exe "normal A\<BS>x\<Esc>"
+ call assert_equal("x x", getline(1))
+
+ call setline(1, 'x ')
+ set sts=-1 sw=4
+ exe "normal A\<BS>x\<Esc>"
+ call assert_equal("x x", getline(1))
+
+ call setline(1, 'x')
+ set sts=-1 sw=0 smarttab
+ exe "normal I\<Tab>\<Esc>"
+ call assert_equal("\tx", getline(1))
+
+ call setline(1, 'x')
+ exe "normal I\<Tab>\<BS>\<Esc>"
+ call assert_equal("x", getline(1))
+
+ set sts=0 sw=0 backspace& nosmarttab
+ bwipe!
+endfunc
diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim
index 2b6a89647e..b261b96c3b 100644
--- a/src/nvim/testdir/test_tabpage.vim
+++ b/src/nvim/testdir/test_tabpage.vim
@@ -142,9 +142,6 @@ endfunc
" Test autocommands
function Test_tabpage_with_autocmd()
- if !has('autocmd')
- return
- endif
command -nargs=1 -bar C :call add(s:li, '=== ' . <q-args> . ' ===')|<args>
augroup TestTabpageGroup
au!
diff --git a/src/nvim/testdir/test_tagfunc.vim b/src/nvim/testdir/test_tagfunc.vim
index 242aa3a235..ffc1d63b90 100644
--- a/src/nvim/testdir/test_tagfunc.vim
+++ b/src/nvim/testdir/test_tagfunc.vim
@@ -43,12 +43,24 @@ func Test_tagfunc()
call assert_equal('one', g:tagfunc_args[0])
call assert_equal('c', g:tagfunc_args[1])
+ let g:tagfunc_args=[]
+ execute "tag /foo$"
+ call assert_equal('foo$', g:tagfunc_args[0])
+ call assert_equal('r', g:tagfunc_args[1])
+
set cpt=t
let g:tagfunc_args=[]
execute "normal! i\<c-n>\<c-y>"
- call assert_equal('ci', g:tagfunc_args[1])
+ call assert_equal('\<\k\k', g:tagfunc_args[0])
+ call assert_equal('cir', g:tagfunc_args[1])
call assert_equal('nothing1', getline('.')[0:7])
+ let g:tagfunc_args=[]
+ execute "normal! ono\<c-n>\<c-n>\<c-y>"
+ call assert_equal('\<no', g:tagfunc_args[0])
+ call assert_equal('cir', g:tagfunc_args[1])
+ call assert_equal('nothing2', getline('.')[0:7])
+
func BadTagFunc1(...)
return 0
endfunc
@@ -81,4 +93,28 @@ func Test_tagfunc()
call delete('Xfile1')
endfunc
+" Test for modifying the tag stack from a tag function and jumping to a tag
+" from a tag function
+func Test_tagfunc_settagstack()
+ func Mytagfunc1(pat, flags, info)
+ call settagstack(1, {'tagname' : 'mytag', 'from' : [0, 10, 1, 0]})
+ return [{'name' : 'mytag', 'filename' : 'Xtest', 'cmd' : '1'}]
+ endfunc
+ set tagfunc=Mytagfunc1
+ call writefile([''], 'Xtest')
+ call assert_fails('tag xyz', 'E986:')
+
+ func Mytagfunc2(pat, flags, info)
+ tag test_tag
+ return [{'name' : 'mytag', 'filename' : 'Xtest', 'cmd' : '1'}]
+ endfunc
+ set tagfunc=Mytagfunc2
+ call assert_fails('tag xyz', 'E986:')
+
+ call delete('Xtest')
+ set tagfunc&
+ delfunc Mytagfunc1
+ delfunc Mytagfunc2
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim
index 7057cdefb2..9f02af7d8e 100644
--- a/src/nvim/testdir/test_tagjump.vim
+++ b/src/nvim/testdir/test_tagjump.vim
@@ -609,6 +609,295 @@ func Test_tagline()
set tags&
endfunc
+" Test for expanding environment variable in a tag file name
+func Test_tag_envvar()
+ call writefile(["Func1\t$FOO\t/^Func1/"], 'Xtags')
+ set tags=Xtags
+
+ let $FOO='TagTestEnv'
+
+ let caught_exception = v:false
+ try
+ tag Func1
+ catch /E429:/
+ call assert_match('E429:.*"TagTestEnv".*', v:exception)
+ let caught_exception = v:true
+ endtry
+ call assert_true(caught_exception)
+
+ set tags&
+ call delete('Xtags')
+ unlet $FOO
+endfunc
+
+" Test for :ptag
+func Test_ptag()
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "second\tXfile1\t2",
+ \ "third\tXfile1\t3",],
+ \ 'Xtags')
+ set tags=Xtags
+ call writefile(['first', 'second', 'third'], 'Xfile1')
+
+ enew | only
+ ptag third
+ call assert_equal(2, winnr())
+ call assert_equal(2, winnr('$'))
+ call assert_equal(1, getwinvar(1, '&previewwindow'))
+ call assert_equal(0, getwinvar(2, '&previewwindow'))
+ wincmd w
+ call assert_equal(3, line('.'))
+
+ " jump to the tag again
+ ptag third
+ call assert_equal(3, line('.'))
+
+ " close the preview window
+ pclose
+ call assert_equal(1, winnr('$'))
+
+ call delete('Xfile1')
+ call delete('Xtags')
+ set tags&
+endfunc
+
+" Tests for guessing the tag location
+func Test_tag_guess()
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "func1\tXfoo\t/^int func1(int x)/",
+ \ "func2\tXfoo\t/^int func2(int y)/",
+ \ "func3\tXfoo\t/^func3/",
+ \ "func4\tXfoo\t/^func4/"],
+ \ 'Xtags')
+ set tags=Xtags
+ let code =<< trim [CODE]
+
+ int FUNC1 (int x) { }
+ int
+ func2 (int y) { }
+ int * func3 () { }
+
+ [CODE]
+ call writefile(code, 'Xfoo')
+
+ let v:statusmsg = ''
+ ta func1
+ call assert_match('E435:', v:statusmsg)
+ call assert_equal(2, line('.'))
+ let v:statusmsg = ''
+ ta func2
+ call assert_match('E435:', v:statusmsg)
+ call assert_equal(4, line('.'))
+ let v:statusmsg = ''
+ ta func3
+ call assert_match('E435:', v:statusmsg)
+ call assert_equal(5, line('.'))
+ call assert_fails('ta func4', 'E434:')
+
+ call delete('Xtags')
+ call delete('Xfoo')
+ set tags&
+endfunc
+
+" Test for an unsorted tags file
+func Test_tag_sort()
+ call writefile([
+ \ "first\tXfoo\t1",
+ \ "ten\tXfoo\t3",
+ \ "six\tXfoo\t2"],
+ \ 'Xtags')
+ set tags=Xtags
+ let code =<< trim [CODE]
+ int first() {}
+ int six() {}
+ int ten() {}
+ [CODE]
+ call writefile(code, 'Xfoo')
+
+ call assert_fails('tag first', 'E432:')
+
+ call delete('Xtags')
+ call delete('Xfoo')
+ set tags&
+ %bwipe
+endfunc
+
+" Test for an unsorted tags file
+func Test_tag_fold()
+ call writefile([
+ \ "!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "!_TAG_FILE_SORTED\t2\t/0=unsorted, 1=sorted, 2=foldcase/",
+ \ "first\tXfoo\t1",
+ \ "second\tXfoo\t2",
+ \ "third\tXfoo\t3"],
+ \ 'Xtags')
+ set tags=Xtags
+ let code =<< trim [CODE]
+ int first() {}
+ int second() {}
+ int third() {}
+ [CODE]
+ call writefile(code, 'Xfoo')
+
+ enew
+ tag second
+ call assert_equal('Xfoo', bufname(''))
+ call assert_equal(2, line('.'))
+
+ call delete('Xtags')
+ call delete('Xfoo')
+ set tags&
+ %bwipe
+endfunc
+
+" Test for the :ltag command
+func Test_ltag()
+ call writefile([
+ \ "!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "first\tXfoo\t1",
+ \ "second\tXfoo\t/^int second() {}$/",
+ \ "third\tXfoo\t3"],
+ \ 'Xtags')
+ set tags=Xtags
+ let code =<< trim [CODE]
+ int first() {}
+ int second() {}
+ int third() {}
+ [CODE]
+ call writefile(code, 'Xfoo')
+
+ enew
+ call setloclist(0, [], 'f')
+ ltag third
+ call assert_equal('Xfoo', bufname(''))
+ call assert_equal(3, line('.'))
+ call assert_equal([{'lnum': 3, 'bufnr': bufnr('Xfoo'), 'col': 0,
+ \ 'pattern': '', 'valid': 1, 'vcol': 0, 'nr': 0, 'type': '',
+ \ 'module': '', 'text': 'third'}], getloclist(0))
+
+ ltag second
+ call assert_equal(2, line('.'))
+ call assert_equal([{'lnum': 0, 'bufnr': bufnr('Xfoo'), 'col': 0,
+ \ 'pattern': '^\Vint second() {}\$', 'valid': 1, 'vcol': 0, 'nr': 0,
+ \ 'type': '', 'module': '', 'text': 'second'}], getloclist(0))
+
+ call delete('Xtags')
+ call delete('Xfoo')
+ set tags&
+ %bwipe
+endfunc
+
+" Test for setting the last search pattern to the tag search pattern
+" when cpoptions has 't'
+func Test_tag_last_search_pat()
+ call writefile([
+ \ "!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "first\tXfoo\t/^int first() {}/",
+ \ "second\tXfoo\t/^int second() {}/",
+ \ "third\tXfoo\t/^int third() {}/"],
+ \ 'Xtags')
+ set tags=Xtags
+ let code =<< trim [CODE]
+ int first() {}
+ int second() {}
+ int third() {}
+ [CODE]
+ call writefile(code, 'Xfoo')
+
+ enew
+ let save_cpo = &cpo
+ set cpo+=t
+ let @/ = ''
+ tag second
+ call assert_equal('^int second() {}', @/)
+ let &cpo = save_cpo
+
+ call delete('Xtags')
+ call delete('Xfoo')
+ set tags&
+ %bwipe
+endfunc
+
+" Test for jumping to a tag when the tag stack is full
+func Test_tag_stack_full()
+ let l = []
+ for i in range(10, 31)
+ let l += ["var" .. i .. "\tXfoo\t/^int var" .. i .. ";$/"]
+ endfor
+ call writefile(l, 'Xtags')
+ set tags=Xtags
+
+ let l = []
+ for i in range(10, 31)
+ let l += ["int var" .. i .. ";"]
+ endfor
+ call writefile(l, 'Xfoo')
+
+ enew
+ for i in range(10, 30)
+ exe "tag var" .. i
+ endfor
+ let l = gettagstack()
+ call assert_equal(20, l.length)
+ call assert_equal('var11', l.items[0].tagname)
+ tag var31
+ let l = gettagstack()
+ call assert_equal('var12', l.items[0].tagname)
+ call assert_equal('var31', l.items[19].tagname)
+
+ " Jump from the top of the stack
+ call assert_fails('tag', 'E556:')
+
+ " Pop from an unsaved buffer
+ enew!
+ call append(1, "sample text")
+ call assert_fails('pop', 'E37:')
+ call assert_equal(21, gettagstack().curidx)
+ enew!
+
+ " Pop all the entries in the tag stack
+ call assert_fails('30pop', 'E555:')
+
+ " Pop the tag stack when it is empty
+ call settagstack(1, {'items' : []})
+ call assert_fails('pop', 'E73:')
+
+ call delete('Xtags')
+ call delete('Xfoo')
+ set tags&
+ %bwipe
+endfunc
+
+" Test for browsing multiple matching tags
+func Test_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')
+
+ tag first
+ tlast
+ call assert_equal(3, line('.'))
+ call assert_fails('tnext', 'E428:')
+ tfirst
+ call assert_equal(1, line('.'))
+ call assert_fails('tprev', 'E425:')
+
+ call delete('Xtags')
+ call delete('Xfoo')
+ set tags&
+ %bwipe
+endfunc
+
" Test for the 'taglength' option
func Test_tag_length()
set tags=Xtags
diff --git a/src/nvim/testdir/test_taglist.vim b/src/nvim/testdir/test_taglist.vim
index d4ff42fd68..e830813081 100644
--- a/src/nvim/testdir/test_taglist.vim
+++ b/src/nvim/testdir/test_taglist.vim
@@ -126,3 +126,99 @@ func Test_tagsfile_without_trailing_newline()
call delete('Xtags')
set tags&
endfunc
+
+" Test for ignoring comments in a tags file
+func Test_tagfile_ignore_comments()
+ call writefile([
+ \ "!_TAG_PROGRAM_NAME /Test tags generator/",
+ \ "FBar\tXfoo\t2" .. ';"' .. "\textrafield\tf",
+ \ "!_TAG_FILE_FORMAT 2 /extended format/",
+ \ ], 'Xtags')
+ set tags=Xtags
+
+ let l = taglist('.*')
+ call assert_equal(1, len(l))
+ call assert_equal('FBar', l[0].name)
+
+ set tags&
+ call delete('Xtags')
+endfunc
+
+" Test for using an excmd in a tags file to position the cursor (instead of a
+" search pattern or a line number)
+func Test_tagfile_excmd()
+ call writefile([
+ \ "vFoo\tXfoo\tcall cursor(3, 4)" .. '|;"' .. "\tv",
+ \ ], 'Xtags')
+ set tags=Xtags
+
+ let l = taglist('.*')
+ call assert_equal([{
+ \ 'cmd' : 'call cursor(3, 4)',
+ \ 'static' : 0,
+ \ 'name' : 'vFoo',
+ \ 'kind' : 'v',
+ \ 'filename' : 'Xfoo'}], l)
+
+ set tags&
+ call delete('Xtags')
+endfunc
+
+" Test for duplicate fields in a tag in a tags file
+func Test_duplicate_field()
+ call writefile([
+ \ "vFoo\tXfoo\t4" .. ';"' .. "\ttypename:int\ttypename:int\tv",
+ \ ], 'Xtags')
+ set tags=Xtags
+
+ let l = taglist('.*')
+ call assert_equal([{
+ \ 'cmd' : '4',
+ \ 'static' : 0,
+ \ 'name' : 'vFoo',
+ \ 'kind' : 'v',
+ \ 'typename' : 'int',
+ \ 'filename' : 'Xfoo'}], l)
+
+ set tags&
+ call delete('Xtags')
+endfunc
+
+" Test for tag address with ;
+func Test_tag_addr_with_semicolon()
+ call writefile([
+ \ "Func1\tXfoo\t6;/^Func1/" .. ';"' .. "\tf"
+ \ ], 'Xtags')
+ set tags=Xtags
+
+ let l = taglist('.*')
+ call assert_equal([{
+ \ 'cmd' : '6;/^Func1/',
+ \ 'static' : 0,
+ \ 'name' : 'Func1',
+ \ 'kind' : 'f',
+ \ 'filename' : 'Xfoo'}], l)
+
+ set tags&
+ call delete('Xtags')
+endfunc
+
+" Test for format error in a tags file
+func Test_format_error()
+ call writefile(['vFoo-Xfoo-4'], 'Xtags')
+ set tags=Xtags
+
+ let caught_exception = v:false
+ try
+ let l = taglist('.*')
+ catch /E431:/
+ " test succeeded
+ let caught_exception = v:true
+ catch
+ call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
+ endtry
+ call assert_true(caught_exception)
+
+ set tags&
+ call delete('Xtags')
+endfunc
diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim
index 4af52b536c..29f0433954 100644
--- a/src/nvim/testdir/test_textformat.vim
+++ b/src/nvim/testdir/test_textformat.vim
@@ -891,6 +891,14 @@ func Test_mps()
bwipe!
endfunc
+func Test_empty_matchpairs()
+ split
+ set matchpairs= showmatch
+ call assert_nobeep('call feedkeys("ax\tx\t\<Esc>", "xt")')
+ set matchpairs& noshowmatch
+ bwipe!
+endfunc
+
" Test for ra on multi-byte characters
func Test_ra_multibyte()
new
diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim
index 13971a918d..ceaa5de92b 100644
--- a/src/nvim/testdir/test_timers.vim
+++ b/src/nvim/testdir/test_timers.vim
@@ -317,8 +317,8 @@ endfunc
" Test that the garbage collector isn't triggered if a timer callback invokes
" vgetc().
func Test_nocatch_garbage_collect()
- " skipped: Nvim does not support test_garbagecollect_soon(), test_override()
- return
+ CheckFunction test_garbagecollect_soon
+ CheckFunction test_override
" 'uptimetime. must be bigger than the timer timeout
set ut=200
call test_garbagecollect_soon()
diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim
index 3b66071d6d..54caed3983 100644
--- a/src/nvim/testdir/test_undo.vim
+++ b/src/nvim/testdir/test_undo.vim
@@ -3,6 +3,8 @@
" undo-able pieces. Do that by setting 'undolevels'.
" Also tests :earlier and :later.
+source check.vim
+
func Test_undotree()
new
@@ -135,7 +137,7 @@ func BackOne(expected)
endfunc
func Test_undo_del_chars()
- throw 'skipped: Nvim does not support test_settime()'
+ CheckFunction test_settime
" Setup a buffer without creating undo entries
new
@@ -330,7 +332,7 @@ func Test_insert_expr()
endfunc
func Test_undofile_earlier()
- throw 'skipped: Nvim does not support test_settime()'
+ CheckFunction test_settime
let t0 = localtime() - 43200
call test_settime(t0)
diff --git a/src/nvim/testdir/test_user_func.vim b/src/nvim/testdir/test_user_func.vim
index 67701ee3ca..e9e181ce3d 100644
--- a/src/nvim/testdir/test_user_func.vim
+++ b/src/nvim/testdir/test_user_func.vim
@@ -95,6 +95,59 @@ func Test_user_func()
enew!
endfunc
+func Log(val, base = 10)
+ return log(a:val) / log(a:base)
+endfunc
+
+func Args(mandatory, optional = v:null, ...)
+ return deepcopy(a:)
+endfunc
+
+func Args2(a = 1, b = 2, c = 3)
+ return deepcopy(a:)
+endfunc
+
+func MakeBadFunc()
+ func s:fcn(a, b=1, c)
+ endfunc
+endfunc
+
+func Test_default_arg()
+ call assert_equal(1.0, Log(10))
+ call assert_equal(log(10), Log(10, exp(1)))
+ call assert_fails("call Log(1,2,3)", 'E118')
+
+ let res = Args(1)
+ call assert_equal(res.mandatory, 1)
+ call assert_equal(res.optional, v:null)
+ call assert_equal(res['0'], 0)
+
+ let res = Args(1,2)
+ call assert_equal(res.mandatory, 1)
+ call assert_equal(res.optional, 2)
+ call assert_equal(res['0'], 0)
+
+ let res = Args(1,2,3)
+ call assert_equal(res.mandatory, 1)
+ call assert_equal(res.optional, 2)
+ call assert_equal(res['0'], 1)
+
+ call assert_fails("call MakeBadFunc()", 'E989')
+ call assert_fails("fu F(a=1 ,) | endf", 'E475')
+
+ " Since neovim does not have v:none, the ability to use the default
+ " argument with the intermediate argument set to v:none has been omitted.
+ " Therefore, this test is not performed.
+ " let d = Args2(7, v:none, 9)
+ " call assert_equal([7, 2, 9], [d.a, d.b, d.c])
+
+ call assert_equal("\n"
+ \ .. " function Args2(a = 1, b = 2, c = 3)\n"
+ \ .. "1 return deepcopy(a:)\n"
+ \ .. " endfunction",
+ \ execute('func Args2'))
+endfunc
+
func Test_failed_call_in_try()
try | call UnknownFunc() | catch | endtry
endfunc
diff --git a/src/nvim/testdir/test_utf8.vim b/src/nvim/testdir/test_utf8.vim
index e8161f8fcb..c51fb3a759 100644
--- a/src/nvim/testdir/test_utf8.vim
+++ b/src/nvim/testdir/test_utf8.vim
@@ -84,7 +84,7 @@ func Test_list2str_str2list_utf8()
" Null list is the same as an empty list
call assert_equal('', list2str([]))
- " call assert_equal('', list2str(test_null_list()))
+ call assert_equal('', list2str(v:_null_list))
endfunc
func Test_list2str_str2list_latin1()
diff --git a/src/nvim/testdir/test_vartabs.vim b/src/nvim/testdir/test_vartabs.vim
new file mode 100644
index 0000000000..2fbf130345
--- /dev/null
+++ b/src/nvim/testdir/test_vartabs.vim
@@ -0,0 +1,381 @@
+" Test for variable tabstops
+
+if !has("vartabs")
+ finish
+endif
+
+source view_util.vim
+
+func s:compare_lines(expect, actual)
+ call assert_equal(join(a:expect, "\n"), join(a:actual, "\n"))
+endfunc
+
+func Test_vartabs()
+ new
+ %d
+
+ " Test normal operation of tabstops ...
+ set ts=4
+ call setline(1, join(split('aaaaa', '\zs'), "\t"))
+ retab 8
+ let expect = "a a\<tab>a a\<tab>a"
+ call assert_equal(expect, getline(1))
+
+ " ... and softtabstops
+ set ts=8 sts=6
+ exe "norm! Sb\<tab>b\<tab>b\<tab>b\<tab>b"
+ let expect = "b b\<tab> b\<tab> b\<tab>b"
+ call assert_equal(expect, getline(1))
+
+ " Test variable tabstops.
+ set sts=0 vts=4,8,4,8
+ exe "norm! Sc\<tab>c\<tab>c\<tab>c\<tab>c\<tab>c"
+ retab 8
+ let expect = "c c\<tab> c\<tab>c\<tab>c\<tab>c"
+ call assert_equal(expect, getline(1))
+
+ set et vts=4,8,4,8
+ exe "norm! Sd\<tab>d\<tab>d\<tab>d\<tab>d\<tab>d"
+ let expect = "d d d d d d"
+ call assert_equal(expect, getline(1))
+
+ " Changing ts should have no effect if vts is in use.
+ call cursor(1, 1)
+ set ts=6
+ exe "norm! Se\<tab>e\<tab>e\<tab>e\<tab>e\<tab>e"
+ let expect = "e e e e e e"
+ call assert_equal(expect, getline(1))
+
+ " Clearing vts should revert to using ts.
+ set vts=
+ exe "norm! Sf\<tab>f\<tab>f\<tab>f\<tab>f\<tab>f"
+ let expect = "f f f f f f"
+ call assert_equal(expect, getline(1))
+
+ " Test variable softtabstops.
+ set noet ts=8 vsts=12,2,6
+ exe "norm! Sg\<tab>g\<tab>g\<tab>g\<tab>g\<tab>g"
+ let expect = "g\<tab> g g\<tab> g\<tab> g\<tab>g"
+ call assert_equal(expect, getline(1))
+
+ " Variable tabstops and softtabstops combined.
+ set vsts=6,12,8 vts=4,6,8
+ exe "norm! Sh\<tab>h\<tab>h\<tab>h\<tab>h"
+ let expect = "h\<tab> h\<tab>\<tab>h\<tab>h\<tab>h"
+ call assert_equal(expect, getline(1))
+
+ " Retab with a single value, not using vts.
+ set ts=8 sts=0 vts= vsts=
+ exe "norm! Si\<tab>i\<tab>i\<tab>i\<tab>i"
+ retab 4
+ let expect = "i\<tab>\<tab>i\<tab>\<tab>i\<tab>\<tab>i\<tab>\<tab>i"
+ call assert_equal(expect, getline(1))
+
+ " Retab with a single value, using vts.
+ set ts=8 sts=0 vts=6 vsts=
+ exe "norm! Sj\<tab>j\<tab>j\<tab>j\<tab>j"
+ retab 4
+ let expect = "j\<tab> j\<tab>\<tab>j\<tab> j\<tab>\<tab>j"
+ call assert_equal(expect, getline(1))
+
+ " Retab with multiple values, not using vts.
+ set ts=6 sts=0 vts= vsts=
+ exe "norm! Sk\<tab>k\<tab>k\<tab>k\<tab>k\<tab>k"
+ retab 4,8
+ let expect = "k\<tab> k\<tab>k k\<tab> k\<tab> k"
+ call assert_equal(expect, getline(1))
+
+ " Retab with multiple values, using vts.
+ set ts=8 sts=0 vts=6 vsts=
+ exe "norm! Sl\<tab>l\<tab>l\<tab>l\<tab>l\<tab>l"
+ retab 4,8
+ let expect = "l\<tab> l\<tab>l l\<tab> l\<tab> l"
+ call assert_equal(expect, getline(1))
+
+ " Check that global and local values are set.
+ set ts=4 vts=6 sts=8 vsts=10
+ call assert_equal(&ts, 4)
+ call assert_equal(&vts, '6')
+ call assert_equal(&sts, 8)
+ call assert_equal(&vsts, '10')
+ new
+ call assert_equal(&ts, 4)
+ call assert_equal(&vts, '6')
+ call assert_equal(&sts, 8)
+ call assert_equal(&vsts, '10')
+ bwipeout!
+
+ " Check that local values only are set.
+ setlocal ts=5 vts=7 sts=9 vsts=11
+ call assert_equal(&ts, 5)
+ call assert_equal(&vts, '7')
+ call assert_equal(&sts, 9)
+ call assert_equal(&vsts, '11')
+ new
+ call assert_equal(&ts, 4)
+ call assert_equal(&vts, '6')
+ call assert_equal(&sts, 8)
+ call assert_equal(&vsts, '10')
+ bwipeout!
+
+ " Check that global values only are set.
+ setglobal ts=6 vts=8 sts=10 vsts=12
+ call assert_equal(&ts, 5)
+ call assert_equal(&vts, '7')
+ call assert_equal(&sts, 9)
+ call assert_equal(&vsts, '11')
+ new
+ call assert_equal(&ts, 6)
+ call assert_equal(&vts, '8')
+ call assert_equal(&sts, 10)
+ call assert_equal(&vsts, '12')
+ bwipeout!
+
+ set ts& vts& sts& vsts& et&
+ bwipeout!
+endfunc
+
+func! Test_vartabs_breakindent()
+ if !exists("+breakindent")
+ return
+ endif
+ new
+ %d
+
+ " Test normal operation of tabstops ...
+ set ts=4
+ call setline(1, join(split('aaaaa', '\zs'), "\t"))
+ retab 8
+ let expect = "a a\<tab>a a\<tab>a"
+ call assert_equal(expect, getline(1))
+
+ " ... and softtabstops
+ set ts=8 sts=6
+ exe "norm! Sb\<tab>b\<tab>b\<tab>b\<tab>b"
+ let expect = "b b\<tab> b\<tab> b\<tab>b"
+ call assert_equal(expect, getline(1))
+
+ " Test variable tabstops.
+ set sts=0 vts=4,8,4,8
+ exe "norm! Sc\<tab>c\<tab>c\<tab>c\<tab>c\<tab>c"
+ retab 8
+ let expect = "c c\<tab> c\<tab>c\<tab>c\<tab>c"
+ call assert_equal(expect, getline(1))
+
+ set et vts=4,8,4,8
+ exe "norm! Sd\<tab>d\<tab>d\<tab>d\<tab>d\<tab>d"
+ let expect = "d d d d d d"
+ call assert_equal(expect, getline(1))
+
+ " Changing ts should have no effect if vts is in use.
+ call cursor(1, 1)
+ set ts=6
+ exe "norm! Se\<tab>e\<tab>e\<tab>e\<tab>e\<tab>e"
+ let expect = "e e e e e e"
+ call assert_equal(expect, getline(1))
+
+ " Clearing vts should revert to using ts.
+ set vts=
+ exe "norm! Sf\<tab>f\<tab>f\<tab>f\<tab>f\<tab>f"
+ let expect = "f f f f f f"
+ call assert_equal(expect, getline(1))
+
+ " Test variable softtabstops.
+ set noet ts=8 vsts=12,2,6
+ exe "norm! Sg\<tab>g\<tab>g\<tab>g\<tab>g\<tab>g"
+ let expect = "g\<tab> g g\<tab> g\<tab> g\<tab>g"
+ call assert_equal(expect, getline(1))
+
+ " Variable tabstops and softtabstops combined.
+ set vsts=6,12,8 vts=4,6,8
+ exe "norm! Sh\<tab>h\<tab>h\<tab>h\<tab>h"
+ let expect = "h\<tab> h\<tab>\<tab>h\<tab>h\<tab>h"
+ call assert_equal(expect, getline(1))
+
+ " Retab with a single value, not using vts.
+ set ts=8 sts=0 vts= vsts=
+ exe "norm! Si\<tab>i\<tab>i\<tab>i\<tab>i"
+ retab 4
+ let expect = "i\<tab>\<tab>i\<tab>\<tab>i\<tab>\<tab>i\<tab>\<tab>i"
+ call assert_equal(expect, getline(1))
+
+ " Retab with a single value, using vts.
+ set ts=8 sts=0 vts=6 vsts=
+ exe "norm! Sj\<tab>j\<tab>j\<tab>j\<tab>j"
+ retab 4
+ let expect = "j\<tab> j\<tab>\<tab>j\<tab> j\<tab>\<tab>j"
+ call assert_equal(expect, getline(1))
+
+ " Retab with multiple values, not using vts.
+ set ts=6 sts=0 vts= vsts=
+ exe "norm! Sk\<tab>k\<tab>k\<tab>k\<tab>k\<tab>k"
+ retab 4,8
+ let expect = "k\<tab> k\<tab>k k\<tab> k\<tab> k"
+ call assert_equal(expect, getline(1))
+
+ " Retab with multiple values, using vts.
+ set ts=8 sts=0 vts=6 vsts=
+ exe "norm! Sl\<tab>l\<tab>l\<tab>l\<tab>l\<tab>l"
+ retab 4,8
+ let expect = "l\<tab> l\<tab>l l\<tab> l\<tab> l"
+ call assert_equal(expect, getline(1))
+
+ " Check that global and local values are set.
+ set ts=4 vts=6 sts=8 vsts=10
+ call assert_equal(&ts, 4)
+ call assert_equal(&vts, '6')
+ call assert_equal(&sts, 8)
+ call assert_equal(&vsts, '10')
+ new
+ call assert_equal(&ts, 4)
+ call assert_equal(&vts, '6')
+ call assert_equal(&sts, 8)
+ call assert_equal(&vsts, '10')
+ bwipeout!
+
+ " Check that local values only are set.
+ setlocal ts=5 vts=7 sts=9 vsts=11
+ call assert_equal(&ts, 5)
+ call assert_equal(&vts, '7')
+ call assert_equal(&sts, 9)
+ call assert_equal(&vsts, '11')
+ new
+ call assert_equal(&ts, 4)
+ call assert_equal(&vts, '6')
+ call assert_equal(&sts, 8)
+ call assert_equal(&vsts, '10')
+ bwipeout!
+
+ " Check that global values only are set.
+ setglobal ts=6 vts=8 sts=10 vsts=12
+ call assert_equal(&ts, 5)
+ call assert_equal(&vts, '7')
+ call assert_equal(&sts, 9)
+ call assert_equal(&vsts, '11')
+ new
+ call assert_equal(&ts, 6)
+ call assert_equal(&vts, '8')
+ call assert_equal(&sts, 10)
+ call assert_equal(&vsts, '12')
+ bwipeout!
+
+ bwipeout!
+endfunc
+
+func Test_vartabs_linebreak()
+ if winwidth(0) < 40
+ return
+ endif
+ new
+ 40vnew
+ %d
+ setl linebreak vartabstop=10,20,30,40
+ call setline(1, "\tx\tx\tx\tx")
+
+ let expect = [' x ',
+ \ 'x x ',
+ \ 'x ']
+ let lines = ScreenLines([1, 3], winwidth(0))
+ call s:compare_lines(expect, lines)
+ setl list listchars=tab:>-
+ let expect = ['>---------x>------------------ ',
+ \ 'x>------------------x>------------------',
+ \ 'x ']
+ let lines = ScreenLines([1, 3], winwidth(0))
+ call s:compare_lines(expect, lines)
+ setl linebreak vartabstop=40
+ let expect = ['>---------------------------------------',
+ \ 'x>--------------------------------------',
+ \ 'x>--------------------------------------',
+ \ 'x>--------------------------------------',
+ \ 'x ']
+ let lines = ScreenLines([1, 5], winwidth(0))
+ call s:compare_lines(expect, lines)
+
+ " cleanup
+ bw!
+ bw!
+ set nolist listchars&vim
+endfunc
+
+func Test_vartabs_shiftwidth()
+ "return
+ if winwidth(0) < 40
+ return
+ endif
+ new
+ 40vnew
+ %d
+" setl varsofttabstop=10,20,30,40
+ setl shiftwidth=0 vartabstop=10,20,30,40
+ call setline(1, "x")
+
+ " Check without any change.
+ let expect = ['x ']
+ let lines = ScreenLines(1, winwidth(0))
+ call s:compare_lines(expect, lines)
+ " Test 1:
+ " shiftwidth depends on the indent, first check with cursor at the end of the
+ " line (which is the same as the start of the line, since there is only one
+ " character).
+ norm! $>>
+ let expect1 = [' x ']
+ let lines = ScreenLines(1, winwidth(0))
+ call s:compare_lines(expect1, lines)
+ call assert_equal(10, shiftwidth())
+ call assert_equal(10, shiftwidth(1))
+ call assert_equal(20, shiftwidth(virtcol('.')))
+ norm! $>>
+ let expect2 = [' x ', '~ ']
+ let lines = ScreenLines([1, 2], winwidth(0))
+ call s:compare_lines(expect2, lines)
+ call assert_equal(20, shiftwidth(virtcol('.')-2))
+ call assert_equal(30, shiftwidth(virtcol('.')))
+ norm! $>>
+ let expect3 = [' ', ' x ', '~ ']
+ let lines = ScreenLines([1, 3], winwidth(0))
+ call s:compare_lines(expect3, lines)
+ call assert_equal(30, shiftwidth(virtcol('.')-2))
+ call assert_equal(40, shiftwidth(virtcol('.')))
+ norm! $>>
+ let expect4 = [' ', ' ', ' x ']
+ let lines = ScreenLines([1, 3], winwidth(0))
+ call assert_equal(40, shiftwidth(virtcol('.')))
+ call s:compare_lines(expect4, lines)
+
+ " Test 2: Put the cursor at the first column, result should be the same
+ call setline(1, "x")
+ norm! 0>>
+ let lines = ScreenLines(1, winwidth(0))
+ call s:compare_lines(expect1, lines)
+ norm! 0>>
+ let lines = ScreenLines([1, 2], winwidth(0))
+ call s:compare_lines(expect2, lines)
+ norm! 0>>
+ let lines = ScreenLines([1, 3], winwidth(0))
+ call s:compare_lines(expect3, lines)
+ norm! 0>>
+ let lines = ScreenLines([1, 3], winwidth(0))
+ call s:compare_lines(expect4, lines)
+
+ " cleanup
+ bw!
+ bw!
+endfunc
+
+func Test_vartabs_failures()
+ call assert_fails('set vts=8,')
+ call assert_fails('set vsts=8,')
+ call assert_fails('set vts=8,,8')
+ call assert_fails('set vsts=8,,8')
+ call assert_fails('set vts=8,,8,')
+ call assert_fails('set vsts=8,,8,')
+ call assert_fails('set vts=,8')
+ call assert_fails('set vsts=,8')
+endfunc
+
+func Test_vartabs_reset()
+ set vts=8
+ set all&
+ call assert_equal('', &vts)
+endfunc
diff --git a/src/nvim/testdir/test_version.vim b/src/nvim/testdir/test_version.vim
index 46cf34979f..5fd38f7cdc 100644
--- a/src/nvim/testdir/test_version.vim
+++ b/src/nvim/testdir/test_version.vim
@@ -1,5 +1,8 @@
" Test :version Ex command
+so check.vim
+so shared.vim
+
func Test_version()
" version should always return the same string.
let v1 = execute('version')
@@ -9,4 +12,15 @@ func Test_version()
call assert_match("^\n\nNVIM v[0-9]\\+\\.[0-9]\\+\\.[0-9]\\+.*", v1)
endfunc
+func Test_version_redirect()
+ CheckNotGui
+ CheckCanRunGui
+ CheckUnix
+
+ call RunVim([], [], '--clean -g --version >Xversion 2>&1')
+ call assert_match('Features included', readfile('Xversion')->join())
+
+ call delete('Xversion')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim
index 7f50894f66..73c7960579 100644
--- a/src/nvim/testdir/test_visual.vim
+++ b/src/nvim/testdir/test_visual.vim
@@ -255,7 +255,6 @@ func TriggerTheProblem()
endfunc
func Test_visual_mode_reset()
- set belloff=all
enew
let g:msg = "Everything's fine."
enew
@@ -268,7 +267,6 @@ func Test_visual_mode_reset()
exe "normal! GV:call TriggerTheProblem()\<CR>"
call assert_equal("Everything's fine.", g:msg)
- set belloff&
endfunc
func Test_Visual_word_textobject()
diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim
index 687b1cb989..a522705238 100644
--- a/src/nvim/testdir/test_window_cmd.vim
+++ b/src/nvim/testdir/test_window_cmd.vim
@@ -513,8 +513,8 @@ func Test_window_colon_command()
endfunc
func Test_access_freed_mem()
- " This was accessing freed memory
- au * 0 vs xxx
+ " This was accessing freed memory (but with what events?)
+ au BufEnter,BufLeave,WinEnter,WinLeave 0 vs xxx
arg 0
argadd
all
@@ -550,16 +550,29 @@ endfunc
func Test_winrestcmd()
2split
3vsplit
- let a = winrestcmd()
+ let restcmd = winrestcmd()
call assert_equal(2, winheight(0))
call assert_equal(3, winwidth(0))
wincmd =
call assert_notequal(2, winheight(0))
call assert_notequal(3, winwidth(0))
- exe a
+ exe restcmd
call assert_equal(2, winheight(0))
call assert_equal(3, winwidth(0))
only
+
+ wincmd v
+ wincmd s
+ wincmd v
+ redraw
+ let restcmd = winrestcmd()
+ wincmd _
+ wincmd |
+ exe restcmd
+ redraw
+ call assert_equal(restcmd, winrestcmd())
+
+ only
endfunc
function! Fun_RenewFile()
@@ -808,6 +821,55 @@ func Test_winnr()
only | tabonly
endfunc
+func Test_win_splitmove()
+ edit a
+ leftabove split b
+ leftabove vsplit c
+ leftabove split d
+ call assert_equal(0, win_splitmove(winnr(), winnr('l')))
+ call assert_equal(bufname(winbufnr(1)), 'c')
+ call assert_equal(bufname(winbufnr(2)), 'd')
+ call assert_equal(bufname(winbufnr(3)), 'b')
+ call assert_equal(bufname(winbufnr(4)), 'a')
+ call assert_equal(0, win_splitmove(winnr(), winnr('j'), {'vertical': 1}))
+ call assert_equal(0, win_splitmove(winnr(), winnr('j'), {'vertical': 1}))
+ call assert_equal(bufname(winbufnr(1)), 'c')
+ call assert_equal(bufname(winbufnr(2)), 'b')
+ call assert_equal(bufname(winbufnr(3)), 'd')
+ call assert_equal(bufname(winbufnr(4)), 'a')
+ call assert_equal(0, win_splitmove(winnr(), winnr('k'), {'vertical': 1}))
+ call assert_equal(bufname(winbufnr(1)), 'd')
+ call assert_equal(bufname(winbufnr(2)), 'c')
+ call assert_equal(bufname(winbufnr(3)), 'b')
+ call assert_equal(bufname(winbufnr(4)), 'a')
+ call assert_equal(0, win_splitmove(winnr(), winnr('j'), {'rightbelow': v:true}))
+ call assert_equal(bufname(winbufnr(1)), 'c')
+ call assert_equal(bufname(winbufnr(2)), 'b')
+ call assert_equal(bufname(winbufnr(3)), 'a')
+ call assert_equal(bufname(winbufnr(4)), 'd')
+ only | bd
+
+ call assert_fails('call win_splitmove(winnr(), 123)', 'E957:')
+ call assert_fails('call win_splitmove(123, winnr())', 'E957:')
+ call assert_fails('call win_splitmove(winnr(), winnr())', 'E957:')
+
+ tabnew
+ call assert_fails('call win_splitmove(1, win_getid(1, 1))', 'E957:')
+ tabclose
+endfunc
+
+func Test_floatwin_splitmove()
+ vsplit
+ let win2 = win_getid()
+ let popup_winid = nvim_open_win(0, 0, {'relative': 'win',
+ \ 'row': 3, 'col': 3, 'width': 12, 'height': 3})
+ call assert_fails('call win_splitmove(popup_winid, win2)', 'E957:')
+ call assert_fails('call win_splitmove(win2, popup_winid)', 'E957:')
+
+ call nvim_win_close(popup_winid, 1)
+ bwipe
+endfunc
+
func Test_window_resize()
" Vertical :resize (absolute, relative, min and max size).
vsplit
diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim
index 56031662a3..c62c01d5f3 100644
--- a/src/nvim/testdir/test_writefile.vim
+++ b/src/nvim/testdir/test_writefile.vim
@@ -1,5 +1,8 @@
" Tests for the writefile() function and some :write commands.
+source check.vim
+source term_util.vim
+
func Test_writefile()
let f = tempname()
call writefile(["over","written"], f, "b")
@@ -179,3 +182,120 @@ func Test_writefile_sync_arg()
call writefile(['two'], 'Xtest', 'S')
call delete('Xtest')
endfunc
+
+" Tests for reading and writing files with conversion for Win32.
+func Test_write_file_encoding()
+ CheckMSWindows
+ throw 'skipped: Nvim does not support :w ++enc=cp1251'
+ let save_encoding = &encoding
+ let save_fileencodings = &fileencodings
+ set encoding& fileencodings&
+ let text =<< trim END
+ 1 utf-8 text: Для Vim version 6.2. Последнее изменение: 1970 Jan 01
+ 2 cp1251 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
+ 3 cp866 text: „«ï Vim version 6.2. ®á«¥¤­¥¥ ¨§¬¥­¥­¨¥: 1970 Jan 01
+ END
+ call writefile(text, 'Xfile')
+ edit Xfile
+
+ " write tests:
+ " combine three values for 'encoding' with three values for 'fileencoding'
+ " also write files for read tests
+ call cursor(1, 1)
+ set encoding=utf-8
+ .w! ++enc=utf-8 Xtest
+ .w ++enc=cp1251 >> Xtest
+ .w ++enc=cp866 >> Xtest
+ .w! ++enc=utf-8 Xutf8
+ let expected =<< trim END
+ 1 utf-8 text: Для Vim version 6.2. Последнее изменение: 1970 Jan 01
+ 1 utf-8 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
+ 1 utf-8 text: „«ï Vim version 6.2. ®á«¥¤­¥¥ ¨§¬¥­¥­¨¥: 1970 Jan 01
+ END
+ call assert_equal(expected, readfile('Xtest'))
+
+ call cursor(2, 1)
+ set encoding=cp1251
+ .w! ++enc=utf-8 Xtest
+ .w ++enc=cp1251 >> Xtest
+ .w ++enc=cp866 >> Xtest
+ .w! ++enc=cp1251 Xcp1251
+ let expected =<< trim END
+ 2 cp1251 text: Для Vim version 6.2. Последнее изменение: 1970 Jan 01
+ 2 cp1251 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
+ 2 cp1251 text: „«ï Vim version 6.2. ®á«¥¤­¥¥ ¨§¬¥­¥­¨¥: 1970 Jan 01
+ END
+ call assert_equal(expected, readfile('Xtest'))
+
+ call cursor(3, 1)
+ set encoding=cp866
+ .w! ++enc=utf-8 Xtest
+ .w ++enc=cp1251 >> Xtest
+ .w ++enc=cp866 >> Xtest
+ .w! ++enc=cp866 Xcp866
+ let expected =<< trim END
+ 3 cp866 text: Для Vim version 6.2. Последнее изменение: 1970 Jan 01
+ 3 cp866 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
+ 3 cp866 text: „«ï Vim version 6.2. ®á«¥¤­¥¥ ¨§¬¥­¥­¨¥: 1970 Jan 01
+ END
+ call assert_equal(expected, readfile('Xtest'))
+
+ " read three 'fileencoding's with utf-8 'encoding'
+ set encoding=utf-8 fencs=utf-8,cp1251
+ e Xutf8
+ .w! ++enc=utf-8 Xtest
+ e Xcp1251
+ .w ++enc=utf-8 >> Xtest
+ set fencs=utf-8,cp866
+ e Xcp866
+ .w ++enc=utf-8 >> Xtest
+ let expected =<< trim END
+ 1 utf-8 text: Для Vim version 6.2. Последнее изменение: 1970 Jan 01
+ 2 cp1251 text: Для Vim version 6.2. Последнее изменение: 1970 Jan 01
+ 3 cp866 text: Для Vim version 6.2. Последнее изменение: 1970 Jan 01
+ END
+ call assert_equal(expected, readfile('Xtest'))
+
+ " read three 'fileencoding's with cp1251 'encoding'
+ set encoding=utf-8 fencs=utf-8,cp1251
+ e Xutf8
+ .w! ++enc=cp1251 Xtest
+ e Xcp1251
+ .w ++enc=cp1251 >> Xtest
+ set fencs=utf-8,cp866
+ e Xcp866
+ .w ++enc=cp1251 >> Xtest
+ let expected =<< trim END
+ 1 utf-8 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
+ 2 cp1251 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
+ 3 cp866 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
+ END
+ call assert_equal(expected, readfile('Xtest'))
+
+ " read three 'fileencoding's with cp866 'encoding'
+ set encoding=cp866 fencs=utf-8,cp1251
+ e Xutf8
+ .w! ++enc=cp866 Xtest
+ e Xcp1251
+ .w ++enc=cp866 >> Xtest
+ set fencs=utf-8,cp866
+ e Xcp866
+ .w ++enc=cp866 >> Xtest
+ let expected =<< trim END
+ 1 utf-8 text: „«ï Vim version 6.2. ®á«¥¤­¥¥ ¨§¬¥­¥­¨¥: 1970 Jan 01
+ 2 cp1251 text: „«ï Vim version 6.2. ®á«¥¤­¥¥ ¨§¬¥­¥­¨¥: 1970 Jan 01
+ 3 cp866 text: „«ï Vim version 6.2. ®á«¥¤­¥¥ ¨§¬¥­¥­¨¥: 1970 Jan 01
+ END
+ call assert_equal(expected, readfile('Xtest'))
+
+ call delete('Xfile')
+ call delete('Xtest')
+ call delete('Xutf8')
+ call delete('Xcp1251')
+ call delete('Xcp866')
+ let &encoding = save_encoding
+ let &fileencodings = save_fileencodings
+ %bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index 124f96e039..6705ab98c2 100644
--- a/src/nvim/tui/input.c
+++ b/src/nvim/tui/input.c
@@ -297,7 +297,7 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key)
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Release");
break;
case TERMKEY_MOUSE_UNKNOWN:
- assert(false);
+ abort();
}
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "><%d,%d>", col, row);
@@ -557,8 +557,8 @@ HandleState ut_handle_background_color(TermInput *input)
static void handle_raw_buffer(TermInput *input, bool force)
{
- HandleState is_paste;
- HandleState is_bc;
+ HandleState is_paste = kNotApplicable;
+ HandleState is_bc = kNotApplicable;
do {
if (!force
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index 62d7dc8b18..ed40a64c66 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -55,7 +55,11 @@
#define STARTS_WITH(str, prefix) (strlen(str) >= (sizeof(prefix) - 1) \
&& 0 == memcmp((str), (prefix), sizeof(prefix) - 1))
#define TMUX_WRAP(is_tmux, seq) ((is_tmux) \
- ? "\x1bPtmux;\x1b" seq "\x1b\\" : seq)
+ ? DCS_STR "tmux;\x1b" seq STERM_STR : seq)
+#define SCREEN_TMUX_WRAP(is_screen, is_tmux, seq) \
+ ((is_screen) \
+ ? DCS_STR seq STERM_STR : (is_tmux) \
+ ? DCS_STR "tmux;\x1b" seq STERM_STR : seq)
#define LINUXSET0C "\x1b[?0c"
#define LINUXSET1C "\x1b[?1c"
@@ -297,6 +301,12 @@ static void terminfo_start(UI *ui)
data->invis, sizeof data->invis);
// Set 't_Co' from the result of unibilium & fix_terminfo.
t_colors = unibi_get_num(data->ut, unibi_max_colors);
+ // Ask the terminal to send us the background color.
+ // If get_bg is sent at the same time after enter_ca_mode, tmux will not send
+ // get_bg to the host terminal. To avoid this, send get_bg before
+ // enter_ca_mode.
+ data->input.waiting_for_bg_response = 5;
+ unibi_out_ext(ui, data->unibi_ext.get_bg);
// Enter alternate screen, save title, and clear.
// NOTE: Do this *before* changing terminal settings. #6433
unibi_out(ui, unibi_enter_ca_mode);
@@ -304,9 +314,6 @@ static void terminfo_start(UI *ui)
unibi_out_ext(ui, data->unibi_ext.save_title);
unibi_out(ui, unibi_keypad_xmit);
unibi_out(ui, unibi_clear_screen);
- // Ask the terminal to send us the background color.
- data->input.waiting_for_bg_response = 5;
- unibi_out_ext(ui, data->unibi_ext.get_bg);
// Enable bracketed paste
unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste);
@@ -328,6 +335,7 @@ static void terminfo_start(UI *ui)
uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0);
uv_pipe_open(&data->output_handle.pipe, data->out_fd);
}
+
flush_buf(ui);
}
@@ -1772,8 +1780,10 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
#define XTERM_SETAB_16 \
"\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e39%;m"
- data->unibi_ext.get_bg = (int)unibi_add_ext_str(ut, "ext.get_bg",
- "\x1b]11;?\x07");
+ data->unibi_ext.get_bg =
+ (int)unibi_add_ext_str(ut, "ext.get_bg",
+ SCREEN_TMUX_WRAP((screen && !tmux), tmux,
+ "\x1b]11;?\x07"));
// Terminals with 256-colour SGR support despite what terminfo says.
if (unibi_get_num(ut, unibi_max_colors) < 256) {
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index c6c09c80d7..94b6e9e39d 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -110,6 +110,7 @@ static char uilog_last_event[1024] = { 0 };
void ui_init(void)
{
default_grid.handle = 1;
+ msg_grid_adj.target = &default_grid;
ui_comp_init();
}
diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c
index 9d3ec21949..a2e9266fbb 100644
--- a/src/nvim/ui_compositor.c
+++ b/src/nvim/ui_compositor.c
@@ -127,6 +127,9 @@ bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width,
bool valid, bool on_top)
{
bool moved;
+
+ grid->comp_height = height;
+ grid->comp_width = width;
if (grid->comp_index != 0) {
moved = (row != grid->comp_row) || (col != grid->comp_col);
if (ui_comp_should_draw()) {
@@ -157,7 +160,7 @@ bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width,
#ifndef NDEBUG
for (size_t i = 0; i < kv_size(layers); i++) {
if (kv_A(layers, i) == grid) {
- assert(false);
+ abort();
}
}
#endif
@@ -181,14 +184,12 @@ bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width,
insert_at--;
}
// not found: new grid
- kv_push(layers, grid);
- if (insert_at < kv_size(layers)-1) {
- for (size_t i = kv_size(layers)-1; i > insert_at; i--) {
- kv_A(layers, i) = kv_A(layers, i-1);
- kv_A(layers, i)->comp_index = i;
- }
- kv_A(layers, insert_at) = grid;
+ kv_pushp(layers);
+ for (size_t i = kv_size(layers)-1; i > insert_at; i--) {
+ kv_A(layers, i) = kv_A(layers, i-1);
+ kv_A(layers, i)->comp_index = i;
}
+ kv_A(layers, insert_at) = grid;
grid->comp_row = row;
grid->comp_col = col;
@@ -277,6 +278,9 @@ static void ui_comp_grid_cursor_goto(UI *ui, Integer grid_handle,
// should configure all grids before entering win_update()
if (curgrid != &default_grid) {
size_t new_index = kv_size(layers)-1;
+ if (kv_A(layers, new_index) == &msg_grid) {
+ new_index--;
+ }
if (kv_A(layers, new_index) == &pum_grid) {
new_index--;
}
@@ -334,17 +338,25 @@ static void compose_line(Integer row, Integer startcol, Integer endcol,
sattr_T *bg_attrs = &default_grid.attrs[default_grid.line_offset[row]
+(size_t)startcol];
+ int grid_width, grid_height;
while (col < endcol) {
int until = 0;
for (size_t i = 0; i < kv_size(layers); i++) {
ScreenGrid *g = kv_A(layers, i);
- if (g->comp_row > row || row >= g->comp_row + g->Rows
+ // compose_line may have been called after a shrinking operation but
+ // before the resize has actually been applied. Therefore, we need to
+ // first check to see if any grids have pending updates to width/height,
+ // to ensure that we don't accidentally put any characters into `linebuf`
+ // that have been invalidated.
+ grid_width = MIN(g->Columns, g->comp_width);
+ grid_height = MIN(g->Rows, g->comp_height);
+ if (g->comp_row > row || row >= g->comp_row + grid_height
|| g->comp_disabled) {
continue;
}
- if (g->comp_col <= col && col < g->comp_col+g->Columns) {
+ if (g->comp_col <= col && col < g->comp_col + grid_width) {
grid = g;
- until = g->comp_col+g->Columns;
+ until = g->comp_col + grid_width;
} else if (g->comp_col > col) {
until = MIN(until, g->comp_col);
}
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index da464c56dc..f52850f6f3 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -580,6 +580,10 @@ int u_savecommon(linenr_T top, linenr_T bot, linenr_T newbot, int reload)
uep->ue_array = NULL;
uep->ue_next = curbuf->b_u_newhead->uh_entry;
curbuf->b_u_newhead->uh_entry = uep;
+ if (reload) {
+ // buffer was reloaded, notify text change subscribers
+ curbuf->b_u_newhead->uh_flags |= UH_RELOAD;
+ }
curbuf->b_u_synced = false;
undo_undoes = false;
@@ -590,13 +594,20 @@ int u_savecommon(linenr_T top, linenr_T bot, linenr_T newbot, int reload)
}
-# define UF_START_MAGIC "Vim\237UnDo\345" /* magic at start of undofile */
+// magic at start of undofile
+# define UF_START_MAGIC "Vim\237UnDo\345"
# define UF_START_MAGIC_LEN 9
-# define UF_HEADER_MAGIC 0x5fd0 /* magic at start of header */
-# define UF_HEADER_END_MAGIC 0xe7aa /* magic after last header */
-# define UF_ENTRY_MAGIC 0xf518 /* magic at start of entry */
-# define UF_ENTRY_END_MAGIC 0x3581 /* magic after last entry */
-# define UF_VERSION 2 /* 2-byte undofile version number */
+// magic at start of header
+# define UF_HEADER_MAGIC 0x5fd0
+// magic after last header
+# define UF_HEADER_END_MAGIC 0xe7aa
+// magic at start of entry
+# define UF_ENTRY_MAGIC 0xf518
+// magic after last entry
+# define UF_ENTRY_END_MAGIC 0x3581
+
+// 2-byte undofile version number
+# define UF_VERSION 3
/* extra fields for header */
# define UF_LAST_SAVE_NR 1
@@ -843,6 +854,15 @@ static bool serialize_uhp(bufinfo_T *bi, u_header_T *uhp)
}
}
undo_write_bytes(bi, (uintmax_t)UF_ENTRY_END_MAGIC, 2);
+
+ // Write all extmark undo objects
+ for (size_t i = 0; i < kv_size(uhp->uh_extmark); i++) {
+ if (!serialize_extmark(bi, kv_A(uhp->uh_extmark, i))) {
+ return false;
+ }
+ }
+ undo_write_bytes(bi, (uintmax_t)UF_ENTRY_END_MAGIC, 2);
+
return true;
}
@@ -924,9 +944,95 @@ static u_header_T *unserialize_uhp(bufinfo_T *bi,
return NULL;
}
+ // Unserialize all extmark undo information
+ ExtmarkUndoObject *extup;
+ kv_init(uhp->uh_extmark);
+
+ while ((c = undo_read_2c(bi)) == UF_ENTRY_MAGIC) {
+ bool error = false;
+ extup = unserialize_extmark(bi, &error, file_name);
+ if (error) {
+ kv_destroy(uhp->uh_extmark);
+ xfree(extup);
+ return NULL;
+ }
+ kv_push(uhp->uh_extmark, *extup);
+ xfree(extup);
+ }
+ if (c != UF_ENTRY_END_MAGIC) {
+ corruption_error("entry end", file_name);
+ u_free_uhp(uhp);
+ return NULL;
+ }
+
return uhp;
}
+static bool serialize_extmark(bufinfo_T *bi, ExtmarkUndoObject extup)
+{
+ if (extup.type == kExtmarkSplice) {
+ undo_write_bytes(bi, (uintmax_t)UF_ENTRY_MAGIC, 2);
+ undo_write_bytes(bi, (uintmax_t)extup.type, 4);
+ if (!undo_write(bi, (uint8_t *)&(extup.data.splice),
+ sizeof(ExtmarkSplice))) {
+ return false;
+ }
+ } else if (extup.type == kExtmarkMove) {
+ undo_write_bytes(bi, (uintmax_t)UF_ENTRY_MAGIC, 2);
+ undo_write_bytes(bi, (uintmax_t)extup.type, 4);
+ if (!undo_write(bi, (uint8_t *)&(extup.data.move), sizeof(ExtmarkMove))) {
+ return false;
+ }
+ }
+ // Note: We do not serialize ExtmarkSavePos information, since
+ // buffer marktrees are not retained when closing/reopening a file
+ return true;
+}
+
+static ExtmarkUndoObject *unserialize_extmark(bufinfo_T *bi, bool *error,
+ const char *filename)
+{
+ UndoObjectType type;
+ uint8_t *buf = NULL;
+ size_t n_elems;
+
+ ExtmarkUndoObject *extup = xmalloc(sizeof(ExtmarkUndoObject));
+
+ type = (UndoObjectType)undo_read_4c(bi);
+ extup->type = type;
+ if (type == kExtmarkSplice) {
+ n_elems = (size_t)sizeof(ExtmarkSplice) / sizeof(uint8_t);
+ buf = xcalloc(sizeof(uint8_t), n_elems);
+ if (!undo_read(bi, buf, n_elems)) {
+ goto error;
+ }
+ extup->data.splice = *(ExtmarkSplice *)buf;
+ } else if (type == kExtmarkMove) {
+ n_elems = (size_t)sizeof(ExtmarkMove) / sizeof(uint8_t);
+ buf = xcalloc(sizeof(uint8_t), n_elems);
+ if (!undo_read(bi, buf, n_elems)) {
+ goto error;
+ }
+ extup->data.move = *(ExtmarkMove *)buf;
+ } else {
+ goto error;
+ }
+
+ if (buf) {
+ xfree(buf);
+ }
+
+ return extup;
+
+error:
+ xfree(extup);
+ if (buf) {
+ xfree(buf);
+ }
+ *error = true;
+ return NULL;
+}
+
/// Serializes "uep".
///
/// @param bi The buffer information
@@ -2157,8 +2263,9 @@ static void u_undoredo(int undo, bool do_buf_event)
u_check(FALSE);
#endif
old_flags = curhead->uh_flags;
- new_flags = (curbuf->b_changed ? UH_CHANGED : 0) +
- ((curbuf->b_ml.ml_flags & ML_EMPTY) ? UH_EMPTYBUF : 0);
+ new_flags = (curbuf->b_changed ? UH_CHANGED : 0)
+ | ((curbuf->b_ml.ml_flags & ML_EMPTY) ? UH_EMPTYBUF : 0)
+ | (old_flags & UH_RELOAD);
setpcmark();
/*
@@ -2299,6 +2406,11 @@ static void u_undoredo(int undo, bool do_buf_event)
extmark_apply_undo(undo_info, undo);
}
}
+ if (curhead->uh_flags & UH_RELOAD) {
+ // TODO(bfredl): this is a bit crude. When 'undoreload' is used we
+ // should have all info to send a buffer-reloaing on_lines/on_bytes event
+ buf_updates_unload(curbuf, true);
+ }
// finish Adjusting extmarks
diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h
index cc2c39a711..b46295a15d 100644
--- a/src/nvim/undo_defs.h
+++ b/src/nvim/undo_defs.h
@@ -69,9 +69,10 @@ struct u_header {
#endif
};
-/* values for uh_flags */
-#define UH_CHANGED 0x01 /* b_changed flag before undo/after redo */
-#define UH_EMPTYBUF 0x02 /* buffer was empty */
+// values for uh_flags
+#define UH_CHANGED 0x01 // b_changed flag before undo/after redo
+#define UH_EMPTYBUF 0x02 // buffer was empty
+#define UH_RELOAD 0x04 // buffer was reloaded
/// Structure passed around between undofile functions.
typedef struct {
diff --git a/src/nvim/version.c b/src/nvim/version.c
index 834d27cc84..deba3f6e49 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -300,7 +300,7 @@ static const int included_patches[] = {
1620,
1619,
1618,
- // 1617,
+ 1617,
// 1616,
1615,
1614,
@@ -392,7 +392,7 @@ static const int included_patches[] = {
1528,
1527,
1526,
- // 1525,
+ 1525,
1524,
1523,
1522,
@@ -462,7 +462,7 @@ static const int included_patches[] = {
1458,
1457,
1456,
- // 1455,
+ 1455,
1454,
1453,
1452,
diff --git a/src/nvim/vim.h b/src/nvim/vim.h
index 01f20cf29a..0245c472ef 100644
--- a/src/nvim/vim.h
+++ b/src/nvim/vim.h
@@ -158,7 +158,9 @@ enum {
EXPAND_MESSAGES,
EXPAND_MAPCLEAR,
EXPAND_ARGLIST,
+ EXPAND_DIFF_BUFFERS,
EXPAND_CHECKHEALTH,
+ EXPAND_LUA,
};
diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c
index 44b6ab5f5a..e9d82ca87d 100644
--- a/src/nvim/viml/parser/expressions.c
+++ b/src/nvim/viml/parser/expressions.c
@@ -2078,7 +2078,7 @@ viml_pexpr_parse_process_token:
case kExprLexMissing:
case kExprLexSpacing:
case kExprLexEOC: {
- assert(false);
+ abort();
}
case kExprLexInvalid: {
ERROR_FROM_TOKEN(cur_token);
@@ -3028,7 +3028,7 @@ viml_pexpr_parse_end:
// Until trailing "}" it is impossible to distinguish curly braces
// identifier and dictionary, so it must not appear in the stack like
// this.
- assert(false);
+ abort();
}
case kExprNodeInteger:
case kExprNodeFloat:
@@ -3042,7 +3042,7 @@ viml_pexpr_parse_end:
// These are plain values and not containers, for them it should only
// be possible to show up in the topmost stack element, but it was
// unconditionally popped at the start.
- assert(false);
+ abort();
}
case kExprNodeComma:
case kExprNodeColon:
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 00f49724b6..c482d265ff 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -605,6 +605,7 @@ win_T *win_new_float(win_T *wp, FloatConfig fconfig, Error *err)
wp->w_vsep_width = 0;
win_config_float(wp, fconfig);
+ win_set_inner_size(wp);
wp->w_pos_changed = true;
redraw_later(wp, VALID);
return wp;
@@ -667,6 +668,12 @@ void win_config_float(win_T *wp, FloatConfig fconfig)
}
bool change_external = fconfig.external != wp->w_float_config.external;
+ bool change_border = (fconfig.border != wp->w_float_config.border
+ || memcmp(fconfig.border_hl_ids,
+ wp->w_float_config.border_hl_ids,
+ sizeof fconfig.border_hl_ids));
+
+
wp->w_float_config = fconfig;
if (!ui_has(kUIMultigrid)) {
@@ -676,11 +683,18 @@ void win_config_float(win_T *wp, FloatConfig fconfig)
win_set_inner_size(wp);
must_redraw = MAX(must_redraw, VALID);
+
wp->w_pos_changed = true;
- if (change_external) {
+ if (change_external || change_border) {
wp->w_hl_needs_update = true;
redraw_later(wp, NOT_VALID);
}
+
+ // changing border style while keeping border only requires redrawing border
+ if (fconfig.border) {
+ wp->w_redr_border = true;
+ redraw_later(wp, VALID);
+ }
}
void win_check_anchored_floats(win_T *win)
@@ -710,10 +724,10 @@ int win_fdccol_count(win_T *wp)
}
-static void ui_ext_win_position(win_T *wp)
+void ui_ext_win_position(win_T *wp)
{
if (!wp->w_floating) {
- ui_call_win_pos(wp->w_grid.handle, wp->handle, wp->w_winrow,
+ ui_call_win_pos(wp->w_grid_alloc.handle, wp->handle, wp->w_winrow,
wp->w_wincol, wp->w_width, wp->w_height);
return;
}
@@ -743,8 +757,8 @@ static void ui_ext_win_position(win_T *wp)
}
if (ui_has(kUIMultigrid)) {
String anchor = cstr_to_string(float_anchor_str[c.anchor]);
- ui_call_win_float_pos(wp->w_grid.handle, wp->handle, anchor, grid->handle,
- row, col, c.focusable);
+ ui_call_win_float_pos(wp->w_grid_alloc.handle, wp->handle, anchor,
+ grid->handle, row, col, c.focusable);
} else {
// TODO(bfredl): ideally, compositor should work like any multigrid UI
// and use standard win_pos events.
@@ -759,17 +773,17 @@ static void ui_ext_win_position(win_T *wp)
wp->w_wincol = comp_col;
bool valid = (wp->w_redr_type == 0);
bool on_top = (curwin == wp) || !curwin->w_floating;
- ui_comp_put_grid(&wp->w_grid, comp_row, comp_col, wp->w_height,
- wp->w_width, valid, on_top);
- ui_check_cursor_grid(wp->w_grid.handle);
- wp->w_grid.focusable = wp->w_float_config.focusable;
+ ui_comp_put_grid(&wp->w_grid_alloc, comp_row, comp_col,
+ wp->w_height_outer, wp->w_width_outer, valid, on_top);
+ ui_check_cursor_grid(wp->w_grid_alloc.handle);
+ wp->w_grid_alloc.focusable = wp->w_float_config.focusable;
if (!valid) {
- wp->w_grid.valid = false;
+ wp->w_grid_alloc.valid = false;
redraw_later(wp, NOT_VALID);
}
}
} else {
- ui_call_win_external_pos(wp->w_grid.handle, wp->handle);
+ ui_call_win_external_pos(wp->w_grid_alloc.handle, wp->handle);
}
}
@@ -784,260 +798,12 @@ void ui_ext_win_viewport(win_T *wp)
// interact with incomplete final line? Diff filler lines?
botline = wp->w_buffer->b_ml.ml_line_count;
}
- ui_call_win_viewport(wp->w_grid.handle, wp->handle, wp->w_topline-1,
+ ui_call_win_viewport(wp->w_grid_alloc.handle, wp->handle, wp->w_topline-1,
botline, wp->w_cursor.lnum-1, wp->w_cursor.col);
wp->w_viewport_invalid = false;
}
}
-static bool parse_float_anchor(String anchor, FloatAnchor *out)
-{
- if (anchor.size == 0) {
- *out = (FloatAnchor)0;
- }
- char *str = anchor.data;
- if (striequal(str, "NW")) {
- *out = 0; // NW is the default
- } else if (striequal(str, "NE")) {
- *out = kFloatAnchorEast;
- } else if (striequal(str, "SW")) {
- *out = kFloatAnchorSouth;
- } else if (striequal(str, "SE")) {
- *out = kFloatAnchorSouth | kFloatAnchorEast;
- } else {
- return false;
- }
- return true;
-}
-
-static bool parse_float_relative(String relative, FloatRelative *out)
-{
- char *str = relative.data;
- if (striequal(str, "editor")) {
- *out = kFloatRelativeEditor;
- } else if (striequal(str, "win")) {
- *out = kFloatRelativeWindow;
- } else if (striequal(str, "cursor")) {
- *out = kFloatRelativeCursor;
- } else {
- return false;
- }
- return true;
-}
-
-static bool parse_float_bufpos(Array bufpos, lpos_T *out)
-{
- if (bufpos.size != 2
- || bufpos.items[0].type != kObjectTypeInteger
- || bufpos.items[1].type != kObjectTypeInteger) {
- return false;
- }
- out->lnum = bufpos.items[0].data.integer;
- out->col = bufpos.items[1].data.integer;
- return true;
-}
-
-bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf,
- Error *err)
-{
- // TODO(bfredl): use a get/has_key interface instead and get rid of extra
- // flags
- bool has_row = false, has_col = false, has_relative = false;
- bool has_external = false, has_window = false;
- bool has_width = false, has_height = false;
- bool has_bufpos = false;
-
- for (size_t i = 0; i < config.size; i++) {
- char *key = config.items[i].key.data;
- Object val = config.items[i].value;
- if (!strcmp(key, "row")) {
- has_row = true;
- if (val.type == kObjectTypeInteger) {
- fconfig->row = val.data.integer;
- } else if (val.type == kObjectTypeFloat) {
- fconfig->row = val.data.floating;
- } else {
- api_set_error(err, kErrorTypeValidation,
- "'row' key must be Integer or Float");
- return false;
- }
- } else if (!strcmp(key, "col")) {
- has_col = true;
- if (val.type == kObjectTypeInteger) {
- fconfig->col = val.data.integer;
- } else if (val.type == kObjectTypeFloat) {
- fconfig->col = val.data.floating;
- } else {
- api_set_error(err, kErrorTypeValidation,
- "'col' key must be Integer or Float");
- return false;
- }
- } else if (strequal(key, "width")) {
- has_width = true;
- if (val.type == kObjectTypeInteger && val.data.integer > 0) {
- fconfig->width = val.data.integer;
- } else {
- api_set_error(err, kErrorTypeValidation,
- "'width' key must be a positive Integer");
- return false;
- }
- } else if (strequal(key, "height")) {
- has_height = true;
- if (val.type == kObjectTypeInteger && val.data.integer > 0) {
- fconfig->height= val.data.integer;
- } else {
- api_set_error(err, kErrorTypeValidation,
- "'height' key must be a positive Integer");
- return false;
- }
- } else if (!strcmp(key, "anchor")) {
- if (val.type != kObjectTypeString) {
- api_set_error(err, kErrorTypeValidation,
- "'anchor' key must be String");
- return false;
- }
- if (!parse_float_anchor(val.data.string, &fconfig->anchor)) {
- api_set_error(err, kErrorTypeValidation,
- "Invalid value of 'anchor' key");
- return false;
- }
- } else if (!strcmp(key, "relative")) {
- if (val.type != kObjectTypeString) {
- api_set_error(err, kErrorTypeValidation,
- "'relative' key must be String");
- return false;
- }
- // ignore empty string, to match nvim_win_get_config
- if (val.data.string.size > 0) {
- has_relative = true;
- if (!parse_float_relative(val.data.string, &fconfig->relative)) {
- api_set_error(err, kErrorTypeValidation,
- "Invalid value of 'relative' key");
- return false;
- }
- }
- } else if (!strcmp(key, "win")) {
- has_window = true;
- if (val.type != kObjectTypeInteger
- && val.type != kObjectTypeWindow) {
- api_set_error(err, kErrorTypeValidation,
- "'win' key must be Integer or Window");
- return false;
- }
- fconfig->window = val.data.integer;
- } else if (!strcmp(key, "bufpos")) {
- if (val.type != kObjectTypeArray) {
- api_set_error(err, kErrorTypeValidation,
- "'bufpos' key must be Array");
- return false;
- }
- if (!parse_float_bufpos(val.data.array, &fconfig->bufpos)) {
- api_set_error(err, kErrorTypeValidation,
- "Invalid value of 'bufpos' key");
- return false;
- }
- has_bufpos = true;
- } else if (!strcmp(key, "external")) {
- if (val.type == kObjectTypeInteger) {
- fconfig->external = val.data.integer;
- } else if (val.type == kObjectTypeBoolean) {
- fconfig->external = val.data.boolean;
- } else {
- api_set_error(err, kErrorTypeValidation,
- "'external' key must be Boolean");
- return false;
- }
- has_external = fconfig->external;
- } else if (!strcmp(key, "focusable")) {
- if (val.type == kObjectTypeInteger) {
- fconfig->focusable = val.data.integer;
- } else if (val.type == kObjectTypeBoolean) {
- fconfig->focusable = val.data.boolean;
- } else {
- api_set_error(err, kErrorTypeValidation,
- "'focusable' key must be Boolean");
- return false;
- }
- } else if (!strcmp(key, "style")) {
- if (val.type != kObjectTypeString) {
- api_set_error(err, kErrorTypeValidation,
- "'style' key must be String");
- return false;
- }
- if (val.data.string.data[0] == NUL) {
- fconfig->style = kWinStyleUnused;
- } else if (striequal(val.data.string.data, "minimal")) {
- fconfig->style = kWinStyleMinimal;
- } else {
- api_set_error(err, kErrorTypeValidation,
- "Invalid value of 'style' key");
- }
- } else {
- api_set_error(err, kErrorTypeValidation,
- "Invalid key '%s'", key);
- return false;
- }
- }
-
- if (has_window && !(has_relative
- && fconfig->relative == kFloatRelativeWindow)) {
- api_set_error(err, kErrorTypeValidation,
- "'win' key is only valid with relative='win'");
- return false;
- }
-
- if ((has_relative && fconfig->relative == kFloatRelativeWindow)
- && (!has_window || fconfig->window == 0)) {
- fconfig->window = curwin->handle;
- }
-
- if (has_window && !has_bufpos) {
- fconfig->bufpos.lnum = -1;
- }
-
- if (has_bufpos) {
- if (!has_row) {
- fconfig->row = (fconfig->anchor & kFloatAnchorSouth) ? 0 : 1;
- has_row = true;
- }
- if (!has_col) {
- fconfig->col = 0;
- has_col = true;
- }
- }
-
- if (has_relative && has_external) {
- api_set_error(err, kErrorTypeValidation,
- "Only one of 'relative' and 'external' must be used");
- return false;
- } else if (!reconf && !has_relative && !has_external) {
- api_set_error(err, kErrorTypeValidation,
- "One of 'relative' and 'external' must be used");
- return false;
- } else if (has_relative) {
- fconfig->external = false;
- }
-
- if (!reconf && !(has_height && has_width)) {
- api_set_error(err, kErrorTypeValidation,
- "Must specify 'width' and 'height'");
- return false;
- }
-
- if (fconfig->external && !ui_has(kUIMultigrid)) {
- api_set_error(err, kErrorTypeValidation,
- "UI doesn't support external windows");
- return false;
- }
-
- if (has_relative != has_row || has_row != has_col) {
- api_set_error(err, kErrorTypeValidation,
- "'relative' requires 'row'/'col' or 'bufpos'");
- return false;
- }
- return true;
-}
-
/*
* split the current window, implements CTRL-W s and :split
*
@@ -1539,6 +1305,10 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
p_wh = i;
}
+ if (!win_valid(oldwin)) {
+ return FAIL;
+ }
+
// Send the window positions to the UI
oldwin->w_pos_changed = true;
@@ -1619,6 +1389,23 @@ static void win_init_some(win_T *newp, win_T *oldp)
win_copy_options(oldp, newp);
}
+/// Return TRUE if "win" is floating window in the current tab page.
+///
+/// @param win window to check
+bool win_valid_floating(const win_T *win)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (win == NULL) {
+ return false;
+ }
+
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp == win) {
+ return wp->w_floating;
+ }
+ }
+ return false;
+}
/// Check if "win" is a pointer to an existing window in the current tabpage.
///
@@ -1936,12 +1723,12 @@ static void win_totop(int size, int flags)
}
if (curwin->w_floating) {
- ui_comp_remove_grid(&curwin->w_grid);
+ ui_comp_remove_grid(&curwin->w_grid_alloc);
if (ui_has(kUIMultigrid)) {
curwin->w_pos_changed = true;
} else {
// No longer a float, a non-multigrid UI shouldn't draw it as such
- ui_call_win_hide(curwin->w_grid.handle);
+ ui_call_win_hide(curwin->w_grid_alloc.handle);
win_free_grid(curwin, false);
}
} else {
@@ -2564,11 +2351,11 @@ int win_close(win_T *win, bool free_buf)
bool was_floating = win->w_floating;
if (ui_has(kUIMultigrid)) {
- ui_call_win_close(win->w_grid.handle);
+ ui_call_win_close(win->w_grid_alloc.handle);
}
if (win->w_floating) {
- ui_comp_remove_grid(&win->w_grid);
+ ui_comp_remove_grid(&win->w_grid_alloc);
if (win->w_float_config.external) {
for (tabpage_T *tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
if (tp == curtab) {
@@ -3746,9 +3533,11 @@ void win_init_size(void)
{
firstwin->w_height = ROWS_AVAIL;
firstwin->w_height_inner = firstwin->w_height;
+ firstwin->w_height_outer = firstwin->w_height;
topframe->fr_height = ROWS_AVAIL;
firstwin->w_width = Columns;
firstwin->w_width_inner = firstwin->w_width;
+ firstwin->w_width_outer = firstwin->w_width;
topframe->fr_width = Columns;
}
@@ -4114,7 +3903,7 @@ static void tabpage_check_windows(tabpage_T *old_curtab)
win_remove(wp, old_curtab);
win_append(lastwin_nofloating(), wp);
} else {
- ui_comp_remove_grid(&wp->w_grid);
+ ui_comp_remove_grid(&wp->w_grid_alloc);
}
}
wp->w_pos_changed = true;
@@ -4709,7 +4498,7 @@ static win_T *win_alloc(win_T *after, int hidden)
new_wp->handle = ++last_win_id;
handle_register_window(new_wp);
- grid_assign_handle(&new_wp->w_grid);
+ grid_assign_handle(&new_wp->w_grid_alloc);
// Init w: variables.
new_wp->w_vars = tv_dict_alloc();
@@ -4833,15 +4622,14 @@ win_free (
void win_free_grid(win_T *wp, bool reinit)
{
- if (wp->w_grid.handle != 0 && ui_has(kUIMultigrid)) {
- ui_call_grid_destroy(wp->w_grid.handle);
- wp->w_grid.handle = 0;
+ if (wp->w_grid_alloc.handle != 0 && ui_has(kUIMultigrid)) {
+ ui_call_grid_destroy(wp->w_grid_alloc.handle);
}
- grid_free(&wp->w_grid);
+ grid_free(&wp->w_grid_alloc);
if (reinit) {
// if a float is turned into a split and back into a float, the grid
// data structure will be reused
- memset(&wp->w_grid, 0, sizeof(wp->w_grid));
+ memset(&wp->w_grid_alloc, 0, sizeof(wp->w_grid_alloc));
}
}
@@ -5502,7 +5290,7 @@ void win_setminheight(void)
// loop until there is a 'winminheight' that is possible
while (p_wmh > 0) {
const int room = Rows - p_ch;
- const int needed = frame_minheight(topframe, NULL);
+ const int needed = min_rows() - 1; // 1 was added for the cmdline
if (room >= needed) {
break;
}
@@ -5946,6 +5734,17 @@ void win_set_inner_size(win_T *wp)
if (wp->w_buffer->terminal) {
terminal_check_size(wp->w_buffer->terminal);
}
+
+ bool has_border = wp->w_floating && wp->w_float_config.border;
+ for (int i = 0; i < 4; i++) {
+ wp->w_border_adj[i] =
+ has_border && wp->w_float_config.border_chars[2 * i+1][0];
+ }
+
+ wp->w_height_outer = (wp->w_height_inner
+ + wp->w_border_adj[0] + wp->w_border_adj[2]);
+ wp->w_width_outer = (wp->w_width_inner
+ + wp->w_border_adj[1] + wp->w_border_adj[3]);
}
/// Set the width of a window.
@@ -5960,9 +5759,17 @@ void win_new_width(win_T *wp, int width)
void win_comp_scroll(win_T *wp)
{
+ const long old_w_p_scr = wp->w_p_scr;
+
wp->w_p_scr = wp->w_height / 2;
- if (wp->w_p_scr == 0)
+ if (wp->w_p_scr == 0) {
wp->w_p_scr = 1;
+ }
+ if (wp->w_p_scr != old_w_p_scr) {
+ // Used by "verbose set scroll".
+ wp->w_p_script_ctx[WV_SCROLL].script_ctx.sc_sid = SID_WINLAYOUT;
+ wp->w_p_script_ctx[WV_SCROLL].script_ctx.sc_lnum = 0;
+ }
}
/*
@@ -7074,11 +6881,11 @@ void get_framelayout(const frame_T *fr, list_T *l, bool outer)
void win_ui_flush(void)
{
FOR_ALL_TAB_WINDOWS(tp, wp) {
- if (wp->w_pos_changed && wp->w_grid.chars != NULL) {
+ if (wp->w_pos_changed && wp->w_grid_alloc.chars != NULL) {
if (tp == curtab) {
ui_ext_win_position(wp);
} else {
- ui_call_win_hide(wp->w_grid.handle);
+ ui_call_win_hide(wp->w_grid_alloc.handle);
}
wp->w_pos_changed = false;
}