aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/CMakeLists.txt6
-rw-r--r--src/nvim/api/buffer.c5
-rw-r--r--src/nvim/api/vim.c10
-rw-r--r--src/nvim/aucmd.c41
-rw-r--r--src/nvim/buffer.c85
-rw-r--r--src/nvim/buffer_defs.h5
-rw-r--r--src/nvim/eval.c116
-rw-r--r--src/nvim/eval.lua2
-rw-r--r--src/nvim/eval/funcs.c5
-rw-r--r--src/nvim/eval/typval.c11
-rw-r--r--src/nvim/eval/typval.h14
-rw-r--r--src/nvim/eval/typval_encode.c.h2
-rw-r--r--src/nvim/eval/userfunc.c51
-rw-r--r--src/nvim/ex_cmds.c65
-rw-r--r--src/nvim/ex_getln.c7
-rw-r--r--src/nvim/fileio.c32
-rw-r--r--src/nvim/hardcopy.c39
-rw-r--r--src/nvim/lua/converter.c27
-rw-r--r--src/nvim/lua/converter.h9
-rw-r--r--src/nvim/lua/executor.c241
-rw-r--r--src/nvim/lua/executor.h1
-rw-r--r--src/nvim/lua/treesitter.c110
-rw-r--r--src/nvim/lua/vim.lua11
-rw-r--r--src/nvim/main.c99
-rw-r--r--src/nvim/main.h34
-rw-r--r--src/nvim/misc1.c9
-rw-r--r--src/nvim/ops.c20
-rw-r--r--src/nvim/option.c31
-rw-r--r--src/nvim/regexp_nfa.c17
-rw-r--r--src/nvim/search.c17
-rw-r--r--src/nvim/shada.c30
-rw-r--r--src/nvim/spell.c12
-rw-r--r--src/nvim/spellfile.c88
-rw-r--r--src/nvim/syntax.c75
-rw-r--r--src/nvim/terminal.c12
-rw-r--r--src/nvim/testdir/shared.vim2
-rw-r--r--src/nvim/testdir/summarize.vim2
-rw-r--r--src/nvim/testdir/test_assert.vim13
-rw-r--r--src/nvim/testdir/test_autocmd.vim4
-rw-r--r--src/nvim/testdir/test_diffmode.vim22
-rw-r--r--src/nvim/testdir/test_filetype.vim55
-rw-r--r--src/nvim/testdir/test_increment.vim36
-rw-r--r--src/nvim/testdir/test_regexp_utf8.vim19
-rw-r--r--src/nvim/testdir/test_registers.vim18
-rw-r--r--src/nvim/testdir/test_statusline.vim24
-rw-r--r--src/nvim/testdir/test_syntax.vim85
-rw-r--r--src/nvim/testdir/test_tagjump.vim69
-rw-r--r--src/nvim/testdir/test_textobjects.vim11
-rw-r--r--src/nvim/tui/tui.c4
-rw-r--r--src/nvim/version.c6
50 files changed, 1371 insertions, 338 deletions
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index 982fab173f..c7258dde12 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -570,10 +570,16 @@ add_library(
)
set_property(TARGET libnvim APPEND PROPERTY
INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS})
+if(MSVC)
+ set(LIBNVIM_NAME libnvim)
+else()
+ set(LIBNVIM_NAME nvim)
+endif()
set_target_properties(
libnvim
PROPERTIES
POSITION_INDEPENDENT_CODE ON
+ OUTPUT_NAME ${LIBNVIM_NAME}
)
set_property(
TARGET libnvim
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index b345dcaccd..8e61976c4b 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -42,6 +42,8 @@
/// \defgroup api-buffer
///
+/// \brief For more information on buffers, see |buffers|
+///
/// Unloaded Buffers:~
///
/// Buffers may be unloaded by the |:bunload| command or the buffer's
@@ -126,6 +128,7 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err)
/// @param opts Optional parameters.
/// - on_lines: Lua callback invoked on change.
/// Return `true` to detach. Args:
+/// - the string "lines"
/// - buffer handle
/// - b:changedtick
/// - first line that changed (zero-indexed)
@@ -136,9 +139,11 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err)
/// - deleted_codeunits (if `utf_sizes` is true)
/// - on_changedtick: Lua callback invoked on changedtick
/// increment without text change. Args:
+/// - the string "changedtick"
/// - buffer handle
/// - b:changedtick
/// - on_detach: Lua callback invoked on detach. Args:
+/// - the string "detach"
/// - buffer handle
/// - utf_sizes: include UTF-32 and UTF-16 size of the replaced
/// region, as args to `on_lines`.
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 40cef87cf0..d6f95c7a5f 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -203,6 +203,16 @@ Integer nvim_get_hl_id_by_name(String name)
/// flags. This is a blocking call, unlike |nvim_input()|.
///
/// On execution error: does not fail, but updates v:errmsg.
+//
+// If you need to input sequences like <C-o> use nvim_replace_termcodes
+// to replace the termcodes and then pass the resulting string to
+// nvim_feedkeys. You'll also want to enable escape_csi.
+///
+/// Example:
+/// <pre>
+/// :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true)
+/// :call nvim_feedkeys(key, 'n', v:true)
+/// </pre>
///
/// @param keys to be typed
/// @param mode behavior flags, see |feedkeys()|
diff --git a/src/nvim/aucmd.c b/src/nvim/aucmd.c
index a9a7d834a4..32c77fa288 100644
--- a/src/nvim/aucmd.c
+++ b/src/nvim/aucmd.c
@@ -8,6 +8,8 @@
#include "nvim/ui.h"
#include "nvim/aucmd.h"
#include "nvim/eval.h"
+#include "nvim/ex_getln.h"
+#include "nvim/buffer.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "aucmd.c.generated.h"
@@ -50,12 +52,47 @@ void aucmd_schedule_focusgained(bool gained)
static void do_autocmd_focusgained(bool gained)
{
static bool recursive = false;
+ static Timestamp last_time = (time_t)0;
+ bool need_redraw = false;
if (recursive) {
return; // disallow recursion
}
recursive = true;
- apply_autocmds((gained ? EVENT_FOCUSGAINED : EVENT_FOCUSLOST),
- NULL, NULL, false, curbuf);
+ need_redraw |= apply_autocmds((gained ? EVENT_FOCUSGAINED : EVENT_FOCUSLOST),
+ NULL, NULL, false, curbuf);
+
+ // When activated: Check if any file was modified outside of Vim.
+ // Only do this when not done within the last two seconds as:
+ // 1. Some filesystems have modification time granularity in seconds. Fat32
+ // has a granularity of 2 seconds.
+ // 2. We could get multiple notifications in a row.
+ if (gained && last_time + (Timestamp)2000 < os_now()) {
+ need_redraw = check_timestamps(true);
+ last_time = os_now();
+ }
+
+ if (need_redraw) {
+ // Something was executed, make sure the cursor is put back where it
+ // belongs.
+ need_wait_return = false;
+
+ if (State & CMDLINE) {
+ redrawcmdline();
+ } else if ((State & NORMAL) || (State & INSERT)) {
+ if (must_redraw != 0) {
+ update_screen(0);
+ }
+
+ setcursor();
+ }
+
+ ui_flush();
+ }
+
+ if (need_maketitle) {
+ maketitle();
+ }
+
recursive = false;
}
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 3ce39feda5..86067aceac 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -3084,28 +3084,29 @@ fileinfo(
}
vim_snprintf_add((char *)buffer, IOSIZE, "\"%s%s%s%s%s%s",
- curbufIsChanged() ? (shortmess(SHM_MOD)
- ? " [+]" : _(" [Modified]")) : " ",
- (curbuf->b_flags & BF_NOTEDITED)
- && !bt_dontwrite(curbuf)
- ? _("[Not edited]") : "",
- (curbuf->b_flags & BF_NEW)
- && !bt_dontwrite(curbuf)
- ? _("[New file]") : "",
- (curbuf->b_flags & BF_READERR) ? _("[Read errors]") : "",
- curbuf->b_p_ro ? (shortmess(SHM_RO) ? _("[RO]")
- : _("[readonly]")) : "",
- (curbufIsChanged() || (curbuf->b_flags & BF_WRITE_MASK)
- || curbuf->b_p_ro) ?
- " " : "");
- /* With 32 bit longs and more than 21,474,836 lines multiplying by 100
- * causes an overflow, thus for large numbers divide instead. */
- if (curwin->w_cursor.lnum > 1000000L)
+ curbufIsChanged()
+ ? (shortmess(SHM_MOD) ? " [+]" : _(" [Modified]")) : " ",
+ (curbuf->b_flags & BF_NOTEDITED) && !bt_dontwrite(curbuf)
+ ? _("[Not edited]") : "",
+ (curbuf->b_flags & BF_NEW) && !bt_dontwrite(curbuf)
+ ? new_file_message() : "",
+ (curbuf->b_flags & BF_READERR)
+ ? _("[Read errors]") : "",
+ curbuf->b_p_ro
+ ? (shortmess(SHM_RO) ? _("[RO]") : _("[readonly]")) : "",
+ (curbufIsChanged()
+ || (curbuf->b_flags & BF_WRITE_MASK)
+ || curbuf->b_p_ro)
+ ? " " : "");
+ // With 32 bit longs and more than 21,474,836 lines multiplying by 100
+ // causes an overflow, thus for large numbers divide instead.
+ if (curwin->w_cursor.lnum > 1000000L) {
n = (int)(((long)curwin->w_cursor.lnum) /
((long)curbuf->b_ml.ml_line_count / 100L));
- else
+ } else {
n = (int)(((long)curwin->w_cursor.lnum * 100L) /
(long)curbuf->b_ml.ml_line_count);
+ }
if (curbuf->b_ml.ml_flags & ML_EMPTY) {
vim_snprintf_add((char *)buffer, IOSIZE, "%s", _(no_lines_msg));
} else if (p_ru) {
@@ -3463,7 +3464,8 @@ int build_stl_str_hl(
} type;
} items[STL_MAX_ITEM];
#define TMPLEN 70
- char_u tmp[TMPLEN];
+ char_u buf_tmp[TMPLEN];
+ char_u win_tmp[TMPLEN];
char_u *usefmt = fmt;
const int save_must_redraw = must_redraw;
const int save_redr_type = curwin->w_redr_type;
@@ -3471,10 +3473,18 @@ int build_stl_str_hl(
// When the format starts with "%!" then evaluate it as an expression and
// use the result as the actual format string.
if (fmt[0] == '%' && fmt[1] == '!') {
+ typval_T tv = {
+ .v_type = VAR_NUMBER,
+ .vval.v_number = wp->handle,
+ };
+ set_var(S_LEN("g:statusline_winid"), &tv, false);
+
usefmt = eval_to_string_safe(fmt + 2, NULL, use_sandbox);
if (usefmt == NULL) {
usefmt = fmt;
}
+
+ do_unlet(S_LEN("g:statusline_winid"), true);
}
if (fillchar == 0) {
@@ -3903,8 +3913,10 @@ int build_stl_str_hl(
// { Evaluate the expression
// Store the current buffer number as a string variable
- vim_snprintf((char *)tmp, sizeof(tmp), "%d", curbuf->b_fnum);
- set_internal_string_var((char_u *)"g:actual_curbuf", tmp);
+ vim_snprintf((char *)buf_tmp, sizeof(buf_tmp), "%d", curbuf->b_fnum);
+ set_internal_string_var((char_u *)"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);
buf_T *const save_curbuf = curbuf;
win_T *const save_curwin = curwin;
@@ -3925,6 +3937,7 @@ int build_stl_str_hl(
// Remove the variable we just stored
do_unlet(S_LEN("g:actual_curbuf"), true);
+ do_unlet(S_LEN("g:actual_curwin"), true);
// }
@@ -3983,8 +3996,8 @@ int build_stl_str_hl(
// Store the position percentage in our temporary buffer.
// Note: We cannot store the value in `num` because
// `get_rel_pos` can return a named position. Ex: "Top"
- get_rel_pos(wp, tmp, TMPLEN);
- str = tmp;
+ get_rel_pos(wp, buf_tmp, TMPLEN);
+ str = buf_tmp;
break;
case STL_ARGLISTSTAT:
@@ -3994,19 +4007,19 @@ int build_stl_str_hl(
// at the end of the null-terminated string.
// Setting the first byte to null means it will place the argument
// number string at the beginning of the buffer.
- tmp[0] = 0;
+ buf_tmp[0] = 0;
// Note: The call will only return true if it actually
- // appended data to the `tmp` buffer.
- if (append_arg_number(wp, tmp, (int)sizeof(tmp), false)) {
- str = tmp;
+ // appended data to the `buf_tmp` buffer.
+ if (append_arg_number(wp, buf_tmp, (int)sizeof(buf_tmp), false)) {
+ str = buf_tmp;
}
break;
case STL_KEYMAP:
fillable = false;
- if (get_keymap_str(wp, (char_u *)"<%s>", tmp, TMPLEN)) {
- str = tmp;
+ if (get_keymap_str(wp, (char_u *)"<%s>", buf_tmp, TMPLEN)) {
+ str = buf_tmp;
}
break;
case STL_PAGENUM:
@@ -4063,9 +4076,9 @@ int build_stl_str_hl(
// (including the brackets and null terminating character)
if (*wp->w_buffer->b_p_ft != NUL
&& STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 3) {
- vim_snprintf((char *)tmp, sizeof(tmp), "[%s]",
- wp->w_buffer->b_p_ft);
- str = tmp;
+ vim_snprintf((char *)buf_tmp, sizeof(buf_tmp), "[%s]",
+ wp->w_buffer->b_p_ft);
+ str = buf_tmp;
}
break;
@@ -4077,13 +4090,13 @@ int build_stl_str_hl(
// (including the comma and null terminating character)
if (*wp->w_buffer->b_p_ft != NUL
&& STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 2) {
- vim_snprintf((char *)tmp, sizeof(tmp), ",%s",
- wp->w_buffer->b_p_ft);
+ vim_snprintf((char *)buf_tmp, sizeof(buf_tmp), ",%s",
+ wp->w_buffer->b_p_ft);
// Uppercase the file extension
- for (char_u *t = tmp; *t != 0; t++) {
+ for (char_u *t = buf_tmp; *t != 0; t++) {
*t = (char_u)TOUPPER_LOC(*t);
}
- str = tmp;
+ str = buf_tmp;
}
break;
}
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index d696eedbb7..550f8a5e40 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -373,6 +373,10 @@ struct stl_hlrec {
#define SYNSPL_TOP 1 // spell check toplevel text
#define SYNSPL_NOTOP 2 // don't spell check toplevel text
+// values for b_syn_foldlevel: how to compute foldlevel on a line
+#define SYNFLD_START 0 // use level of item at start of line
+#define SYNFLD_MINIMUM 1 // use lowest local minimum level on line
+
// avoid #ifdefs for when b_spell is not available
# define B_SPELL(buf) ((buf)->b_spell)
@@ -398,6 +402,7 @@ typedef struct {
int b_syn_error; // TRUE when error occurred in HL
bool b_syn_slow; // true when 'redrawtime' reached
int b_syn_ic; // ignore case for :syn cmds
+ int b_syn_foldlevel; // how to compute foldlevel on a line
int b_syn_spell; // SYNSPL_ values
garray_T b_syn_patterns; // table for syntax patterns
garray_T b_syn_clusters; // table for syntax clusters
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 66cd0e09c6..0a83c3a586 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -2818,7 +2818,7 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit)
/// @param[in] fonceit If true, do not complain if variable doesn’t exist.
///
/// @return OK if it existed, FAIL otherwise.
-int do_unlet(const char *const name, const size_t name_len, const int forceit)
+int do_unlet(const char *const name, const size_t name_len, const bool forceit)
FUNC_ATTR_NONNULL_ALL
{
const char *varname;
@@ -5552,19 +5552,18 @@ void prepare_assert_error(garray_T *gap)
}
}
-// Append "str" to "gap", escaping unprintable characters.
+// Append "p[clen]" to "gap", escaping unprintable characters.
// Changes NL to \n, CR to \r, etc.
-static void ga_concat_esc(garray_T *gap, char_u *str)
+static void ga_concat_esc(garray_T *gap, const char_u *p, int clen)
+ FUNC_ATTR_NONNULL_ALL
{
- char_u *p;
char_u buf[NUMBUFLEN];
- if (str == NULL) {
- ga_concat(gap, (char_u *)"NULL");
- return;
- }
-
- for (p = str; *p != NUL; p++) {
+ if (clen > 1) {
+ memmove(buf, p, clen);
+ buf[clen] = NUL;
+ ga_concat(gap, buf);
+ } else {
switch (*p) {
case BS: ga_concat(gap, (char_u *)"\\b"); break;
case ESC: ga_concat(gap, (char_u *)"\\e"); break;
@@ -5585,6 +5584,41 @@ static void ga_concat_esc(garray_T *gap, char_u *str)
}
}
+// Append "str" to "gap", escaping unprintable characters.
+// Changes NL to \n, CR to \r, etc.
+static void ga_concat_shorten_esc(garray_T *gap, const char_u *str)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ char_u buf[NUMBUFLEN];
+
+ if (str == NULL) {
+ ga_concat(gap, (char_u *)"NULL");
+ return;
+ }
+
+ for (const char_u *p = str; *p != NUL; p++) {
+ int same_len = 1;
+ const char_u *s = p;
+ const int c = mb_ptr2char_adv(&s);
+ const int clen = s - p;
+ while (*s != NUL && c == utf_ptr2char(s)) {
+ same_len++;
+ s += clen;
+ }
+ if (same_len > 20) {
+ ga_concat(gap, (char_u *)"\\[");
+ ga_concat_esc(gap, p, clen);
+ ga_concat(gap, (char_u *)" occurs ");
+ vim_snprintf((char *)buf, NUMBUFLEN, "%d", same_len);
+ ga_concat(gap, buf);
+ ga_concat(gap, (char_u *)" times]");
+ p = s - 1;
+ } else {
+ ga_concat_esc(gap, p, clen);
+ }
+ }
+}
+
// Fill "gap" with information about an assert error.
void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv,
char_u *exp_str, typval_T *exp_tv,
@@ -5609,10 +5643,10 @@ void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv,
if (exp_str == NULL) {
tofree = (char_u *)encode_tv2string(exp_tv, NULL);
- ga_concat_esc(gap, tofree);
+ ga_concat_shorten_esc(gap, tofree);
xfree(tofree);
} else {
- ga_concat_esc(gap, exp_str);
+ ga_concat_shorten_esc(gap, exp_str);
}
if (atype != ASSERT_NOTEQUAL) {
@@ -5624,7 +5658,7 @@ void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv,
ga_concat(gap, (char_u *)" but got ");
}
tofree = (char_u *)encode_tv2string(got_tv, NULL);
- ga_concat_esc(gap, tofree);
+ ga_concat_shorten_esc(gap, tofree);
xfree(tofree);
}
}
@@ -5674,6 +5708,9 @@ int assert_equalfile(typval_T *argvars)
IObuff[0] = NUL;
FILE *const fd1 = os_fopen(fname1, READBIN);
+ char line1[200];
+ char line2[200];
+ ptrdiff_t lineidx = 0;
if (fd1 == NULL) {
snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname1);
} else {
@@ -5682,6 +5719,7 @@ int assert_equalfile(typval_T *argvars)
fclose(fd1);
snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname2);
} else {
+ int64_t linecount = 1;
for (int64_t count = 0; ; count++) {
const int c1 = fgetc(fd1);
const int c2 = fgetc(fd2);
@@ -5693,10 +5731,24 @@ int assert_equalfile(typval_T *argvars)
} else if (c2 == EOF) {
STRCPY(IObuff, "second file is shorter");
break;
- } else if (c1 != c2) {
- snprintf((char *)IObuff, IOSIZE,
- "difference at byte %" PRId64, count);
- break;
+ } else {
+ line1[lineidx] = c1;
+ line2[lineidx] = c2;
+ lineidx++;
+ if (c1 != c2) {
+ snprintf((char *)IObuff, IOSIZE,
+ "difference at byte %" PRId64 ", line %" PRId64,
+ count, linecount);
+ break;
+ }
+ }
+ if (c1 == NL) {
+ linecount++;
+ lineidx = 0;
+ } else if (lineidx + 2 == (ptrdiff_t)sizeof(line1)) {
+ memmove(line1, line1 + 100, lineidx - 100);
+ memmove(line2, line2 + 100, lineidx - 100);
+ lineidx -= 100;
}
}
fclose(fd1);
@@ -5705,7 +5757,24 @@ int assert_equalfile(typval_T *argvars)
}
if (IObuff[0] != NUL) {
prepare_assert_error(&ga);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ char *const tofree = encode_tv2echo(&argvars[2], NULL);
+ ga_concat(&ga, (char_u *)tofree);
+ xfree(tofree);
+ ga_concat(&ga, (char_u *)": ");
+ }
ga_concat(&ga, IObuff);
+ if (lineidx > 0) {
+ line1[lineidx] = NUL;
+ line2[lineidx] = NUL;
+ ga_concat(&ga, (char_u *)" after \"");
+ ga_concat(&ga, (char_u *)line1);
+ if (STRCMP(line1, line2) != 0) {
+ ga_concat(&ga, (char_u *)"\" vs \"");
+ ga_concat(&ga, (char_u *)line2);
+ }
+ ga_concat(&ga, (char_u *)"\"");
+ }
assert_error(&ga);
ga_clear(&ga);
return 1;
@@ -7143,6 +7212,16 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg)
func_ref(name);
callback->data.funcref = vim_strsave(name);
callback->type = kCallbackFuncref;
+ } else if (nlua_is_table_from_lua(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 {
+ r = FAIL;
+ }
} else if (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0) {
callback->type = kCallbackNone;
} else {
@@ -8137,9 +8216,6 @@ void set_argv_var(char **argv, int argc)
list_T *l = tv_list_alloc(argc);
int i;
- if (l == NULL) {
- getout(1);
- }
tv_list_set_lock(l, VAR_FIXED);
for (i = 0; i < argc; i++) {
tv_list_append_string(l, (const char *const)argv[i], -1);
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 51872a7ba8..d20e381481 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -28,7 +28,7 @@ return {
asin={args=1, func="float_op_wrapper", data="&asin"}, -- WJMc
assert_beeps={args={1, 2}},
assert_equal={args={2, 3}},
- assert_equalfile={args=2},
+ assert_equalfile={args={2, 3}},
assert_exception={args={1, 2}},
assert_fails={args={1, 3}},
assert_false={args={1, 2}},
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 1071e75c06..831167a489 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -415,7 +415,7 @@ static void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL);
}
-// "assert_equalfile(fname-one, fname-two)" function
+// "assert_equalfile(fname-one, fname-two[, msg])" function
static void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
rettv->vval.v_number = assert_equalfile(argvars);
@@ -824,9 +824,12 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr)
} else if (argvars[0].v_type == VAR_PARTIAL) {
partial = argvars[0].vval.v_partial;
func = partial_name(partial);
+ } else if (nlua_is_table_from_lua(&argvars[0])) {
+ func = nlua_register_table_as_callable(&argvars[0]);
} else {
func = (char_u *)tv_get_string(&argvars[0]);
}
+
if (*func == NUL) {
return; // type error or empty name
}
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index 0daaf6c878..2394eb8099 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -16,6 +16,7 @@
#include "nvim/eval/typval_encode.h"
#include "nvim/eval.h"
#include "nvim/eval/userfunc.h"
+#include "nvim/lua/executor.h"
#include "nvim/types.h"
#include "nvim/assert.h"
#include "nvim/memory.h"
@@ -301,6 +302,7 @@ void tv_list_free_list(list_T *const l)
}
list_log(l, NULL, NULL, "freelist");
+ nlua_free_typval_list(l);
xfree(l);
}
@@ -797,10 +799,14 @@ bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic,
if (l1 == l2) {
return true;
}
- if (l1 == NULL || l2 == NULL) {
+ if (tv_list_len(l1) != tv_list_len(l2)) {
return false;
}
- if (tv_list_len(l1) != tv_list_len(l2)) {
+ if (tv_list_len(l1) == 0) {
+ // empty and NULL list are considered equal
+ return true;
+ }
+ if (l1 == NULL || l2 == NULL) {
return false;
}
@@ -1374,6 +1380,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);
xfree(d);
}
diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h
index 343dd205ff..503a32a81e 100644
--- a/src/nvim/eval/typval.h
+++ b/src/nvim/eval/typval.h
@@ -180,6 +180,8 @@ struct listvar_S {
int lv_idx; ///< Index of a cached item, used for optimising repeated l[idx].
int lv_copyID; ///< ID used by deepcopy().
VarLockStatus lv_lock; ///< Zero, VAR_LOCKED, VAR_FIXED.
+
+ LuaRef lua_table_ref;
};
// Static list with 10 items. Use tv_list_init_static10() to initialize.
@@ -245,6 +247,8 @@ struct dictvar_S {
dict_T *dv_used_next; ///< Next dictionary in used dictionaries list.
dict_T *dv_used_prev; ///< Previous dictionary in used dictionaries list.
QUEUE watchers; ///< Dictionary key watchers set by user code.
+
+ LuaRef lua_table_ref;
};
/// Type used for script ID
@@ -271,6 +275,12 @@ typedef struct {
/// Number of fixed variables used for arguments
#define FIXVAR_CNT 12
+/// Callback interface for C function reference>
+/// Used for managing functions that were registered with |register_cfunc|
+typedef int (*cfunc_T)(int argcount, typval_T *argvars, typval_T *rettv, void *state); // NOLINT
+/// Callback to clear cfunc_T and any associated state.
+typedef void (*cfunc_free_T)(void *state);
+
// Structure to hold info for a function that is currently being executed.
typedef struct funccall_S funccall_T;
@@ -307,6 +317,10 @@ struct ufunc {
garray_T uf_lines; ///< function lines
int uf_profiling; ///< true when func is being profiled
int uf_prof_initialized;
+ // Managing cfuncs
+ cfunc_T uf_cb; ///< C function extension callback
+ cfunc_free_T uf_cb_free; ///< C function extesion free callback
+ void *uf_cb_state; ///< State of C function extension.
// Profiling the function as a whole.
int uf_tm_count; ///< nr of calls
proftime_T uf_tm_total; ///< time spent in function + children
diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h
index 0aa64b1d5f..91c948ce7e 100644
--- a/src/nvim/eval/typval_encode.c.h
+++ b/src/nvim/eval/typval_encode.c.h
@@ -364,7 +364,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
_TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, copyID,
kMPConvList);
TYPVAL_ENCODE_CONV_LIST_START(tv, tv_list_len(tv->vval.v_list));
- assert(saved_copyID != copyID && saved_copyID != copyID - 1);
+ assert(saved_copyID != copyID);
_mp_push(*mpstack, ((MPConvStackVal) {
.type = kMPConvList,
.tv = tv,
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index 4d658498c1..229f0e8dde 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -32,6 +32,7 @@
#define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0
#define FC_REMOVED 0x20 // function redefined while uf_refcount > 0
#define FC_SANDBOX 0x40 // function defined in the sandbox
+#define FC_CFUNC 0x80 // C function extension
#ifdef INCLUDE_GENERATED_DECLARATIONS
#include "eval/userfunc.c.generated.h"
@@ -162,6 +163,17 @@ static void register_closure(ufunc_T *fp)
[current_funccal->fc_funcs.ga_len++] = fp;
}
+
+/// Get a name for a lambda. Returned in static memory.
+char_u * get_lambda_name(void)
+{
+ static char_u name[30];
+ static int lambda_no = 0;
+
+ snprintf((char *)name, sizeof(name), "<lambda>%d", ++lambda_no);
+ return name;
+}
+
/// Parse a lambda expression and get a Funcref from "*arg".
///
/// @return OK or FAIL. Returns NOTDONE for dict or {expr}.
@@ -175,7 +187,6 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
int ret;
char_u *start = skipwhite(*arg + 1);
char_u *s, *e;
- static int lambda_no = 0;
bool *old_eval_lavars = eval_lavars_used;
bool eval_lavars = false;
@@ -219,11 +230,9 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
if (evaluate) {
int len, flags = 0;
char_u *p;
- char_u name[20];
garray_T newlines;
- lambda_no++;
- snprintf((char *)name, sizeof(name), "<lambda>%d", lambda_no);
+ char_u *name = get_lambda_name();
fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
pt = xcalloc(1, sizeof(partial_T));
@@ -700,6 +709,11 @@ static void func_clear_items(ufunc_T *fp)
ga_clear_strings(&(fp->uf_args));
ga_clear_strings(&(fp->uf_lines));
+ if (fp->uf_cb_free != NULL) {
+ fp->uf_cb_free(fp->uf_cb_state);
+ fp->uf_cb_free = NULL;
+ }
+
XFREE_CLEAR(fp->uf_tml_count);
XFREE_CLEAR(fp->uf_tml_total);
XFREE_CLEAR(fp->uf_tml_self);
@@ -1408,6 +1422,9 @@ call_func(
if (fp != NULL && (fp->uf_flags & FC_DELETED)) {
error = ERROR_DELETED;
+ } else if (fp != NULL && (fp->uf_flags & FC_CFUNC)) {
+ cfunc_T cb = fp->uf_cb;
+ error = (*cb)(argcount, argvars, rettv, fp->uf_cb_state);
} else if (fp != NULL) {
if (argv_func != NULL) {
// postponed filling in the arguments, do it now
@@ -3435,3 +3452,29 @@ bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID)
xfree(tofree);
return abort;
}
+
+/// Registers a C extension user function.
+char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state)
+{
+ char_u *name = get_lambda_name();
+ ufunc_T *fp = NULL;
+
+ fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
+ if (fp == NULL) {
+ return NULL;
+ }
+
+ fp->uf_refcount = 1;
+ fp->uf_varargs = true;
+ fp->uf_flags = FC_CFUNC;
+ fp->uf_calls = 0;
+ fp->uf_script_ctx = current_sctx;
+ fp->uf_cb = cb;
+ fp->uf_cb_free = cb_free;
+ fp->uf_cb_state = state;
+
+ STRCPY(fp->uf_name, name);
+ hash_add(&func_hashtab, UF2HIKEY(fp));
+
+ return fp->uf_name;
+}
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 8a0f2e634a..519978f4fb 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -109,6 +109,8 @@ typedef struct {
# include "ex_cmds.c.generated.h"
#endif
+static int preview_bufnr = 0;
+
/// ":ascii" and "ga" implementation
void do_ascii(const exarg_T *const eap)
{
@@ -3244,7 +3246,7 @@ static char_u *sub_parse_flags(char_u *cmd, subflags_T *subflags,
/// @param do_buf_event If `true`, send buffer updates.
/// @return buffer used for 'inccommand' preview
static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
- bool do_buf_event)
+ bool do_buf_event, handle_T bufnr)
{
long i = 0;
regmmatch_T regmatch;
@@ -4198,7 +4200,7 @@ skip:
}
curbuf->b_changed = save_b_changed; // preserve 'modified' during preview
preview_buf = show_sub(eap, old_cursor, &preview_lines,
- pre_hl_id, pre_src_id);
+ pre_hl_id, pre_src_id, bufnr);
if (subsize > 0) {
extmark_clear(orig_buf, pre_src_id, eap->line1-1, 0,
kv_last(preview_lines.subresults).end.lnum-1, MAXCOL);
@@ -5575,14 +5577,31 @@ void ex_helpclose(exarg_T *eap)
}
}
+/// Tries to enter to an existing window of given buffer. If no existing buffer
+/// is found, creates a new split.
+///
+/// Returns OK/FAIL.
+int sub_preview_win(buf_T *preview_buf)
+{
+ if (preview_buf != NULL) {
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_buffer == preview_buf) {
+ win_enter(wp, false);
+
+ return OK;
+ }
+ }
+ }
+ return win_split((int)p_cwh, WSP_BOT);
+}
+
/// Shows the effects of the :substitute command being typed ('inccommand').
/// If inccommand=split, shows a preview window and later restores the layout.
static buf_T *show_sub(exarg_T *eap, pos_T old_cusr,
- PreviewLines *preview_lines, int hl_id, int src_id)
+ PreviewLines *preview_lines, int hl_id, int src_id,
+ handle_T bufnr)
FUNC_ATTR_NONNULL_ALL
{
- static handle_T bufnr = 0; // special buffer, re-used on each visit
-
win_T *save_curwin = curwin;
cmdmod_T save_cmdmod = cmdmod;
char_u *save_shm_p = vim_strsave(p_shm);
@@ -5600,9 +5619,9 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr,
bool outside_curline = (eap->line1 != old_cusr.lnum
|| eap->line2 != old_cusr.lnum);
- bool split = outside_curline && (*p_icm != 'n');
+ bool preview = outside_curline && (*p_icm != 'n');
if (preview_buf == curbuf) { // Preview buffer cannot preview itself!
- split = false;
+ preview = false;
preview_buf = NULL;
}
@@ -5620,11 +5639,10 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr,
linenr_T highest_num_line = 0;
int col_width = 0;
- if (split && win_split((int)p_cwh, WSP_BOT) != FAIL) {
+ if (preview && sub_preview_win(preview_buf) != FAIL) {
buf_open_scratch(preview_buf ? bufnr : 0, "[Preview]");
buf_clear();
preview_buf = curbuf;
- bufnr = preview_buf->handle;
curbuf->b_p_bl = false;
curbuf->b_p_ma = true;
curbuf->b_p_ul = -1;
@@ -5713,7 +5731,7 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr,
win_enter(save_curwin, false); // Return to original window
update_topline();
- // Update screen now. Must do this _before_ close_windows().
+ // Update screen now.
int save_rd = RedrawingDisabled;
RedrawingDisabled = 0;
update_screen(SOME_VALID);
@@ -5727,6 +5745,17 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr,
return preview_buf;
}
+/// Closes any open windows for inccommand preview buffer.
+void close_preview_windows(void)
+{
+ block_autocmds();
+ buf_T *buf = preview_bufnr ? buflist_findnr(preview_bufnr) : NULL;
+ if (buf != NULL) {
+ close_windows(buf, false);
+ }
+ unblock_autocmds();
+}
+
/// :substitute command
///
/// If 'inccommand' is empty: calls do_sub().
@@ -5736,7 +5765,9 @@ void ex_substitute(exarg_T *eap)
{
bool preview = (State & CMDPREVIEW);
if (*p_icm == NUL || !preview) { // 'inccommand' is disabled
- (void)do_sub(eap, profile_zero(), true);
+ close_preview_windows();
+ (void)do_sub(eap, profile_zero(), true, preview_bufnr);
+
return;
}
@@ -5760,9 +5791,14 @@ void ex_substitute(exarg_T *eap)
// Don't show search highlighting during live substitution
bool save_hls = p_hls;
p_hls = false;
- buf_T *preview_buf = do_sub(eap, profile_setlimit(p_rdt), false);
+ buf_T *preview_buf = do_sub(eap, profile_setlimit(p_rdt), false,
+ preview_bufnr);
p_hls = save_hls;
+ if (preview_buf != NULL) {
+ preview_bufnr = preview_buf->handle;
+ }
+
if (save_changedtick != buf_get_changedtick(curbuf)) {
// Undo invisibly. This also moves the cursor!
if (!u_undo_and_forget(1)) { abort(); }
@@ -5772,10 +5808,7 @@ void ex_substitute(exarg_T *eap)
curbuf->b_u_time_cur = save_b_u_time_cur;
buf_set_changedtick(curbuf, save_changedtick);
}
- if (buf_valid(preview_buf)) {
- // XXX: Must do this *after* u_undo_and_forget(), why?
- close_windows(preview_buf, false);
- }
+
curbuf->b_p_ul = save_b_p_ul;
curwin->w_p_cul = save_w_p_cul; // Restore 'cursorline'
curwin->w_p_cuc = save_w_p_cuc; // Restore 'cursorcolumn'
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 1a836b7a98..93684e8606 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -450,6 +450,11 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
ExpandCleanup(&s->xpc);
ccline.xpc = NULL;
+ if (s->gotesc) {
+ // There might be a preview window open for inccommand. Close it.
+ close_preview_windows();
+ }
+
if (s->did_incsearch) {
if (s->gotesc) {
curwin->w_cursor = s->save_cursor;
@@ -1958,8 +1963,10 @@ static int command_line_changed(CommandLineState *s)
update_topline();
redrawcmdline();
+
} else if (State & CMDPREVIEW) {
State = (State & ~CMDPREVIEW);
+ close_preview_windows();
update_screen(SOME_VALID); // Clear 'inccommand' preview.
}
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index f29304867a..20f0cdccc3 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -569,20 +569,21 @@ readfile(
return FAIL;
}
}
- if (dir_of_file_exists(fname))
- filemess(curbuf, sfname, (char_u *)_("[New File]"), 0);
- else
- filemess(curbuf, sfname,
- (char_u *)_("[New DIRECTORY]"), 0);
- /* Even though this is a new file, it might have been
- * edited before and deleted. Get the old marks. */
+ if (dir_of_file_exists(fname)) {
+ filemess(curbuf, sfname, (char_u *)new_file_message(), 0);
+ } else {
+ filemess(curbuf, sfname, (char_u *)_("[New DIRECTORY]"), 0);
+ }
+ // Even though this is a new file, it might have been
+ // edited before and deleted. Get the old marks.
check_marks_read();
- /* Set forced 'fileencoding'. */
- if (eap != NULL)
+ // Set forced 'fileencoding'.
+ if (eap != NULL) {
set_forced_fenc(eap);
+ }
apply_autocmds_exarg(EVENT_BUFNEWFILE, sfname, sfname,
- FALSE, curbuf, eap);
- /* remember the current fileformat */
+ false, curbuf, eap);
+ // remember the current fileformat
save_file_ff(curbuf);
if (aborting()) /* autocmds may abort script processing */
@@ -2203,6 +2204,11 @@ static void check_marks_read(void)
curbuf->b_marks_read = true;
}
+char *new_file_message(void)
+{
+ return shortmess(SHM_NEW) ? _("[New]") : _("[New File]");
+}
+
/*
* buf_write() - write to file "fname" lines "start" through "end"
*
@@ -3513,8 +3519,8 @@ restore_backup:
STRCAT(IObuff, _("[Device]"));
c = TRUE;
} else if (newfile) {
- STRCAT(IObuff, shortmess(SHM_NEW) ? _("[New]") : _("[New File]"));
- c = TRUE;
+ STRCAT(IObuff, new_file_message());
+ c = true;
}
if (no_eol) {
msg_add_eol();
diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c
index f1f84e63be..4a64cc31b1 100644
--- a/src/nvim/hardcopy.c
+++ b/src/nvim/hardcopy.c
@@ -210,10 +210,25 @@ struct prt_ps_mbfont_S {
char *defcs;
};
+// Types of PS resource file currently used
+typedef enum {
+ PRT_RESOURCE_TYPE_PROCSET = 0,
+ PRT_RESOURCE_TYPE_ENCODING = 1,
+ PRT_RESOURCE_TYPE_CMAP = 2,
+} PrtResourceType;
+
+// String versions of PS resource types
+static const char *const prt_resource_types[] =
+{
+ [PRT_RESOURCE_TYPE_PROCSET] = "procset",
+ [PRT_RESOURCE_TYPE_ENCODING] = "encoding",
+ [PRT_RESOURCE_TYPE_CMAP] = "cmap",
+};
+
struct prt_ps_resource_S {
char_u name[64];
char_u filename[MAXPATHL + 1];
- int type;
+ PrtResourceType type;
char_u title[256];
char_u version[256];
};
@@ -1171,11 +1186,6 @@ static struct prt_ps_mbfont_S prt_ps_mbfonts[] =
}
};
-// Types of PS resource file currently used
-#define PRT_RESOURCE_TYPE_PROCSET (0)
-#define PRT_RESOURCE_TYPE_ENCODING (1)
-#define PRT_RESOURCE_TYPE_CMAP (2)
-
/* The PS prolog file version number has to match - if the prolog file is
* updated, increment the number in the file and here. Version checking was
* added as of VIM 6.2.
@@ -1189,16 +1199,6 @@ static struct prt_ps_mbfont_S prt_ps_mbfonts[] =
#define PRT_PROLOG_VERSION ((char_u *)"1.4")
#define PRT_CID_PROLOG_VERSION ((char_u *)"1.0")
-/* String versions of PS resource types - indexed by constants above so don't
- * re-order!
- */
-static char *prt_resource_types[] =
-{
- "procset",
- "encoding",
- "cmap"
-};
-
// Strings to look for in a PS resource file
#define PRT_RESOURCE_HEADER "%!PS-Adobe-"
#define PRT_RESOURCE_RESOURCE "Resource-"
@@ -1845,10 +1845,11 @@ static void prt_dsc_ints(char *comment, int count, int *ints)
}
static void prt_dsc_resources(
- char *comment, // if NULL add to previous
- char *type,
- char *string
+ const char *comment, // if NULL add to previous
+ const char *type,
+ const char *string
)
+ FUNC_ATTR_NONNULL_ARG(2, 3)
{
if (comment != NULL)
vim_snprintf((char *)prt_line_buffer, sizeof(prt_line_buffer),
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
index 69114c967d..32e804d213 100644
--- a/src/nvim/lua/converter.c
+++ b/src/nvim/lua/converter.c
@@ -19,6 +19,7 @@
#include "nvim/globals.h"
#include "nvim/message.h"
#include "nvim/eval/typval.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/ascii.h"
#include "nvim/macros.h"
@@ -50,6 +51,7 @@ typedef struct {
#define LUA_PUSH_STATIC_STRING(lstate, s) \
lua_pushlstring(lstate, s, sizeof(s) - 1)
+
static LuaTableProps nlua_traverse_table(lua_State *const lstate)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
@@ -314,6 +316,13 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
break;
}
case LUA_TTABLE: {
+ // Only need to track table refs if we have a metatable associated.
+ LuaRef table_ref = LUA_NOREF;
+ if (lua_getmetatable(lstate, -1)) {
+ lua_pop(lstate, 1);
+ table_ref = nlua_ref(lstate, -1);
+ }
+
const LuaTableProps table_props = nlua_traverse_table(lstate);
for (size_t i = 0; i < kv_size(stack); i++) {
@@ -329,6 +338,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
case kObjectTypeArray: {
cur.tv->v_type = VAR_LIST;
cur.tv->vval.v_list = tv_list_alloc((ptrdiff_t)table_props.maxidx);
+ cur.tv->vval.v_list->lua_table_ref = table_ref;
tv_list_ref(cur.tv->vval.v_list);
if (table_props.maxidx != 0) {
cur.container = true;
@@ -342,6 +352,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
cur.tv->v_type = VAR_DICT;
cur.tv->vval.v_dict = tv_dict_alloc();
cur.tv->vval.v_dict->dv_refcount++;
+ cur.tv->vval.v_dict->lua_table_ref = table_ref;
} else {
cur.special = table_props.has_string_with_nul;
if (table_props.has_string_with_nul) {
@@ -352,11 +363,13 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
S_LEN("_VAL"));
assert(val_di != NULL);
cur.tv = &val_di->di_tv;
+ cur.tv->vval.v_list->lua_table_ref = table_ref;
assert(cur.tv->v_type == VAR_LIST);
} else {
cur.tv->v_type = VAR_DICT;
cur.tv->vval.v_dict = tv_dict_alloc();
cur.tv->vval.v_dict->dv_refcount++;
+ cur.tv->vval.v_dict->lua_table_ref = table_ref;
}
cur.container = true;
cur.idx = lua_gettop(lstate);
@@ -384,6 +397,20 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
nlua_pop_typval_table_processing_end:
break;
}
+ 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,
+ &nlua_CFunction_func_free,
+ state);
+
+ cur.tv->v_type = VAR_FUNC;
+ cur.tv->vval.v_string = vim_strsave(name);
+ break;
+ }
case LUA_TUSERDATA: {
nlua_pushref(lstate, nlua_nil_ref);
bool is_nil = lua_rawequal(lstate, -2, -1);
diff --git a/src/nvim/lua/converter.h b/src/nvim/lua/converter.h
index 542c56ea3e..8601a32418 100644
--- a/src/nvim/lua/converter.h
+++ b/src/nvim/lua/converter.h
@@ -9,6 +9,15 @@
#include "nvim/func_attr.h"
#include "nvim/eval.h"
+typedef struct {
+ LuaRef func_ref;
+ LuaRef table_ref;
+} LuaCallable;
+
+typedef struct {
+ LuaCallable lua_callable;
+} LuaCFunctionState;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/converter.h.generated.h"
#endif
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index 327ed6d6b7..9f30609d66 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -35,8 +35,8 @@
#include "nvim/os/os.h"
#endif
-#include "nvim/lua/executor.h"
#include "nvim/lua/converter.h"
+#include "nvim/lua/executor.h"
#include "nvim/lua/treesitter.h"
#include "luv/luv.h"
@@ -53,6 +53,15 @@ typedef struct {
# include "lua/executor.c.generated.h"
#endif
+#define PUSH_ALL_TYPVALS(lstate, args, argcount, special) \
+ for (int i = 0; i < argcount; i++) { \
+ if (args[i].v_type == VAR_UNKNOWN) { \
+ lua_pushnil(lstate); \
+ } else { \
+ nlua_push_typval(lstate, &args[i], special); \
+ } \
+ }
+
/// Convert lua error into a Vim error message
///
/// @param lstate Lua interpreter state.
@@ -700,24 +709,25 @@ int nlua_call(lua_State *lstate)
}
TRY_WRAP({
- // TODO(bfredl): this should be simplified in error handling refactor
- force_abort = false;
- suppress_errthrow = false;
- current_exception = NULL;
- did_emsg = false;
-
- try_start();
- typval_T rettv;
- int dummy;
- // call_func() retval is deceptive, ignore it. Instead we set `msg_list`
- // (TRY_WRAP) to capture abort-causing non-exception errors.
- (void)call_func(name, (int)name_len, &rettv, nargs,
- vim_args, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum,
- &dummy, true, NULL, NULL);
- if (!try_end(&err)) {
- nlua_push_typval(lstate, &rettv, false);
- }
- tv_clear(&rettv);
+ // TODO(bfredl): this should be simplified in error handling refactor
+ force_abort = false;
+ suppress_errthrow = false;
+ current_exception = NULL;
+ did_emsg = false;
+
+ try_start();
+ typval_T rettv;
+ int dummy;
+ // call_func() retval is deceptive, ignore it. Instead we set `msg_list`
+ // (TRY_WRAP) to capture abort-causing non-exception errors.
+ (void)call_func(name, (int)name_len, &rettv, nargs,
+ vim_args, NULL,
+ curwin->w_cursor.lnum, curwin->w_cursor.lnum,
+ &dummy, true, NULL, NULL);
+ if (!try_end(&err)) {
+ nlua_push_typval(lstate, &rettv, false);
+ }
+ tv_clear(&rettv);
});
free_vim_args:
@@ -833,12 +843,25 @@ void executor_free_luaref(LuaRef ref)
nlua_unref(lstate, ref);
}
-/// push a value referenced in the regirstry
+/// push a value referenced in the registry
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;
+}
+
/// Evaluate lua string
///
/// Used for luaeval().
@@ -916,13 +939,8 @@ static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name,
return;
}
- for (int i = 0; i < argcount; i++) {
- if (args[i].v_type == VAR_UNKNOWN) {
- lua_pushnil(lstate);
- } else {
- nlua_push_typval(lstate, &args[i], special);
- }
- }
+ PUSH_ALL_TYPVALS(lstate, args, argcount, special);
+
if (lua_pcall(lstate, argcount, ret_tv ? 1 : 0, 0)) {
nlua_error(lstate, _("E5108: Error executing lua %.*s"));
return;
@@ -933,6 +951,51 @@ static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name,
}
}
+/// Call a LuaCallable given some typvals
+///
+/// Used to call any lua callable passed from Lua into VimL
+///
+/// @param[in] lstate Lua State
+/// @param[in] lua_cb Lua Callable
+/// @param[in] argcount Count of typval arguments
+/// @param[in] argvars Typval Arguments
+/// @param[out] rettv The return value from the called function.
+int typval_exec_lua_callable(
+ lua_State *lstate,
+ LuaCallable lua_cb,
+ int argcount,
+ typval_T *argvars,
+ 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)) {
+ luaL_error(lstate, "nlua_CFunction_func_call failed.");
+ return ERROR_OTHER;
+ }
+
+ nlua_pop_typval(lstate, rettv);
+
+ return ERROR_NONE;
+}
+
/// Execute Lua string
///
/// Used for nvim_exec_lua().
@@ -1128,21 +1191,11 @@ void ex_luafile(exarg_T *const eap)
}
}
-static int create_tslua_parser(lua_State *L)
-{
- if (lua_gettop(L) < 1 || !lua_isstring(L, 1)) {
- return luaL_error(L, "string expected");
- }
-
- const char *lang_name = lua_tostring(L, 1);
- return tslua_push_parser(L, lang_name);
-}
-
static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
{
tslua_init(lstate);
- lua_pushcfunction(lstate, create_tslua_parser);
+ lua_pushcfunction(lstate, tslua_push_parser);
lua_setfield(lstate, -2, "_create_ts_parser");
lua_pushcfunction(lstate, tslua_add_language);
@@ -1290,3 +1343,115 @@ static int regex_match_line(lua_State *lstate)
return nret;
}
+
+int nlua_CFunction_func_call(
+ int argcount,
+ typval_T *argvars,
+ typval_T *rettv,
+ void *state)
+{
+ lua_State *const lstate = nlua_enter();
+ LuaCFunctionState *funcstate = (LuaCFunctionState *)state;
+
+ 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;
+ } 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 false;
+}
+
+char_u *nlua_register_table_as_callable(typval_T *const arg)
+{
+ if (!nlua_is_table_from_lua(arg)) {
+ return NULL;
+ }
+
+ LuaRef table_ref;
+ 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 {
+ return NULL;
+ }
+
+ lua_State *const lstate = nlua_enter();
+
+ int top = lua_gettop(lstate);
+
+ nlua_pushref(lstate, table_ref);
+ if (!lua_getmetatable(lstate, -1)) {
+ return NULL;
+ }
+
+ lua_getfield(lstate, -1, "__call");
+ if (!lua_isfunction(lstate, -1)) {
+ return NULL;
+ }
+
+ LuaRef new_table_ref = nlua_newref(lstate, table_ref);
+
+ 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);
+
+
+ lua_pop(lstate, 3);
+ 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;
+ }
+}
diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h
index 3259fc0fa1..6599b44584 100644
--- a/src/nvim/lua/executor.h
+++ b/src/nvim/lua/executor.h
@@ -8,6 +8,7 @@
#include "nvim/func_attr.h"
#include "nvim/eval/typval.h"
#include "nvim/ex_cmds_defs.h"
+#include "nvim/lua/converter.h"
// Generated by msgpack-gen.lua
void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL;
diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c
index 51d9549033..138031237e 100644
--- a/src/nvim/lua/treesitter.c
+++ b/src/nvim/lua/treesitter.c
@@ -20,6 +20,7 @@
#include "nvim/lua/treesitter.h"
#include "nvim/api/private/handle.h"
#include "nvim/memline.h"
+#include "nvim/buffer.h"
typedef struct {
TSParser *parser;
@@ -41,6 +42,7 @@ static struct luaL_Reg parser_meta[] = {
{ "parse_buf", parser_parse_buf },
{ "edit", parser_edit },
{ "tree", parser_tree },
+ { "set_included_ranges", parser_set_ranges },
{ NULL, NULL }
};
@@ -214,8 +216,13 @@ int tslua_inspect_lang(lua_State *L)
return 1;
}
-int tslua_push_parser(lua_State *L, const char *lang_name)
+int tslua_push_parser(lua_State *L)
{
+ // Gather language
+ if (lua_gettop(L) < 1 || !lua_isstring(L, 1)) {
+ return luaL_error(L, "string expected");
+ }
+ const char *lang_name = lua_tostring(L, 1);
TSLanguage *lang = pmap_get(cstr_t)(langs, lang_name);
if (!lang) {
return luaL_error(L, "no such language: %s", lang_name);
@@ -300,11 +307,13 @@ static int parser_parse_buf(lua_State *L)
}
long bufnr = lua_tointeger(L, 2);
- void *payload = handle_get_buffer(bufnr);
- if (!payload) {
+ buf_T *buf = handle_get_buffer(bufnr);
+
+ if (!buf) {
return luaL_error(L, "invalid buffer handle: %d", bufnr);
}
- TSInput input = { payload, input_cb, TSInputEncodingUTF8 };
+
+ TSInput input = { (void *)buf, input_cb, TSInputEncodingUTF8 };
TSTree *new_tree = ts_parser_parse(p->parser, p->tree, input);
uint32_t n_ranges = 0;
@@ -377,6 +386,57 @@ static int parser_edit(lua_State *L)
return 0;
}
+static int parser_set_ranges(lua_State *L)
+{
+ if (lua_gettop(L) < 2) {
+ return luaL_error(
+ L,
+ "not enough args to parser:set_included_ranges()");
+ }
+
+ TSLua_parser *p = parser_check(L);
+ if (!p || !p->tree) {
+ return 0;
+ }
+
+ if (!lua_istable(L, 2)) {
+ return luaL_error(
+ L,
+ "argument for parser:set_included_ranges() should be a table.");
+ }
+
+ size_t tbl_len = lua_objlen(L, 2);
+ TSRange *ranges = xmalloc(sizeof(TSRange) * tbl_len);
+
+
+ // [ parser, ranges ]
+ for (size_t index = 0; index < tbl_len; index++) {
+ lua_rawgeti(L, 2, index + 1); // [ parser, ranges, range ]
+
+ TSNode node;
+ if (!node_check(L, -1, &node)) {
+ xfree(ranges);
+ return luaL_error(
+ L,
+ "ranges should be tables of nodes.");
+ }
+ lua_pop(L, 1); // [ parser, ranges ]
+
+ ranges[index] = (TSRange) {
+ .start_point = ts_node_start_point(node),
+ .end_point = ts_node_end_point(node),
+ .start_byte = ts_node_start_byte(node),
+ .end_byte = ts_node_end_byte(node)
+ };
+ }
+
+ // This memcpies ranges, thus we can free it afterwards
+ ts_parser_set_included_ranges(p->parser, ranges, tbl_len);
+ xfree(ranges);
+
+ return 0;
+}
+
// Tree methods
@@ -459,9 +519,9 @@ static void push_node(lua_State *L, TSNode node, int uindex)
lua_setfenv(L, -2); // [udata]
}
-static bool node_check(lua_State *L, TSNode *res)
+static bool node_check(lua_State *L, int index, TSNode *res)
{
- TSNode *ud = luaL_checkudata(L, 1, "treesitter_node");
+ TSNode *ud = luaL_checkudata(L, index, "treesitter_node");
if (ud) {
*res = *ud;
return true;
@@ -473,7 +533,7 @@ static bool node_check(lua_State *L, TSNode *res)
static int node_tostring(lua_State *L)
{
TSNode node;
- if (!node_check(L, &node)) {
+ if (!node_check(L, 1, &node)) {
return 0;
}
lua_pushstring(L, "<node ");
@@ -486,7 +546,7 @@ static int node_tostring(lua_State *L)
static int node_eq(lua_State *L)
{
TSNode node;
- if (!node_check(L, &node)) {
+ if (!node_check(L, 1, &node)) {
return 0;
}
// This should only be called if both x and y in "x == y" has the
@@ -503,7 +563,7 @@ static int node_eq(lua_State *L)
static int node_range(lua_State *L)
{
TSNode node;
- if (!node_check(L, &node)) {
+ if (!node_check(L, 1, &node)) {
return 0;
}
TSPoint start = ts_node_start_point(node);
@@ -518,7 +578,7 @@ static int node_range(lua_State *L)
static int node_start(lua_State *L)
{
TSNode node;
- if (!node_check(L, &node)) {
+ if (!node_check(L, 1, &node)) {
return 0;
}
TSPoint start = ts_node_start_point(node);
@@ -532,7 +592,7 @@ static int node_start(lua_State *L)
static int node_end(lua_State *L)
{
TSNode node;
- if (!node_check(L, &node)) {
+ if (!node_check(L, 1, &node)) {
return 0;
}
TSPoint end = ts_node_end_point(node);
@@ -546,7 +606,7 @@ static int node_end(lua_State *L)
static int node_child_count(lua_State *L)
{
TSNode node;
- if (!node_check(L, &node)) {
+ if (!node_check(L, 1, &node)) {
return 0;
}
uint32_t count = ts_node_child_count(node);
@@ -557,7 +617,7 @@ static int node_child_count(lua_State *L)
static int node_named_child_count(lua_State *L)
{
TSNode node;
- if (!node_check(L, &node)) {
+ if (!node_check(L, 1, &node)) {
return 0;
}
uint32_t count = ts_node_named_child_count(node);
@@ -568,7 +628,7 @@ static int node_named_child_count(lua_State *L)
static int node_type(lua_State *L)
{
TSNode node;
- if (!node_check(L, &node)) {
+ if (!node_check(L, 1, &node)) {
return 0;
}
lua_pushstring(L, ts_node_type(node));
@@ -578,7 +638,7 @@ static int node_type(lua_State *L)
static int node_symbol(lua_State *L)
{
TSNode node;
- if (!node_check(L, &node)) {
+ if (!node_check(L, 1, &node)) {
return 0;
}
TSSymbol symbol = ts_node_symbol(node);
@@ -589,7 +649,7 @@ static int node_symbol(lua_State *L)
static int node_named(lua_State *L)
{
TSNode node;
- if (!node_check(L, &node)) {
+ if (!node_check(L, 1, &node)) {
return 0;
}
lua_pushboolean(L, ts_node_is_named(node));
@@ -599,7 +659,7 @@ static int node_named(lua_State *L)
static int node_sexpr(lua_State *L)
{
TSNode node;
- if (!node_check(L, &node)) {
+ if (!node_check(L, 1, &node)) {
return 0;
}
char *allocated = ts_node_string(node);
@@ -611,7 +671,7 @@ static int node_sexpr(lua_State *L)
static int node_missing(lua_State *L)
{
TSNode node;
- if (!node_check(L, &node)) {
+ if (!node_check(L, 1, &node)) {
return 0;
}
lua_pushboolean(L, ts_node_is_missing(node));
@@ -621,7 +681,7 @@ static int node_missing(lua_State *L)
static int node_has_error(lua_State *L)
{
TSNode node;
- if (!node_check(L, &node)) {
+ if (!node_check(L, 1, &node)) {
return 0;
}
lua_pushboolean(L, ts_node_has_error(node));
@@ -631,7 +691,7 @@ static int node_has_error(lua_State *L)
static int node_child(lua_State *L)
{
TSNode node;
- if (!node_check(L, &node)) {
+ if (!node_check(L, 1, &node)) {
return 0;
}
long num = lua_tointeger(L, 2);
@@ -644,7 +704,7 @@ static int node_child(lua_State *L)
static int node_named_child(lua_State *L)
{
TSNode node;
- if (!node_check(L, &node)) {
+ if (!node_check(L, 1, &node)) {
return 0;
}
long num = lua_tointeger(L, 2);
@@ -657,7 +717,7 @@ static int node_named_child(lua_State *L)
static int node_descendant_for_range(lua_State *L)
{
TSNode node;
- if (!node_check(L, &node)) {
+ if (!node_check(L, 1, &node)) {
return 0;
}
TSPoint start = { (uint32_t)lua_tointeger(L, 2),
@@ -673,7 +733,7 @@ static int node_descendant_for_range(lua_State *L)
static int node_named_descendant_for_range(lua_State *L)
{
TSNode node;
- if (!node_check(L, &node)) {
+ if (!node_check(L, 1, &node)) {
return 0;
}
TSPoint start = { (uint32_t)lua_tointeger(L, 2),
@@ -689,7 +749,7 @@ static int node_named_descendant_for_range(lua_State *L)
static int node_parent(lua_State *L)
{
TSNode node;
- if (!node_check(L, &node)) {
+ if (!node_check(L, 1, &node)) {
return 0;
}
TSNode parent = ts_node_parent(node);
@@ -771,7 +831,7 @@ static int query_next_capture(lua_State *L)
static int node_rawquery(lua_State *L)
{
TSNode node;
- if (!node_check(L, &node)) {
+ if (!node_check(L, 1, &node)) {
return 0;
}
TSQuery *query = query_check(L, 2);
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
index 523d23ec12..820b237c4f 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -255,6 +255,8 @@ function vim.schedule_wrap(cb)
end)
end
+--- <Docs described in |vim.empty_dict()| >
+--@private
function vim.empty_dict()
return setmetatable({}, vim._empty_dict_mt)
end
@@ -270,6 +272,10 @@ vim.fn = setmetatable({}, {
end
})
+vim.funcref = function(viml_func_name)
+ return vim.fn[viml_func_name]
+end
+
-- These are for loading runtime modules lazily since they aren't available in
-- the nvim binary as specified in executor.c
local function __index(t, key)
@@ -286,6 +292,9 @@ local function __index(t, key)
elseif key == 'lsp' then
t.lsp = require('vim.lsp')
return t.lsp
+ elseif key == 'highlight' then
+ t.highlight = require('vim.highlight')
+ return t.highlight
end
end
@@ -462,6 +471,8 @@ end
--- Defers calling `fn` until `timeout` ms passes.
---
--- Use to do a one-shot timer that calls `fn`
+--- Note: The {fn} is |schedule_wrap|ped automatically, so API functions are
+--- safe to call.
--@param fn Callback to call once `timeout` expires
--@param timeout Number of milliseconds to wait before calling `fn`
--@return timer luv timer object
diff --git a/src/nvim/main.c b/src/nvim/main.c
index b3a903cbe3..f79fb57eae 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -84,44 +84,11 @@
#endif
#include "nvim/api/vim.h"
-// Maximum number of commands from + or -c arguments.
-#define MAX_ARG_CMDS 10
-
// values for "window_layout"
#define WIN_HOR 1 // "-o" horizontally split windows
#define WIN_VER 2 // "-O" vertically split windows
#define WIN_TABS 3 // "-p" windows on tab pages
-// Struct for various parameters passed between main() and other functions.
-typedef struct {
- int argc;
- char **argv;
-
- char *use_vimrc; // vimrc from -u argument
-
- int n_commands; // no. of commands from + or -c
- char *commands[MAX_ARG_CMDS]; // commands from + or -c arg
- char_u cmds_tofree[MAX_ARG_CMDS]; // commands that need free()
- int n_pre_commands; // no. of commands from --cmd
- char *pre_commands[MAX_ARG_CMDS]; // commands from --cmd argument
-
- int edit_type; // type of editing to do
- char_u *tagname; // tag from -t argument
- char_u *use_ef; // 'errorfile' from -q argument
-
- bool input_isatty; // stdin is a terminal
- bool output_isatty; // stdout is a terminal
- bool err_isatty; // stderr is a terminal
- int no_swap_file; // "-n" argument used
- int use_debug_break_level;
- int window_count; // number of windows to use
- int window_layout; // 0, WIN_HOR, WIN_VER or WIN_TABS
-
- int diff_mode; // start with 'diff' set
-
- char *listen_addr; // --listen {address}
-} mparm_T;
-
// Values for edit_type.
#define EDIT_NONE 0 // no edit type yet
#define EDIT_FILE 1 // file name argument[s] given, use argument list
@@ -188,7 +155,7 @@ bool event_teardown(void)
/// Performs early initialization.
///
/// Needed for unit tests. Must be called after `time_init()`.
-void early_init(void)
+void early_init(mparm_T *paramp)
{
env_init();
fs_init();
@@ -222,7 +189,7 @@ void early_init(void)
// msg_outtrans_len_attr().
// First find out the home directory, needed to expand "~" in options.
init_homedir(); // find real value of $HOME
- set_init_1();
+ set_init_1(paramp != NULL ? paramp->clean : false);
log_init();
TIME_MSG("inits 1");
@@ -265,9 +232,17 @@ int main(int argc, char **argv)
init_startuptime(&params);
+ // Need to find "--clean" before actually parsing arguments.
+ for (int i = 1; i < params.argc; i++) {
+ if (STRICMP(params.argv[i], "--clean") == 0) {
+ params.clean = true;
+ break;
+ }
+ }
+
event_init();
- early_init();
+ early_init(&params);
set_argv_var(argv, argc); // set v:argv
@@ -291,6 +266,12 @@ int main(int argc, char **argv)
fname = get_fname(&params, cwd);
}
+ // Recovery mode without a file name: List swap files.
+ // In this case, no UI is needed.
+ if (recoverymode && fname == NULL) {
+ headless_mode = true;
+ }
+
TIME_MSG("expanding arguments");
if (params.diff_mode && params.window_count == -1)
@@ -332,6 +313,26 @@ int main(int argc, char **argv)
input_start(STDIN_FILENO);
}
+ // Wait for UIs to set up Nvim or show early messages
+ // and prompts (--cmd, swapfile dialog, …).
+ bool use_remote_ui = (embedded_mode && !headless_mode);
+ bool use_builtin_ui = (!headless_mode && !embedded_mode && !silent_mode);
+ if (use_remote_ui || use_builtin_ui) {
+ TIME_MSG("waiting for UI");
+ if (use_remote_ui) {
+ remote_ui_wait_for_attach();
+ } else {
+ ui_builtin_start();
+ }
+ TIME_MSG("done waiting for UI");
+
+ // prepare screen now, so external UIs can display messages
+ starting = NO_BUFFERS;
+ screenclear();
+ TIME_MSG("initialized screen early for UI");
+ }
+
+
// open terminals when opening files that start with term://
#define PROTO "term://"
do_cmdline_cmd("augroup nvim_terminal");
@@ -354,25 +355,6 @@ int main(int argc, char **argv)
p_lpl = false;
}
- // Wait for UIs to set up Nvim or show early messages
- // and prompts (--cmd, swapfile dialog, …).
- bool use_remote_ui = (embedded_mode && !headless_mode);
- bool use_builtin_ui = (!headless_mode && !embedded_mode && !silent_mode);
- if (use_remote_ui || use_builtin_ui) {
- TIME_MSG("waiting for UI");
- if (use_remote_ui) {
- remote_ui_wait_for_attach();
- } else {
- ui_builtin_start();
- }
- TIME_MSG("done waiting for UI");
-
- // prepare screen now, so external UIs can display messages
- starting = NO_BUFFERS;
- screenclear();
- TIME_MSG("initialized screen early for UI");
- }
-
// Execute --cmd arguments.
exe_pre_commands(&params);
@@ -862,6 +844,7 @@ static void command_line_scan(mparm_T *parmp)
argv_idx += 11;
} else if (STRNICMP(argv[0] + argv_idx, "clean", 5) == 0) {
parmp->use_vimrc = "NONE";
+ parmp->clean = true;
set_option_value("shadafile", 0L, "NONE", 0);
} else {
if (argv[0][argv_idx])
@@ -978,7 +961,6 @@ static void command_line_scan(mparm_T *parmp)
case 'r': // "-r" recovery mode
case 'L': { // "-L" recovery mode
recoverymode = 1;
- headless_mode = true;
break;
}
case 's': {
@@ -1277,9 +1259,8 @@ static void init_params(mparm_T *paramp, int argc, char **argv)
/// Initialize global startuptime file if "--startuptime" passed as an argument.
static void init_startuptime(mparm_T *paramp)
{
- for (int i = 1; i < paramp->argc; i++) {
- if (STRICMP(paramp->argv[i], "--startuptime") == 0
- && i + 1 < paramp->argc) {
+ for (int i = 1; i < paramp->argc - 1; i++) {
+ if (STRICMP(paramp->argv[i], "--startuptime") == 0) {
time_fd = os_fopen(paramp->argv[i + 1], "a");
time_start("--- NVIM STARTING ---");
break;
diff --git a/src/nvim/main.h b/src/nvim/main.h
index 86d25fe657..61252f2bce 100644
--- a/src/nvim/main.h
+++ b/src/nvim/main.h
@@ -4,8 +4,42 @@
#include "nvim/normal.h"
#include "nvim/event/loop.h"
+// Maximum number of commands from + or -c arguments.
+#define MAX_ARG_CMDS 10
+
extern Loop main_loop;
+// Struct for various parameters passed between main() and other functions.
+typedef struct {
+ int argc;
+ char **argv;
+
+ char *use_vimrc; // vimrc from -u argument
+ bool clean; // --clean argument
+
+ int n_commands; // no. of commands from + or -c
+ char *commands[MAX_ARG_CMDS]; // commands from + or -c arg
+ char_u cmds_tofree[MAX_ARG_CMDS]; // commands that need free()
+ int n_pre_commands; // no. of commands from --cmd
+ char *pre_commands[MAX_ARG_CMDS]; // commands from --cmd argument
+
+ int edit_type; // type of editing to do
+ char_u *tagname; // tag from -t argument
+ char_u *use_ef; // 'errorfile' from -q argument
+
+ bool input_isatty; // stdin is a terminal
+ bool output_isatty; // stdout is a terminal
+ bool err_isatty; // stderr is a terminal
+ int no_swap_file; // "-n" argument used
+ int use_debug_break_level;
+ int window_count; // number of windows to use
+ int window_layout; // 0, WIN_HOR, WIN_VER or WIN_TABS
+
+ int diff_mode; // start with 'diff' set
+
+ char *listen_addr; // --listen {address}
+} mparm_T;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "main.h.generated.h"
#endif
diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c
index e10770b6bd..6dafbafb3e 100644
--- a/src/nvim/misc1.c
+++ b/src/nvim/misc1.c
@@ -1029,6 +1029,15 @@ void fast_breakcheck(void)
}
}
+// Like line_breakcheck() but check 100 times less often.
+void veryfast_breakcheck(void)
+{
+ if (++breakcheck_count >= BREAKCHECK_SKIP * 100) {
+ breakcheck_count = 0;
+ os_breakcheck();
+ }
+}
+
/// os_call_shell() wrapper. Handles 'verbose', :profile, and v:shell_error.
/// Invalidates cached tags.
///
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index eb32a1dd9b..595a699563 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -3080,10 +3080,12 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
if (ve_flags == VE_ALL
&& (curwin->w_cursor.coladd > 0
|| endcol2 == curwin->w_cursor.col)) {
- if (dir == FORWARD && c == NUL)
- ++col;
- if (dir != FORWARD && c != NUL)
- ++curwin->w_cursor.col;
+ if (dir == FORWARD && c == NUL) {
+ col++;
+ }
+ if (dir != FORWARD && c != NUL && curwin->w_cursor.coladd > 0) {
+ curwin->w_cursor.col++;
+ }
if (c == TAB) {
if (dir == BACKWARD && curwin->w_cursor.col)
curwin->w_cursor.col--;
@@ -4671,17 +4673,23 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
int maxlen = 0;
pos_T startpos;
pos_T endpos;
+ colnr_T save_coladd = 0;
dohex = (vim_strchr(curbuf->b_p_nf, 'x') != NULL); // "heX"
dooct = (vim_strchr(curbuf->b_p_nf, 'o') != NULL); // "Octal"
dobin = (vim_strchr(curbuf->b_p_nf, 'b') != NULL); // "Bin"
doalp = (vim_strchr(curbuf->b_p_nf, 'p') != NULL); // "alPha"
+ if (virtual_active()) {
+ save_coladd = pos->coladd;
+ pos->coladd = 0;
+ }
+
curwin->w_cursor = *pos;
ptr = ml_get(pos->lnum);
col = pos->col;
- if (*ptr == NUL) {
+ if (*ptr == NUL || col + !!save_coladd >= (int)STRLEN(ptr)) {
goto theend;
}
@@ -4976,6 +4984,8 @@ theend:
curwin->w_cursor = save_cursor;
} else if (did_change) {
curwin->w_set_curswant = true;
+ } else if (virtual_active()) {
+ curwin->w_cursor.coladd = save_coladd;
}
return did_change;
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 96f8e1529a..d789ad3587 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -524,11 +524,17 @@ char *get_lib_dir(void)
///
/// Windows: Uses "…/nvim-data" for kXDGDataHome to avoid storing
/// configuration and data files in the same path. #4403
-static void set_runtimepath_default(void)
+///
+/// If "clean_arg" is true, Nvim was started with --clean.
+static void set_runtimepath_default(bool clean_arg)
{
size_t rtp_size = 0;
- char *const data_home = stdpaths_get_xdg_var(kXDGDataHome);
- char *const config_home = stdpaths_get_xdg_var(kXDGConfigHome);
+ char *const data_home = clean_arg
+ ? NULL
+ : stdpaths_get_xdg_var(kXDGDataHome);
+ char *const config_home = clean_arg
+ ? NULL
+ : stdpaths_get_xdg_var(kXDGConfigHome);
char *const vimruntime = vim_getenv("VIMRUNTIME");
char *const libdir = get_lib_dir();
char *const data_dirs = stdpaths_get_xdg_var(kXDGDataDirs);
@@ -622,7 +628,8 @@ static void set_runtimepath_default(void)
/// Initialize the options, first part.
///
/// Called only once from main(), just after creating the first buffer.
-void set_init_1(void)
+/// If "clean_arg" is true, Nvim was started with --clean.
+void set_init_1(bool clean_arg)
{
int opt_idx;
@@ -765,7 +772,7 @@ void set_init_1(void)
true);
// Set default for &runtimepath. All necessary expansions are performed in
// this function.
- set_runtimepath_default();
+ set_runtimepath_default(clean_arg);
/*
* Set all the options (except the terminal options) to their default
@@ -2538,8 +2545,8 @@ static bool valid_filetype(const char_u *val)
return valid_name(val, ".-_");
}
-/// Return true if "val" is a valid 'spellang' value.
-bool valid_spellang(const char_u *val)
+/// Return true if "val" is a valid 'spelllang' value.
+bool valid_spelllang(const char_u *val)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
return valid_name(val, ".-_,@");
@@ -3071,7 +3078,7 @@ ambw_end:
const bool is_spellfile = varp == &(curwin->w_s->b_p_spf);
if ((is_spellfile && !valid_spellfile(*varp))
- || (!is_spellfile && !valid_spellang(*varp))) {
+ || (!is_spellfile && !valid_spelllang(*varp))) {
errmsg = e_invarg;
} else {
errmsg = did_set_spell_option(is_spellfile);
@@ -5469,9 +5476,6 @@ static int put_setstring(FILE *fd, char *cmd, char *name,
// replace home directory in the whole option value into "buf"
buf = xmalloc(size);
- if (buf == NULL) {
- goto fail;
- }
home_replace(NULL, *valuep, buf, size, false);
// If the option value is longer than MAXPATHL, we need to append
@@ -5479,10 +5483,7 @@ static int put_setstring(FILE *fd, char *cmd, char *name,
// can be expanded when read back.
if (size >= MAXPATHL && (flags & P_COMMA) != 0
&& vim_strchr(*valuep, ',') != NULL) {
- part = xmalloc(size);
- if (part == NULL) {
- goto fail;
- }
+ part = xmalloc(size);
// write line break to clear the option, e.g. ':set rtp='
if (put_eol(fd) == FAIL) {
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
index 2ca5f42e51..387732fdee 100644
--- a/src/nvim/regexp_nfa.c
+++ b/src/nvim/regexp_nfa.c
@@ -4960,7 +4960,7 @@ static long find_match_text(colnr_T startcol, int regstart, char_u *match_text)
int c2_len = PTR2LEN(s2);
int c2 = PTR2CHAR(s2);
- if ((c1 != c2 && (!rex.reg_ic || mb_tolower(c1) != mb_tolower(c2)))
+ if ((c1 != c2 && (!rex.reg_ic || utf_fold(c1) != utf_fold(c2)))
|| c1_len != c2_len) {
match = false;
break;
@@ -5682,11 +5682,11 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
break;
}
if (rex.reg_ic) {
- int curc_low = mb_tolower(curc);
+ int curc_low = utf_fold(curc);
int done = false;
for (; c1 <= c2; c1++) {
- if (mb_tolower(c1) == curc_low) {
+ if (utf_fold(c1) == curc_low) {
result = result_if_matched;
done = TRUE;
break;
@@ -5698,8 +5698,8 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
}
} else if (state->c < 0 ? check_char_class(state->c, curc)
: (curc == state->c
- || (rex.reg_ic && mb_tolower(curc)
- == mb_tolower(state->c)))) {
+ || (rex.reg_ic
+ && utf_fold(curc) == utf_fold(state->c)))) {
result = result_if_matched;
break;
}
@@ -6106,7 +6106,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
result = (c == curc);
if (!result && rex.reg_ic) {
- result = mb_tolower(c) == mb_tolower(curc);
+ result = utf_fold(c) == utf_fold(curc);
}
// If rex.reg_icombine is not set only skip over the character
@@ -6260,8 +6260,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
// Checking if the required start character matches is
// cheaper than adding a state that won't match.
c = PTR2CHAR(reginput + clen);
- if (c != prog->regstart && (!rex.reg_ic || mb_tolower(c)
- != mb_tolower(prog->regstart))) {
+ if (c != prog->regstart
+ && (!rex.reg_ic
+ || utf_fold(c) != utf_fold(prog->regstart))) {
#ifdef REGEXP_DEBUG
fprintf(log_fd,
" Skipping start state, regstart does not match\n");
diff --git a/src/nvim/search.c b/src/nvim/search.c
index 23086c629b..b105d99d7c 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -2326,9 +2326,9 @@ int findsent(Direction dir, long count)
func = decl;
while (count--) {
- /*
- * if on an empty line, skip up to a non-empty line
- */
+ const pos_T prev_pos = pos;
+
+ // if on an empty line, skip up to a non-empty line
if (gchar_pos(&pos) == NUL) {
do {
if ((*func)(&pos) == -1) {
@@ -2411,6 +2411,17 @@ found:
while (!noskip && ((c = gchar_pos(&pos)) == ' ' || c == '\t'))
if (incl(&pos) == -1)
break;
+
+ if (equalpos(prev_pos, pos)) {
+ // didn't actually move, advance one character and try again
+ if ((*func)(&pos) == -1) {
+ if (count) {
+ return FAIL;
+ }
+ break;
+ }
+ count++;
+ }
}
setpcmark();
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index 19a14f340b..95257fe945 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -2676,6 +2676,36 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer,
if (name == NULL) {
break;
}
+ switch (vartv.v_type) {
+ case VAR_FUNC:
+ case VAR_PARTIAL:
+ tv_clear(&vartv);
+ continue;
+ case VAR_DICT:
+ {
+ dict_T *di = vartv.vval.v_dict;
+ int copyID = get_copyID();
+ if (!set_ref_in_ht(&di->dv_hashtab, copyID, NULL)
+ && copyID == di->dv_copyID) {
+ tv_clear(&vartv);
+ continue;
+ }
+ break;
+ }
+ case VAR_LIST:
+ {
+ list_T *l = vartv.vval.v_list;
+ int copyID = get_copyID();
+ if (!set_ref_in_list(l, copyID, NULL)
+ && copyID == l->lv_copyID) {
+ tv_clear(&vartv);
+ continue;
+ }
+ break;
+ }
+ default:
+ break;
+ }
typval_T tgttv;
tv_copy(&vartv, &tgttv);
ShaDaWriteResult spe_ret;
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index 180073ade1..95948dac78 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -2007,7 +2007,7 @@ char_u *did_set_spelllang(win_T *wp)
region = NULL;
len = (int)STRLEN(lang);
- if (!valid_spellang(lang)) {
+ if (!valid_spelllang(lang)) {
continue;
}
@@ -2930,8 +2930,6 @@ void spell_suggest(int count)
memmove(p, line, c);
STRCPY(p + c, stp->st_word);
STRCAT(p, sug.su_badptr + stp->st_orglen);
- ml_replace(curwin->w_cursor.lnum, p, false);
- curwin->w_cursor.col = c;
// For redo we use a change-word command.
ResetRedobuff();
@@ -2940,7 +2938,10 @@ void spell_suggest(int count)
stp->st_wordlen + sug.su_badlen - stp->st_orglen);
AppendCharToRedobuff(ESC);
- // After this "p" may be invalid.
+ // "p" may be freed here
+ ml_replace(curwin->w_cursor.lnum, p, false);
+ curwin->w_cursor.col = c;
+
changed_bytes(curwin->w_cursor.lnum, c);
} else
curwin->w_cursor = prev_cursor;
@@ -3761,7 +3762,8 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
tword[sp->ts_twordlen] = NUL;
if (sp->ts_prefixdepth <= PFD_NOTSPECIAL
- && (sp->ts_flags & TSF_PREFIXOK) == 0) {
+ && (sp->ts_flags & TSF_PREFIXOK) == 0
+ && pbyts != NULL) {
// There was a prefix before the word. Check that the prefix
// can be used with this word.
// Count the length of the NULs in the prefix. If there are
diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c
index f8c10d0258..6b9348e55d 100644
--- a/src/nvim/spellfile.c
+++ b/src/nvim/spellfile.c
@@ -1134,7 +1134,6 @@ static int read_sal_section(FILE *fd, slang_T *slang)
salitem_T *smp;
int ccnt;
char_u *p;
- int c = NUL;
slang->sl_sofo = false;
@@ -1158,7 +1157,9 @@ static int read_sal_section(FILE *fd, slang_T *slang)
ga_grow(gap, cnt + 1);
// <sal> : <salfromlen> <salfrom> <saltolen> <salto>
- for (; gap->ga_len < cnt; ++gap->ga_len) {
+ for (; gap->ga_len < cnt; gap->ga_len++) {
+ int c = NUL;
+
smp = &((salitem_T *)gap->ga_data)[gap->ga_len];
ccnt = getc(fd); // <salfromlen>
if (ccnt < 0)
@@ -1810,7 +1811,8 @@ spell_reload_one (
#define CONDIT_SUF 4 // add a suffix for matching flags
#define CONDIT_AFF 8 // word already has an affix
-// Tunable parameters for when the tree is compressed. See 'mkspellmem'.
+// Tunable parameters for when the tree is compressed. Filled from the
+// 'mkspellmem' option.
static long compress_start = 30000; // memory / SBLOCKSIZE
static long compress_inc = 100; // memory / SBLOCKSIZE
static long compress_added = 500000; // word count
@@ -3015,6 +3017,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile)
char_u message[MAXLINELEN + MAXWLEN];
int flags;
int duplicate = 0;
+ Timestamp last_msg_time = 0;
// Open the file.
fd = os_fopen((char *)fname, "r");
@@ -3090,18 +3093,22 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile)
continue;
}
- // This takes time, print a message every 10000 words.
+ // This takes time, print a message every 10000 words, but not more
+ // often than once per second.
if (spin->si_verbose && spin->si_msg_count > 10000) {
spin->si_msg_count = 0;
- vim_snprintf((char *)message, sizeof(message),
- _("line %6d, word %6ld - %s"),
- lnum, spin->si_foldwcount + spin->si_keepwcount, w);
- msg_start();
- msg_puts_long_attr(message, 0);
- msg_clr_eos();
- msg_didout = FALSE;
- msg_col = 0;
- ui_flush();
+ if (os_time() > last_msg_time) {
+ last_msg_time = os_time();
+ vim_snprintf((char *)message, sizeof(message),
+ _("line %6d, word %6ld - %s"),
+ lnum, spin->si_foldwcount + spin->si_keepwcount, w);
+ msg_start();
+ msg_puts_long_attr(message, 0);
+ msg_clr_eos();
+ msg_didout = false;
+ msg_col = 0;
+ ui_flush();
+ }
}
// Store the word in the hashtable to be able to find duplicates.
@@ -3914,9 +3921,10 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int
++spin->si_msg_count;
if (spin->si_compress_cnt > 1) {
- if (--spin->si_compress_cnt == 1)
+ if (--spin->si_compress_cnt == 1) {
// Did enough words to lower the block count limit.
spin->si_blocks_cnt += compress_inc;
+ }
}
// When we have allocated lots of memory we need to compress the word tree
@@ -3955,9 +3963,10 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int
// compression useful, or one of them is small, which means
// compression goes fast. But when filling the soundfold word tree
// there is no keep-case tree.
- wordtree_compress(spin, spin->si_foldroot);
- if (affixID >= 0)
- wordtree_compress(spin, spin->si_keeproot);
+ wordtree_compress(spin, spin->si_foldroot, "case-folded");
+ if (affixID >= 0) {
+ wordtree_compress(spin, spin->si_keeproot, "keep-case");
+ }
}
return OK;
@@ -3990,6 +3999,7 @@ static wordnode_T *get_wordnode(spellinfo_T *spin)
// siblings.
// Returns the number of nodes actually freed.
static int deref_wordnode(spellinfo_T *spin, wordnode_T *node)
+ FUNC_ATTR_NONNULL_ALL
{
wordnode_T *np;
int cnt = 0;
@@ -4009,6 +4019,7 @@ static int deref_wordnode(spellinfo_T *spin, wordnode_T *node)
// Free a wordnode_T for re-use later.
// Only the "wn_child" field becomes invalid.
static void free_wordnode(spellinfo_T *spin, wordnode_T *n)
+ FUNC_ATTR_NONNULL_ALL
{
n->wn_child = spin->si_first_free;
spin->si_first_free = n;
@@ -4016,18 +4027,19 @@ static void free_wordnode(spellinfo_T *spin, wordnode_T *n)
}
// Compress a tree: find tails that are identical and can be shared.
-static void wordtree_compress(spellinfo_T *spin, wordnode_T *root)
+static void wordtree_compress(spellinfo_T *spin, wordnode_T *root,
+ const char *name)
+ FUNC_ATTR_NONNULL_ALL
{
hashtab_T ht;
- int n;
- int tot = 0;
- int perc;
+ long tot = 0;
+ long perc;
// Skip the root itself, it's not actually used. The first sibling is the
// start of the tree.
if (root->wn_sibling != NULL) {
hash_init(&ht);
- n = node_compress(spin, root->wn_sibling, &ht, &tot);
+ const long n = node_compress(spin, root->wn_sibling, &ht, &tot);
#ifndef SPELL_PRINTTREE
if (spin->si_verbose || p_verbose > 2)
@@ -4040,8 +4052,8 @@ static void wordtree_compress(spellinfo_T *spin, wordnode_T *root)
else
perc = (tot - n) * 100 / tot;
vim_snprintf((char *)IObuff, IOSIZE,
- _("Compressed %d of %d nodes; %d (%d%%) remaining"),
- n, tot, tot - n, perc);
+ _("Compressed %s of %ld nodes; %ld (%ld%%) remaining"),
+ name, tot, tot - n, perc);
spell_message(spin, IObuff);
}
#ifdef SPELL_PRINTTREE
@@ -4053,23 +4065,23 @@ static void wordtree_compress(spellinfo_T *spin, wordnode_T *root)
// Compress a node, its siblings and its children, depth first.
// Returns the number of compressed nodes.
-static int
-node_compress (
+static long node_compress(
spellinfo_T *spin,
wordnode_T *node,
hashtab_T *ht,
- int *tot // total count of nodes before compressing,
+ long *tot // total count of nodes before compressing,
// incremented while going through the tree
)
+ FUNC_ATTR_NONNULL_ALL
{
wordnode_T *np;
wordnode_T *tp;
wordnode_T *child;
hash_T hash;
hashitem_T *hi;
- int len = 0;
+ long len = 0;
unsigned nr, n;
- int compressed = 0;
+ long compressed = 0;
// Go through the list of siblings. Compress each child and then try
// finding an identical child to replace it.
@@ -4142,7 +4154,7 @@ node_compress (
node->wn_u1.hashkey[5] = NUL;
// Check for CTRL-C pressed now and then.
- fast_breakcheck();
+ veryfast_breakcheck();
return compressed;
}
@@ -4749,7 +4761,7 @@ static void spell_make_sugfile(spellinfo_T *spin, char_u *wfname)
// Compress the soundfold trie.
spell_message(spin, (char_u *)_(msg_compressing));
- wordtree_compress(spin, spin->si_foldroot);
+ wordtree_compress(spin, spin->si_foldroot, "case-folded");
// Write the .sug file.
// Make the file name by changing ".spl" to ".sug".
@@ -5095,7 +5107,8 @@ mkspell (
spin.si_newcompID = 127; // start compound ID at first maximum
// default: fnames[0] is output file, following are input files
- innames = &fnames[1];
+ // When "fcount" is 1 there is only one file.
+ innames = &fnames[fcount == 1 ? 0 : 1];
incount = fcount - 1;
wfname = xmalloc(MAXPATHL);
@@ -5105,12 +5118,10 @@ mkspell (
if (fcount == 1 && len > 4 && STRCMP(fnames[0] + len - 4, ".add") == 0) {
// For ":mkspell path/en.latin1.add" output file is
// "path/en.latin1.add.spl".
- innames = &fnames[0];
incount = 1;
vim_snprintf((char *)wfname, MAXPATHL, "%s.spl", fnames[0]);
} else if (fcount == 1) {
// For ":mkspell path/vim" output file is "path/vim.latin1.spl".
- innames = &fnames[0];
incount = 1;
vim_snprintf((char *)wfname, MAXPATHL, SPL_FNAME_TMPL,
fnames[0], spin.si_ascii ? (char_u *)"ascii" : spell_enc());
@@ -5220,9 +5231,9 @@ mkspell (
if (!error && !got_int) {
// Combine tails in the tree.
spell_message(&spin, (char_u *)_(msg_compressing));
- wordtree_compress(&spin, spin.si_foldroot);
- wordtree_compress(&spin, spin.si_keeproot);
- wordtree_compress(&spin, spin.si_prefroot);
+ wordtree_compress(&spin, spin.si_foldroot, "case-folded");
+ wordtree_compress(&spin, spin.si_keeproot, "keep-case");
+ wordtree_compress(&spin, spin.si_prefroot, "prefixes");
}
if (!error && !got_int) {
@@ -5274,7 +5285,8 @@ theend:
// Display a message for spell file processing when 'verbose' is set or using
// ":mkspell". "str" can be IObuff.
-static void spell_message(spellinfo_T *spin, char_u *str)
+static void spell_message(const spellinfo_T *spin, char_u *str)
+ FUNC_ATTR_NONNULL_ALL
{
if (spin->si_verbose || p_verbose > 2) {
if (!spin->si_verbose)
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index f3b05c3961..4aa7c21ce4 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -121,6 +121,8 @@ static int hl_attr_table[] =
{ HL_BOLD, HL_STANDOUT, HL_UNDERLINE, HL_UNDERCURL, HL_ITALIC, HL_INVERSE,
HL_INVERSE, HL_STRIKETHROUGH, HL_NOCOMBINE, 0 };
+static char e_illegal_arg[] = N_("E390: Illegal argument: %s");
+
// The patterns that are being searched for are stored in a syn_pattern.
// A match item consists of one pattern.
// A start/end item consists of n start patterns and m end patterns.
@@ -3045,7 +3047,7 @@ static void syn_cmd_conceal(exarg_T *eap, int syncing)
} else if (STRNICMP(arg, "off", 3) == 0 && next - arg == 3) {
curwin->w_s->b_syn_conceal = false;
} else {
- EMSG2(_("E390: Illegal argument: %s"), arg);
+ EMSG2(_(e_illegal_arg), arg);
}
}
@@ -3073,7 +3075,42 @@ static void syn_cmd_case(exarg_T *eap, int syncing)
} else if (STRNICMP(arg, "ignore", 6) == 0 && next - arg == 6) {
curwin->w_s->b_syn_ic = true;
} else {
- EMSG2(_("E390: Illegal argument: %s"), arg);
+ EMSG2(_(e_illegal_arg), arg);
+ }
+}
+
+/// Handle ":syntax foldlevel" command.
+static void syn_cmd_foldlevel(exarg_T *eap, int syncing)
+{
+ char_u *arg = eap->arg;
+ char_u *arg_end;
+
+ eap->nextcmd = find_nextcmd(arg);
+ if (eap->skip)
+ return;
+
+ if (*arg == NUL) {
+ switch (curwin->w_s->b_syn_foldlevel) {
+ case SYNFLD_START: MSG(_("syntax foldlevel start")); break;
+ case SYNFLD_MINIMUM: MSG(_("syntax foldlevel minimum")); break;
+ default: break;
+ }
+ return;
+ }
+
+ arg_end = skiptowhite(arg);
+ if (STRNICMP(arg, "start", 5) == 0 && arg_end - arg == 5) {
+ curwin->w_s->b_syn_foldlevel = SYNFLD_START;
+ } else if (STRNICMP(arg, "minimum", 7) == 0 && arg_end - arg == 7) {
+ curwin->w_s->b_syn_foldlevel = SYNFLD_MINIMUM;
+ } else {
+ EMSG2(_(e_illegal_arg), arg);
+ return;
+ }
+
+ arg = skipwhite(arg_end);
+ if (*arg != NUL) {
+ EMSG2(_(e_illegal_arg), arg);
}
}
@@ -3105,7 +3142,7 @@ static void syn_cmd_spell(exarg_T *eap, int syncing)
} else if (STRNICMP(arg, "default", 7) == 0 && next - arg == 7) {
curwin->w_s->b_syn_spell = SYNSPL_DEFAULT;
} else {
- EMSG2(_("E390: Illegal argument: %s"), arg);
+ EMSG2(_(e_illegal_arg), arg);
return;
}
@@ -3161,6 +3198,7 @@ void syntax_clear(synblock_T *block)
block->b_syn_error = false; // clear previous error
block->b_syn_slow = false; // clear previous timeout
block->b_syn_ic = false; // Use case, by default
+ block->b_syn_foldlevel = SYNFLD_START;
block->b_syn_spell = SYNSPL_DEFAULT; // default spell checking
block->b_syn_containedin = false;
block->b_syn_conceal = false;
@@ -5485,6 +5523,7 @@ static struct subcommand subcommands[] =
{ "cluster", syn_cmd_cluster },
{ "conceal", syn_cmd_conceal },
{ "enable", syn_cmd_enable },
+ { "foldlevel", syn_cmd_foldlevel },
{ "include", syn_cmd_include },
{ "iskeyword", syn_cmd_iskeyword },
{ "keyword", syn_cmd_keyword },
@@ -5763,6 +5802,17 @@ int syn_get_stack_item(int i)
return CUR_STATE(i).si_id;
}
+static int syn_cur_foldlevel(void)
+{
+ int level = 0;
+ for (int i = 0; i < current_state.ga_len; i++) {
+ if (CUR_STATE(i).si_flags & HL_FOLD) {
+ level++;
+ }
+ }
+ return level;
+}
+
/*
* Function called to get folding level for line "lnum" in window "wp".
*/
@@ -5776,9 +5826,22 @@ int syn_get_foldlevel(win_T *wp, long lnum)
&& !wp->w_s->b_syn_slow) {
syntax_start(wp, lnum);
- for (int i = 0; i < current_state.ga_len; ++i) {
- if (CUR_STATE(i).si_flags & HL_FOLD) {
- ++level;
+ // Start with the fold level at the start of the line.
+ level = syn_cur_foldlevel();
+
+ if (wp->w_s->b_syn_foldlevel == SYNFLD_MINIMUM) {
+ // Find the lowest fold level that is followed by a higher one.
+ int cur_level = level;
+ int low_level = cur_level;
+ while (!current_finished) {
+ (void)syn_current_attr(false, false, NULL, false);
+ cur_level = syn_cur_foldlevel();
+ if (cur_level < low_level) {
+ low_level = cur_level;
+ } else if (cur_level > low_level) {
+ level = low_level;
+ }
+ current_col++;
}
}
}
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index a096b77ac6..6a13341a89 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -489,7 +489,17 @@ static int terminal_execute(VimState *state, int key)
terminal_send_key(s->term, key);
}
- return curbuf->handle == s->term->buf_handle;
+ if (curbuf->terminal == NULL) {
+ return 0;
+ }
+ if (s->term != curbuf->terminal) {
+ invalidate_terminal(s->term, s->term->cursor.row, s->term->cursor.row + 1);
+ invalidate_terminal(curbuf->terminal,
+ curbuf->terminal->cursor.row,
+ curbuf->terminal->cursor.row + 1);
+ s->term = curbuf->terminal;
+ }
+ return 1;
}
void terminal_destroy(Terminal *term)
diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim
index b041fdedb1..41ff9b2bd6 100644
--- a/src/nvim/testdir/shared.vim
+++ b/src/nvim/testdir/shared.vim
@@ -275,7 +275,7 @@ func GetVimCommand(...)
" If using valgrind, make sure every run uses a different log file.
if cmd =~ 'valgrind.*--log-file='
- let cmd = substitute(cmd, '--log-file=\(^\s*\)', '--log-file=\1.' . g:valgrind_cnt, '')
+ let cmd = substitute(cmd, '--log-file=\(\S*\)', '--log-file=\1.' . g:valgrind_cnt, '')
let g:valgrind_cnt += 1
endif
diff --git a/src/nvim/testdir/summarize.vim b/src/nvim/testdir/summarize.vim
index 4e4135287b..7f8f758a71 100644
--- a/src/nvim/testdir/summarize.vim
+++ b/src/nvim/testdir/summarize.vim
@@ -28,7 +28,7 @@ if 1
" This uses the :s command to just fetch and process the output of the
" tests, it doesn't actually replace anything.
" And it uses "silent" to avoid reporting the number of matches.
- silent %s/^Executed\s\+\zs\d\+\ze\s\+tests\?/\=Count(submatch(0),'executed')/egn
+ silent %s/Executed\s\+\zs\d\+\ze\s\+tests\?/\=Count(submatch(0),'executed')/egn
silent %s/^SKIPPED \zs.*/\=Count(submatch(0), 'skipped')/egn
silent %s/^\(\d\+\)\s\+FAILED:/\=Count(submatch(1), 'failed')/egn
diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim
index 4cc90eca7a..b4f7478807 100644
--- a/src/nvim/testdir/test_assert.vim
+++ b/src/nvim/testdir/test_assert.vim
@@ -28,7 +28,18 @@ func Test_assert_equalfile()
call writefile(['1234X89'], 'Xone')
call writefile(['1234Y89'], 'Xtwo')
call assert_equal(1, assert_equalfile('Xone', 'Xtwo'))
- call assert_match("difference at byte 4", v:errors[0])
+ call assert_match('difference at byte 4, line 1 after "1234X" vs "1234Y"', v:errors[0])
+ call remove(v:errors, 0)
+
+ call writefile([repeat('x', 234) .. 'X'], 'Xone')
+ call writefile([repeat('x', 234) .. 'Y'], 'Xtwo')
+ call assert_equal(1, assert_equalfile('Xone', 'Xtwo'))
+ let xes = repeat('x', 134)
+ call assert_match('difference at byte 234, line 1 after "' .. xes .. 'X" vs "' .. xes .. 'Y"', v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, assert_equalfile('Xone', 'Xtwo', 'a message'))
+ call assert_match("a message: difference at byte 234, line 1 after", v:errors[0])
call remove(v:errors, 0)
call delete('Xone')
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index 5217aa7339..d116246ef3 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -1252,6 +1252,10 @@ func Test_TextYankPost()
call assert_equal(
\{'regcontents': ['foo'], 'inclusive': v:false, 'regname': '', 'operator': 'y', 'visual': v:false, 'regtype': 'V'},
\g:event)
+ norm Vy
+ call assert_equal(
+ \{'regcontents': ['foo'], 'inclusive': v:true, 'regname': '', 'operator': 'y', 'visual': v:true, 'regtype': 'V'},
+ \g:event)
call feedkeys("\<C-V>y", 'x')
call assert_equal(
\{'regcontents': ['f'], 'inclusive': v:true, 'regname': '', 'operator': 'y', 'visual': v:true, 'regtype': "\x161"},
diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim
index 42e18ed027..49bbe84869 100644
--- a/src/nvim/testdir/test_diffmode.vim
+++ b/src/nvim/testdir/test_diffmode.vim
@@ -800,3 +800,25 @@ func Test_diff_closeoff()
diffoff!
enew!
endfunc
+
+func Test_diff_and_scroll()
+ " this was causing an ml_get error
+ set ls=2
+ for i in range(winheight(0) * 2)
+ call setline(i, i < winheight(0) - 10 ? i : i + 10)
+ endfor
+ vnew
+ for i in range(winheight(0)*2 + 10)
+ call setline(i, i < winheight(0) - 10 ? 0 : i)
+ endfor
+ diffthis
+ wincmd p
+ diffthis
+ execute 'normal ' . winheight(0) . "\<C-d>"
+
+ bwipe!
+ bwipe!
+ set ls&
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index ffd2cee80f..d440bdcb1e 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -131,7 +131,7 @@ let s:filename_checks = {
\ 'def': ['file.def'],
\ 'denyhosts': ['denyhosts.conf'],
\ 'desc': ['file.desc'],
- \ 'desktop': ['file.desktop', '.directory'],
+ \ 'desktop': ['file.desktop', '.directory', 'file.directory'],
\ 'dictconf': ['dict.conf', '.dictrc'],
\ 'dictdconf': ['dictd.conf'],
\ 'diff': ['file.diff', 'file.rej'],
@@ -139,7 +139,7 @@ let s:filename_checks = {
\ 'dnsmasq': ['/etc/dnsmasq.conf'],
\ 'dockerfile': ['Dockerfile', 'file.Dockerfile'],
\ 'dosbatch': ['file.bat', 'file.sys'],
- \ 'dosini': ['.editorconfig', '/etc/yum.conf', 'file.ini'],
+ \ 'dosini': ['.editorconfig', '/etc/pacman.conf', '/etc/yum.conf', 'file.ini'],
\ 'dot': ['file.dot', 'file.gv'],
\ 'dracula': ['file.drac', 'file.drc', 'filelvs', 'filelpe'],
\ 'dsl': ['file.dsl'],
@@ -150,7 +150,7 @@ let s:filename_checks = {
\ 'dylanlid': ['file.lid'],
\ 'ecd': ['file.ecd'],
\ 'edif': ['file.edf', 'file.edif', 'file.edo'],
- \ 'elinks': ['/etc/elinks.conf', '/.elinks/elinks.conf'],
+ \ 'elinks': ['elinks.conf'],
\ 'elm': ['file.elm'],
\ 'elmfilt': ['filter-rules'],
\ 'erlang': ['file.erl', 'file.hrl', 'file.yaws'],
@@ -329,7 +329,7 @@ let s:filename_checks = {
\ 'pccts': ['file.g'],
\ 'pdf': ['file.pdf'],
\ 'perl': ['file.plx', 'file.al', 'file.psgi', 'gitolite.rc', '.gitolite.rc', 'example.gitolite.rc'],
- \ 'perl6': ['file.p6', 'file.pm6', 'file.pl6'],
+ \ 'perl6': ['file.p6', 'file.pm6', 'file.pl6', 'file.raku', 'file.rakumod'],
\ 'pf': ['pf.conf'],
\ 'pfmain': ['main.cf'],
\ 'php': ['file.php', 'file.php9', 'file.phtml', 'file.ctp'],
@@ -360,7 +360,7 @@ let s:filename_checks = {
\ 'protocols': ['/etc/protocols'],
\ 'psf': ['file.psf'],
\ 'pyrex': ['file.pyx', 'file.pxd'],
- \ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', 'file.ptl', 'file.pyi'],
+ \ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', 'file.ptl', 'file.pyi', 'SConstruct'],
\ 'quake': ['anybaseq2/file.cfg', 'anyid1/file.cfg', 'quake3/file.cfg'],
\ 'radiance': ['file.rad', 'file.mat'],
\ 'ratpoison': ['.ratpoisonrc', 'ratpoisonrc'],
@@ -427,8 +427,8 @@ let s:filename_checks = {
\ 'sqr': ['file.sqr', 'file.sqi'],
\ 'squid': ['squid.conf'],
\ 'srec': ['file.s19', 'file.s28', 'file.s37', 'file.mot', 'file.srec'],
- \ 'sshconfig': ['ssh_config', '/.ssh/config'],
- \ 'sshdconfig': ['sshd_config'],
+ \ 'sshconfig': ['ssh_config', '/.ssh/config', '/etc/ssh/ssh_config.d/file.conf', 'any/etc/ssh/ssh_config.d/file.conf'],
+ \ 'sshdconfig': ['sshd_config', '/etc/ssh/sshd_config.d/file.conf', 'any/etc/ssh/sshd_config.d/file.conf'],
\ 'st': ['file.st'],
\ 'stata': ['file.ado', 'file.do', 'file.imata', 'file.mata'],
\ 'stp': ['file.stp'],
@@ -439,7 +439,7 @@ let s:filename_checks = {
\ 'swiftgyb': ['file.swift.gyb'],
\ 'sil': ['file.sil'],
\ 'sysctl': ['/etc/sysctl.conf', '/etc/sysctl.d/file.conf'],
- \ 'systemd': ['any/systemd/file.automount', 'any/systemd/file.mount', 'any/systemd/file.path', 'any/systemd/file.service', 'any/systemd/file.socket', 'any/systemd/file.swap', 'any/systemd/file.target', 'any/systemd/file.timer', '/etc/systemd/system/some.d/file.conf', '/etc/systemd/system/some.d/.#file', '/etc/systemd/system/.#otherfile', '/home/user/.config/systemd/user/some.d/mine.conf', '/home/user/.config/systemd/user/some.d/.#file', '/home/user/.config/systemd/user/.#otherfile'],
+ \ 'systemd': ['any/systemd/file.automount', 'any/systemd/file.dnssd', 'any/systemd/file.link', 'any/systemd/file.mount', 'any/systemd/file.netdev', 'any/systemd/file.network', 'any/systemd/file.nspawn', 'any/systemd/file.path', 'any/systemd/file.service', 'any/systemd/file.slice', 'any/systemd/file.socket', 'any/systemd/file.swap', 'any/systemd/file.target', 'any/systemd/file.timer', '/etc/systemd/some.conf.d/file.conf', '/etc/systemd/system/some.d/file.conf', '/etc/systemd/system/some.d/.#file', '/etc/systemd/system/.#otherfile', '/home/user/.config/systemd/user/some.d/mine.conf', '/home/user/.config/systemd/user/some.d/.#file', '/home/user/.config/systemd/user/.#otherfile'],
\ 'systemverilog': ['file.sv', 'file.svh'],
\ 'tags': ['tags'],
\ 'tak': ['file.tak'],
@@ -606,9 +606,19 @@ let s:script_checks = {
\ 'yaml': [['%YAML 1.2']],
\ }
-func Test_script_detection()
+" Various forms of "env" optional arguments.
+let s:script_env_checks = {
+ \ 'perl': [['#!/usr/bin/env VAR=val perl']],
+ \ 'scala': [['#!/usr/bin/env VAR=val VVAR=vval scala']],
+ \ 'awk': [['#!/usr/bin/env VAR=val -i awk']],
+ \ 'scheme': [['#!/usr/bin/env VAR=val --ignore-environment scheme']],
+ \ 'python': [['#!/usr/bin/env VAR=val -S python -w -T']],
+ \ 'wml': [['#!/usr/bin/env VAR=val --split-string wml']],
+ \ }
+
+func Run_script_detection(test_dict)
filetype on
- for [ft, files] in items(s:script_checks)
+ for [ft, files] in items(a:test_dict)
for file in files
call writefile(file, 'Xtest')
split Xtest
@@ -620,7 +630,32 @@ func Test_script_detection()
filetype off
endfunc
+func Test_script_detection()
+ call Run_script_detection(s:script_checks)
+ call Run_script_detection(s:script_env_checks)
+endfunc
+
func Test_setfiletype_completion()
call feedkeys(":setfiletype java\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"setfiletype java javacc javascript javascriptreact', @:)
endfunc
+
+func Test_hook_file()
+ filetype on
+
+ call writefile(['[Trigger]', 'this is pacman config'], 'Xfile.hook')
+ split Xfile.hook
+ call assert_equal('dosini', &filetype)
+ bwipe!
+
+ call writefile(['not pacman'], 'Xfile.hook')
+ split Xfile.hook
+ call assert_notequal('dosini', &filetype)
+ bwipe!
+
+ call delete('Xfile.hook')
+ filetype off
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_increment.vim b/src/nvim/testdir/test_increment.vim
index ab11d943d9..f81f8edbde 100644
--- a/src/nvim/testdir/test_increment.vim
+++ b/src/nvim/testdir/test_increment.vim
@@ -779,4 +779,40 @@ func Test_increment_empty_line()
bwipe!
endfunc
+func Test_normal_increment_with_virtualedit()
+ set virtualedit=all
+
+ call setline(1, ["\<TAB>1"])
+ exec "norm! 0\<C-A>"
+ call assert_equal("\<TAB>2", getline(1))
+ call assert_equal([0, 1, 2, 0], getpos('.'))
+
+ call setline(1, ["\<TAB>1"])
+ exec "norm! 0l\<C-A>"
+ call assert_equal("\<TAB>2", getline(1))
+ call assert_equal([0, 1, 2, 0], getpos('.'))
+
+ call setline(1, ["\<TAB>1"])
+ exec "norm! 07l\<C-A>"
+ call assert_equal("\<TAB>2", getline(1))
+ call assert_equal([0, 1, 2, 0], getpos('.'))
+
+ call setline(1, ["\<TAB>1"])
+ exec "norm! 0w\<C-A>"
+ call assert_equal("\<TAB>2", getline(1))
+ call assert_equal([0, 1, 2, 0], getpos('.'))
+
+ call setline(1, ["\<TAB>1"])
+ exec "norm! 0wl\<C-A>"
+ call assert_equal("\<TAB>1", getline(1))
+ call assert_equal([0, 1, 3, 0], getpos('.'))
+
+ call setline(1, ["\<TAB>1"])
+ exec "norm! 0w30l\<C-A>"
+ call assert_equal("\<TAB>1", getline(1))
+ call assert_equal([0, 1, 3, 29], getpos('.'))
+
+ set virtualedit&
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim
index ecd0e8d56b..f48458566b 100644
--- a/src/nvim/testdir/test_regexp_utf8.vim
+++ b/src/nvim/testdir/test_regexp_utf8.vim
@@ -332,4 +332,23 @@ func Test_ambiwidth()
set regexpengine& ambiwidth&
endfunc
+func Run_regexp_ignore_case()
+ call assert_equal('iIİ', substitute('iIİ', '\([iIİ]\)', '\1', 'g'))
+
+ call assert_equal('iIx', substitute('iIİ', '\c\([İ]\)', 'x', 'g'))
+ call assert_equal('xxİ', substitute('iIİ', '\(i\c\)', 'x', 'g'))
+ call assert_equal('iIx', substitute('iIİ', '\(İ\c\)', 'x', 'g'))
+ call assert_equal('iIx', substitute('iIİ', '\c\(\%u0130\)', 'x', 'g'))
+ call assert_equal('iIx', substitute('iIİ', '\c\([\u0130]\)', 'x', 'g'))
+ call assert_equal('iIx', substitute('iIİ', '\c\([\u012f-\u0131]\)', 'x', 'g'))
+endfunc
+
+func Test_regexp_ignore_case()
+ set regexpengine=1
+ call Run_regexp_ignore_case()
+ set regexpengine=2
+ call Run_regexp_ignore_case()
+ set regexpengine&
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim
index d4f58af10a..d20f8d1eef 100644
--- a/src/nvim/testdir/test_registers.vim
+++ b/src/nvim/testdir/test_registers.vim
@@ -167,4 +167,22 @@ func Test_set_register()
enew!
endfunc
+func Test_ve_blockpaste()
+ new
+ set ve=all
+ 0put =['QWERTZ','ASDFGH']
+ call cursor(1,1)
+ exe ":norm! \<C-V>3ljdP"
+ call assert_equal(1, col('.'))
+ call assert_equal(getline(1, 2), ['QWERTZ', 'ASDFGH'])
+ call cursor(1,1)
+ exe ":norm! \<C-V>3ljd"
+ call cursor(1,1)
+ norm! $3lP
+ call assert_equal(5, col('.'))
+ call assert_equal(getline(1, 2), ['TZ QWER', 'GH ASDF'])
+ set ve&vim
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim
index 66b6e6c05c..8c81ec3431 100644
--- a/src/nvim/testdir/test_statusline.vim
+++ b/src/nvim/testdir/test_statusline.vim
@@ -7,6 +7,7 @@
" %X
source view_util.vim
+source term_util.vim
func s:get_statusline()
return ScreenLines(&lines - 1, &columns)[0]
@@ -29,7 +30,9 @@ endfunc
" Function used to display syntax group.
func SyntaxItem()
- return synIDattr(synID(line("."),col("."),1),"name")
+ call assert_equal(s:expected_curbuf, g:actual_curbuf)
+ call assert_equal(s:expected_curwin, g:actual_curwin)
+ return synIDattr(synID(line("."), col("."),1), "name")
endfunc
func Test_caught_error_in_statusline()
@@ -218,6 +221,8 @@ func Test_statusline()
"%{: Evaluate expression between '%{' and '}' and substitute result.
syntax on
+ let s:expected_curbuf = string(bufnr(''))
+ let s:expected_curwin = string(win_getid())
set statusline=%{SyntaxItem()}
call assert_match('^vimNumber\s*$', s:get_statusline())
s/^/"/
@@ -332,6 +337,23 @@ func Test_statusline()
set statusline=%!2*3+1
call assert_match('7\s*$', s:get_statusline())
+ func GetNested()
+ call assert_equal(string(win_getid()), g:actual_curwin)
+ call assert_equal(string(bufnr('')), g:actual_curbuf)
+ return 'nested'
+ endfunc
+ func GetStatusLine()
+ call assert_equal(win_getid(), g:statusline_winid)
+ return 'the %{GetNested()} line'
+ endfunc
+ set statusline=%!GetStatusLine()
+ call assert_match('the nested line', s:get_statusline())
+ call assert_false(exists('g:actual_curwin'))
+ call assert_false(exists('g:actual_curbuf'))
+ call assert_false(exists('g:statusline_winid'))
+ delfunc GetNested
+ delfunc GetStatusLine
+
" Check statusline in current and non-current window
" with the 'fillchars' option.
set fillchars=stl:^,stlnc:=,vert:\|,fold:-,diff:-
diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim
index 6cada1503f..85ee42420e 100644
--- a/src/nvim/testdir/test_syntax.vim
+++ b/src/nvim/testdir/test_syntax.vim
@@ -153,7 +153,7 @@ endfunc
func Test_syntax_completion()
call feedkeys(":syn \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"syn case clear cluster conceal enable include iskeyword keyword list manual match off on region reset spell sync', @:)
+ call assert_equal('"syn case clear cluster conceal enable foldlevel include iskeyword keyword list manual match off on region reset spell sync', @:)
call feedkeys(":syn case \<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"syn case ignore match', @:)
@@ -579,3 +579,86 @@ func Test_syntax_hangs()
set redrawtime&
bwipe!
endfunc
+
+func Test_syntax_foldlevel()
+ new
+ call setline(1, [
+ \ 'void f(int a)',
+ \ '{',
+ \ ' if (a == 1) {',
+ \ ' a = 0;',
+ \ ' } else if (a == 2) {',
+ \ ' a = 1;',
+ \ ' } else {',
+ \ ' a = 2;',
+ \ ' }',
+ \ ' if (a > 0) {',
+ \ ' if (a == 1) {',
+ \ ' a = 0;',
+ \ ' } /* missing newline */ } /* end of outer if */ else {',
+ \ ' a = 1;',
+ \ ' }',
+ \ ' if (a == 1)',
+ \ ' {',
+ \ ' a = 0;',
+ \ ' }',
+ \ ' else if (a == 2)',
+ \ ' {',
+ \ ' a = 1;',
+ \ ' }',
+ \ ' else',
+ \ ' {',
+ \ ' a = 2;',
+ \ ' }',
+ \ '}',
+ \ ])
+ setfiletype c
+ syntax on
+ set foldmethod=syntax
+
+ call assert_fails('syn foldlevel start start', 'E390')
+ call assert_fails('syn foldlevel not_an_option', 'E390')
+
+ set foldlevel=1
+
+ syn foldlevel start
+ redir @c
+ syn foldlevel
+ redir END
+ call assert_equal("\nsyntax foldlevel start", @c)
+ syn sync fromstart
+ let a = map(range(3,9), 'foldclosed(v:val)')
+ call assert_equal([3,3,3,3,3,3,3], a) " attached cascade folds together
+ let a = map(range(10,15), 'foldclosed(v:val)')
+ call assert_equal([10,10,10,10,10,10], a) " over-attached 'else' hidden
+ let a = map(range(16,27), 'foldclosed(v:val)')
+ let unattached_results = [-1,17,17,17,-1,21,21,21,-1,25,25,25]
+ call assert_equal(unattached_results, a) " unattached cascade folds separately
+
+ syn foldlevel minimum
+ redir @c
+ syn foldlevel
+ redir END
+ call assert_equal("\nsyntax foldlevel minimum", @c)
+ syn sync fromstart
+ let a = map(range(3,9), 'foldclosed(v:val)')
+ call assert_equal([3,3,5,5,7,7,7], a) " attached cascade folds separately
+ let a = map(range(10,15), 'foldclosed(v:val)')
+ call assert_equal([10,10,10,13,13,13], a) " over-attached 'else' visible
+ let a = map(range(16,27), 'foldclosed(v:val)')
+ call assert_equal(unattached_results, a) " unattached cascade folds separately
+
+ set foldlevel=2
+
+ syn foldlevel start
+ syn sync fromstart
+ let a = map(range(11,14), 'foldclosed(v:val)')
+ call assert_equal([11,11,11,-1], a) " over-attached 'else' hidden
+
+ syn foldlevel minimum
+ syn sync fromstart
+ let a = map(range(11,14), 'foldclosed(v:val)')
+ call assert_equal([11,11,-1,-1], a) " over-attached 'else' visible
+
+ quit!
+endfunc
diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim
index 5fd71d8bfc..6abe5b7c89 100644
--- a/src/nvim/testdir/test_tagjump.vim
+++ b/src/nvim/testdir/test_tagjump.vim
@@ -255,8 +255,52 @@ func Test_tagjump_etags()
ta foo
call assert_equal('void foo() {}', getline('.'))
+ " Test for including another tags file
+ call writefile([
+ \ "\x0c",
+ \ "Xmain.c,64",
+ \ "void foo() {}\x7ffoo\x011,0",
+ \ "\x0c",
+ \ "Xnonexisting,include",
+ \ "\x0c",
+ \ "Xtags2,include"
+ \ ], 'Xtags')
+ call writefile([
+ \ "\x0c",
+ \ "Xmain.c,64",
+ \ "int main(int argc, char **argv)\x7fmain\x012,14",
+ \ ], 'Xtags2')
+ tag main
+ call assert_equal(2, line('.'))
+
+ " corrupted tag line
+ call writefile([
+ \ "\x0c",
+ \ "Xmain.c,8",
+ \ "int main"
+ \ ], 'Xtags', 'b')
+ call assert_fails('tag foo', 'E426:')
+
+ " invalid line number
+ call writefile([
+ \ "\x0c",
+ \ "Xmain.c,64",
+ \ "void foo() {}\x7ffoo\x0abc,0",
+ \ ], 'Xtags')
+ call assert_fails('tag foo', 'E426:')
+
+ " invalid tag name
+ call writefile([
+ \ "\x0c",
+ \ "Xmain.c,64",
+ \ ";;;;\x7f1,0",
+ \ ], 'Xtags')
+ call assert_fails('tag foo', 'E426:')
+
call delete('Xtags')
+ call delete('Xtags2')
call delete('Xmain.c')
+ set tags&
bwipe!
endfunc
@@ -531,4 +575,29 @@ func Test_tagline()
set tags&
endfunc
+" Test for the 'taglength' option
+func Test_tag_length()
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "tame\tXfile1\t1;",
+ \ "tape\tXfile2\t1;"], 'Xtags')
+ call writefile(['tame'], 'Xfile1')
+ call writefile(['tape'], 'Xfile2')
+
+ " Jumping to the tag 'tape', should instead jump to 'tame'
+ new
+ set taglength=2
+ tag tape
+ call assert_equal('Xfile1', @%)
+ " Tag search should jump to the right tag
+ enew
+ tag /^tape$
+ call assert_equal('Xfile2', @%)
+
+ call delete('Xtags')
+ call delete('Xfile1')
+ call delete('Xfile2')
+ set tags& taglength&
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_textobjects.vim b/src/nvim/testdir/test_textobjects.vim
index b20c4df311..7863317eb0 100644
--- a/src/nvim/testdir/test_textobjects.vim
+++ b/src/nvim/testdir/test_textobjects.vim
@@ -290,5 +290,16 @@ func! Test_sentence_with_cursor_on_delimiter()
normal! 17|yas
call assert_equal("A '([sentence.])' ", @")
+ " don't get stuck on a quote at the start of a sentence
+ %delete _
+ call setline(1, ['A sentence.', '"A sentence"?', 'A sentence!'])
+ normal gg))
+ call assert_equal(3, getcurpos()[1])
+
+ %delete _
+ call setline(1, ['A sentence.', "'A sentence'?", 'A sentence!'])
+ normal gg))
+ call assert_equal(3, getcurpos()[1])
+
%delete _
endfunc
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index b4d91a01fc..bfd9435c49 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -274,7 +274,7 @@ static void terminfo_start(UI *ui)
: (konsole ? 1 : 0);
patch_terminfo_bugs(data, term, colorterm, vtev, konsolev, iterm_env, nsterm);
- augment_terminfo(data, term, colorterm, vtev, konsolev, iterm_env, nsterm);
+ augment_terminfo(data, term, vtev, konsolev, iterm_env, nsterm);
data->can_change_scroll_region =
!!unibi_get_str(data->ut, unibi_change_scroll_region);
data->can_set_lr_margin =
@@ -1907,7 +1907,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
/// This adds stuff that is not in standard terminfo as extended unibilium
/// capabilities.
static void augment_terminfo(TUIData *data, const char *term,
- const char *colorterm, long vte_version,
+ long vte_version,
long konsolev, bool iterm_env, bool nsterm)
{
unibi_term *ut = data->ut;
diff --git a/src/nvim/version.c b/src/nvim/version.c
index e9f2b37e6b..190f13e74b 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -250,7 +250,7 @@ static const int included_patches[] = {
1671,
1670,
1669,
- // 1668,
+ 1668,
1667,
1666,
1665,
@@ -364,7 +364,7 @@ static const int included_patches[] = {
1557,
1556,
1555,
- // 1554,
+ 1554,
1553,
1552,
1551,
@@ -375,7 +375,7 @@ static const int included_patches[] = {
1546,
1545,
// 1544,
- // 1543,
+ 1543,
1542,
1541,
1540,