aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/eval.txt17
-rw-r--r--runtime/doc/ui.txt9
-rw-r--r--src/nvim/api/ui.c22
-rw-r--r--src/nvim/buffer.c5
-rw-r--r--src/nvim/cursor_shape.c24
-rw-r--r--src/nvim/edit.c64
-rw-r--r--src/nvim/eval.c65
-rw-r--r--src/nvim/eval.lua1
-rw-r--r--src/nvim/fileio.c8
-rw-r--r--src/nvim/highlight.c91
-rw-r--r--src/nvim/quickfix.c12
-rw-r--r--src/nvim/screen.c16
-rw-r--r--src/nvim/syntax.c7
-rw-r--r--src/nvim/tag.c46
-rw-r--r--src/nvim/testdir/Makefile1
-rw-r--r--src/nvim/testdir/test_alot.vim1
-rw-r--r--src/nvim/testdir/test_autocmd.vim71
-rw-r--r--src/nvim/testdir/test_functions.vim23
-rw-r--r--src/nvim/testdir/test_join.vim35
-rw-r--r--src/nvim/testdir/test_stat.vim93
-rw-r--r--src/nvim/testdir/test_tagjump.vim28
-rw-r--r--src/nvim/testdir/test_taglist.vim7
-rw-r--r--src/nvim/testdir/test_user_func.vim96
-rw-r--r--src/nvim/tui/tui.c13
-rw-r--r--src/nvim/ui.c13
-rw-r--r--test/functional/legacy/packadd_spec.lua2
-rw-r--r--test/functional/ui/cursor_spec.lua46
-rw-r--r--test/functional/ui/screen.lua11
28 files changed, 681 insertions, 146 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 4c0ee6cc66..cfc3b70443 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -2336,6 +2336,7 @@ tolower({expr}) String the String {expr} switched to lowercase
toupper({expr}) String the String {expr} switched to uppercase
tr({src}, {fromstr}, {tostr}) String translate chars of {src} in {fromstr}
to chars in {tostr}
+trim({text}[, {mask}]) String trim characters in {mask} from {text}
trunc({expr}) Float truncate Float {expr}
type({name}) Number type of variable {name}
undofile({name}) String undo file name for {name}
@@ -7962,6 +7963,22 @@ tr({src}, {fromstr}, {tostr}) *tr()*
echo tr("<blob>", "<>", "{}")
< returns "{blob}"
+trim({text}[, {mask}]) *trim()*
+ Return {text} as a String where any character in {mask} is
+ removed from the beginning and end of {text}.
+ If {mask} is not given, {mask} is all characters up to 0x20,
+ which includes Tab, space, NL and CR, plus the non-breaking
+ space character 0xa0.
+ This code deals with multibyte characters properly.
+
+ Examples: >
+ echo trim(" \r\t\t\r RESERVE \t \t\n\x0B\x0B")."_TAIL"
+< returns "RESERVE_TAIL" >
+ echo trim("needrmvRESERVEnnneeedddrrmmmmvv", "ednmrv")
+< returns "RESERVE" >
+ echo trim("rm<blob1><blob2><any_chars>rrmm<blob1><blob2><blob2>", "rm<blob1><blob2>")
+< returns "any_chas"
+
trunc({expr}) *trunc()*
Return the largest integral value with magnitude less than or
equal to {expr} as a |Float| (truncate towards zero).
diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt
index 9d10756e23..51af11a2cd 100644
--- a/runtime/doc/ui.txt
+++ b/runtime/doc/ui.txt
@@ -89,13 +89,18 @@ Global Events *ui-global*
`cursor_shape`: "block", "horizontal", "vertical"
`cell_percentage`: Cell % occupied by the cursor.
`blinkwait`, `blinkon`, `blinkoff`: See |cursor-blinking|.
- `hl_id`: Cursor highlight group.
- `hl_lm`: Cursor highlight group if 'langmap' is active.
+ `attr_id`: Cursor attribute id (defined by `hl_attr_define`)
+ `attr_id_lm`: Cursor attribute id for when 'langmap' is active.
`short_name`: Mode code name, see 'guicursor'.
`name`: Mode descriptive name.
`mouse_shape`: (To be implemented.)
Some keys are missing in some modes.
+
+ The following keys are deprecated:
+
+ `hl_id`: Use `attr_id` instead.
+ `hl_lm`: Use `attr_id_lm` instead.
["option_set", name, value]
UI-related option changed, where `name` is one of:
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index d0db43c588..37d34c5843 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -351,12 +351,8 @@ static void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs,
Array args = ARRAY_DICT_INIT;
ADD(args, INTEGER_OBJ(id));
-
- Dictionary rgb_hl = hlattrs2dict(&rgb_attrs, true);
- ADD(args, DICTIONARY_OBJ(rgb_hl));
-
- Dictionary cterm_hl = hlattrs2dict(&cterm_attrs, false);
- ADD(args, DICTIONARY_OBJ(cterm_hl));
+ ADD(args, DICTIONARY_OBJ(hlattrs2dict(rgb_attrs, true)));
+ ADD(args, DICTIONARY_OBJ(hlattrs2dict(cterm_attrs, false)));
if (ui->ui_ext[kUIHlState]) {
ADD(args, ARRAY_OBJ(copy_array(info)));
@@ -372,21 +368,12 @@ static void remote_ui_highlight_set(UI *ui, int id)
Array args = ARRAY_DICT_INIT;
UIData *data = ui->data;
- HlAttrs attrs = HLATTRS_INIT;
if (data->hl_id == id) {
return;
}
data->hl_id = id;
-
- if (id != 0) {
- HlAttrs *aep = syn_attr2entry(id);
- if (aep) {
- attrs = *aep;
- }
- }
-
- Dictionary hl = hlattrs2dict(&attrs, ui->rgb);
+ Dictionary hl = hlattrs2dict(syn_attr2entry(id), ui->rgb);
ADD(args, DICTIONARY_OBJ(hl));
push_call(ui, "highlight_set", args);
@@ -524,8 +511,7 @@ static void remote_ui_cmdline_show(UI *ui, Array args)
Array new_item = ARRAY_DICT_INIT;
int attr = (int)item.items[0].data.integer;
if (attr) {
- HlAttrs *aep = syn_attr2entry(attr);
- Dictionary rgb_attrs = hlattrs2dict(aep, ui->rgb ? kTrue : kFalse);
+ Dictionary rgb_attrs = hlattrs2dict(syn_attr2entry(attr), ui->rgb);
ADD(new_item, DICTIONARY_OBJ(rgb_attrs));
} else {
ADD(new_item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT));
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 3fadcc75bf..00d472b4c8 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -296,6 +296,11 @@ open_buffer (
}
save_file_ff(curbuf); // keep this fileformat
+ // Set last_changedtick to avoid triggering a TextChanged autocommand right
+ // after it was added.
+ curbuf->b_last_changedtick = buf_get_changedtick(curbuf);
+ curbuf->b_last_changedtick_pum = buf_get_changedtick(curbuf);
+
/* require "!" to overwrite the file, because it wasn't read completely */
if (aborting())
curbuf->b_flags |= BF_READERR;
diff --git a/src/nvim/cursor_shape.c b/src/nvim/cursor_shape.c
index b45e7002f7..cf79005a37 100644
--- a/src/nvim/cursor_shape.c
+++ b/src/nvim/cursor_shape.c
@@ -64,6 +64,9 @@ Array mode_style_array(void)
PUT(dic, "blinkoff", INTEGER_OBJ(cur->blinkoff));
PUT(dic, "hl_id", INTEGER_OBJ(cur->id));
PUT(dic, "id_lm", INTEGER_OBJ(cur->id_lm));
+ PUT(dic, "attr_id", INTEGER_OBJ(cur->id ? syn_id2attr(cur->id) : 0));
+ PUT(dic, "attr_id_lm", INTEGER_OBJ(cur->id_lm ? syn_id2attr(cur->id_lm)
+ : 0));
}
PUT(dic, "name", STRING_OBJ(cstr_to_string(cur->full_name)));
PUT(dic, "short_name", STRING_OBJ(cstr_to_string(cur->name)));
@@ -258,15 +261,30 @@ char_u *parse_shape_opt(int what)
/// @return -1 in case of failure, else the matching SHAPE_ID* integer
int cursor_mode_str2int(const char *mode)
{
- for (int current_mode = 0; current_mode < SHAPE_IDX_COUNT; current_mode++) {
- if (strcmp(shape_table[current_mode].full_name, mode) == 0) {
- return current_mode;
+ for (int mode_idx = 0; mode_idx < SHAPE_IDX_COUNT; mode_idx++) {
+ if (strcmp(shape_table[mode_idx].full_name, mode) == 0) {
+ return mode_idx;
}
}
WLOG("Unknown mode %s", mode);
return -1;
}
+/// Check if a syntax id is used as a cursor style.
+bool cursor_mode_uses_syn_id(int syn_id)
+{
+ if (*p_guicursor == NUL) {
+ return false;
+ }
+ for (int mode_idx = 0; mode_idx < SHAPE_IDX_COUNT; mode_idx++) {
+ if (shape_table[mode_idx].id == syn_id
+ || shape_table[mode_idx].id_lm == syn_id) {
+ return true;
+ }
+ }
+ return false;
+}
+
/// Return the index into shape_table[] for the current mode.
int cursor_get_mode_idx(void)
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 6231ecd977..085f12473e 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -276,7 +276,7 @@ static void insert_enter(InsertState *s)
set_vim_var_string(VV_INSERTMODE, (char *) s->ptr, 1);
set_vim_var_string(VV_CHAR, NULL, -1);
- apply_autocmds(EVENT_INSERTENTER, NULL, NULL, false, curbuf);
+ ins_apply_autocmds(EVENT_INSERTENTER);
// Make sure the cursor didn't move. Do call check_cursor_col() in
// case the text was modified. Since Insert mode was not started yet
@@ -469,7 +469,7 @@ static void insert_enter(InsertState *s)
foldUpdateAfterInsert();
if (s->cmdchar != 'r' && s->cmdchar != 'v') {
- apply_autocmds(EVENT_INSERTLEAVE, NULL, NULL, false, curbuf);
+ ins_apply_autocmds(EVENT_INSERTLEAVE);
}
did_cursorhold = false;
}
@@ -1376,7 +1376,7 @@ ins_redraw (
// Make sure curswant is correct, an autocommand may call
// getcurpos()
update_curswant();
- apply_autocmds(EVENT_CURSORMOVEDI, NULL, NULL, false, curbuf);
+ ins_apply_autocmds(EVENT_CURSORMOVEDI);
}
if (curwin->w_p_cole > 0) {
conceal_old_cursor_line = last_cursormoved.lnum;
@@ -1390,8 +1390,18 @@ ins_redraw (
if (ready && has_event(EVENT_TEXTCHANGEDI)
&& curbuf->b_last_changedtick != buf_get_changedtick(curbuf)
&& !pum_visible()) {
+ aco_save_T aco;
+ varnumber_T tick = buf_get_changedtick(curbuf);
+
+ // save and restore curwin and curbuf, in case the autocmd changes them
+ aucmd_prepbuf(&aco, curbuf);
apply_autocmds(EVENT_TEXTCHANGEDI, NULL, NULL, false, curbuf);
+ aucmd_restbuf(&aco);
curbuf->b_last_changedtick = buf_get_changedtick(curbuf);
+ if (tick != buf_get_changedtick(curbuf)) { // see ins_apply_autocmds()
+ u_save(curwin->w_cursor.lnum,
+ (linenr_T)(curwin->w_cursor.lnum + 1));
+ }
}
// Trigger TextChangedP if changedtick differs. When the popupmenu closes
@@ -1400,8 +1410,18 @@ ins_redraw (
if (ready && has_event(EVENT_TEXTCHANGEDP)
&& curbuf->b_last_changedtick_pum != buf_get_changedtick(curbuf)
&& pum_visible()) {
- apply_autocmds(EVENT_TEXTCHANGEDP, NULL, NULL, false, curbuf);
- curbuf->b_last_changedtick_pum = buf_get_changedtick(curbuf);
+ aco_save_T aco;
+ varnumber_T tick = buf_get_changedtick(curbuf);
+
+ // save and restore curwin and curbuf, in case the autocmd changes them
+ aucmd_prepbuf(&aco, curbuf);
+ apply_autocmds(EVENT_TEXTCHANGEDP, NULL, NULL, false, curbuf);
+ aucmd_restbuf(&aco);
+ curbuf->b_last_changedtick_pum = buf_get_changedtick(curbuf);
+ if (tick != buf_get_changedtick(curbuf)) { // see ins_apply_autocmds()
+ u_save(curwin->w_cursor.lnum,
+ (linenr_T)(curwin->w_cursor.lnum + 1));
+ }
}
if (must_redraw)
@@ -3392,12 +3412,12 @@ static bool ins_compl_prep(int c)
do_c_expr_indent();
/* Trigger the CompleteDone event to give scripts a chance to act
* upon the completion. */
- apply_autocmds(EVENT_COMPLETEDONE, NULL, NULL, FALSE, curbuf);
+ ins_apply_autocmds(EVENT_COMPLETEDONE);
}
} else if (ctrl_x_mode == CTRL_X_LOCAL_MSG)
/* Trigger the CompleteDone event to give scripts a chance to act
* upon the (possibly failed) completion. */
- apply_autocmds(EVENT_COMPLETEDONE, NULL, NULL, FALSE, curbuf);
+ ins_apply_autocmds(EVENT_COMPLETEDONE);
/* reset continue_* if we left expansion-mode, if we stay they'll be
* (re)set properly in ins_complete() */
@@ -7399,7 +7419,7 @@ static void ins_insert(int replaceState)
set_vim_var_string(VV_INSERTMODE, ((State & REPLACE_FLAG) ? "i" :
replaceState == VREPLACE ? "v" :
"r"), 1);
- apply_autocmds(EVENT_INSERTCHANGE, NULL, NULL, false, curbuf);
+ ins_apply_autocmds(EVENT_INSERTCHANGE);
if (State & REPLACE_FLAG) {
State = INSERT | (State & LANGMAP);
} else {
@@ -8661,12 +8681,13 @@ static char_u *do_insert_char_pre(int c)
set_vim_var_string(VV_CHAR, buf, -1);
char_u *res = NULL;
- if (apply_autocmds(EVENT_INSERTCHARPRE, NULL, NULL, FALSE, curbuf)) {
- /* Get the value of v:char. It may be empty or more than one
- * character. Only use it when changed, otherwise continue with the
- * original character to avoid breaking autoindent. */
- if (STRCMP(buf, get_vim_var_str(VV_CHAR)) != 0)
+ if (ins_apply_autocmds(EVENT_INSERTCHARPRE)) {
+ // Get the value of v:char. It may be empty or more than one
+ // character. Only use it when changed, otherwise continue with the
+ // original character to avoid breaking autoindent.
+ if (STRCMP(buf, get_vim_var_str(VV_CHAR)) != 0) {
res = vim_strsave(get_vim_var_str(VV_CHAR));
+ }
}
set_vim_var_string(VV_CHAR, NULL, -1);
@@ -8675,6 +8696,23 @@ static char_u *do_insert_char_pre(int c)
return res;
}
+/// Trigger "event" and take care of fixing undo.
+static int ins_apply_autocmds(event_T event)
+{
+ varnumber_T tick = buf_get_changedtick(curbuf);
+ int r;
+
+ r = apply_autocmds(event, NULL, NULL, false, curbuf);
+
+ // If u_savesub() was called then we are not prepared to start
+ // a new line. Call u_save() with no contents to fix that.
+ if (tick != buf_get_changedtick(curbuf)) {
+ u_save(curwin->w_cursor.lnum, (linenr_T)(curwin->w_cursor.lnum + 1));
+ }
+
+ return r;
+}
+
static void show_pum(int prev_w_wrow, int prev_w_leftcol)
{
// RedrawingDisabled may be set when invoked through complete().
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 22cb544f54..86f57ee5a2 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -9874,7 +9874,7 @@ static void f_getftype(typval_T *argvars, typval_T *rettv, FunPtr fptr)
# endif
# ifdef S_ISSOCK
else if (S_ISSOCK(mode))
- t = "fifo";
+ t = "socket";
# endif
else
t = "other";
@@ -17218,6 +17218,69 @@ error:
return;
}
+// "trim({expr})" function
+static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char buf1[NUMBUFLEN];
+ char buf2[NUMBUFLEN];
+ const char_u *head = (const char_u *)tv_get_string_buf_chk(&argvars[0], buf1);
+ const char_u *mask = NULL;
+ const char_u *tail;
+ const char_u *prev;
+ const char_u *p;
+ int c1;
+
+ rettv->v_type = VAR_STRING;
+ if (head == NULL) {
+ rettv->vval.v_string = NULL;
+ return;
+ }
+
+ if (argvars[1].v_type == VAR_STRING) {
+ mask = (const char_u *)tv_get_string_buf_chk(&argvars[1], buf2);
+ }
+
+ while (*head != NUL) {
+ c1 = PTR2CHAR(head);
+ if (mask == NULL) {
+ if (c1 > ' ' && c1 != 0xa0) {
+ break;
+ }
+ } else {
+ for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
+ if (c1 == PTR2CHAR(p)) {
+ break;
+ }
+ }
+ if (*p == NUL) {
+ break;
+ }
+ }
+ MB_PTR_ADV(head);
+ }
+
+ for (tail = head + STRLEN(head); tail > head; tail = prev) {
+ prev = tail;
+ MB_PTR_BACK(head, prev);
+ c1 = PTR2CHAR(prev);
+ if (mask == NULL) {
+ if (c1 > ' ' && c1 != 0xa0) {
+ break;
+ }
+ } else {
+ for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
+ if (c1 == PTR2CHAR(p)) {
+ break;
+ }
+ }
+ if (*p == NUL) {
+ break;
+ }
+ }
+ }
+ rettv->vval.v_string = vim_strnsave(head, (int)(tail - head));
+}
+
/*
* "type(expr)" function
*/
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 801d2cc468..23959f348a 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -323,6 +323,7 @@ return {
tolower={args=1},
toupper={args=1},
tr={args=3},
+ trim={args={1,2}},
trunc={args=1, func="float_op_wrapper", data="&trunc"},
type={args=1},
undofile={args=1},
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index 78fac5acf8..8b650d0d5b 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -4892,10 +4892,12 @@ buf_check_timestamp (
)) {
retval = 1;
- /* set b_mtime to stop further warnings (e.g., when executing
- * FileChangedShell autocmd) */
+ // set b_mtime to stop further warnings (e.g., when executing
+ // FileChangedShell autocmd)
if (!file_info_ok) {
- buf->b_mtime = 0;
+ // When 'autoread' is set we'll check the file again to see if it
+ // re-appears.
+ buf->b_mtime = buf->b_p_ar;
buf->b_orig_size = 0;
buf->b_orig_mode = 0;
} else {
diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c
index 0b39ba442e..a104137d9e 100644
--- a/src/nvim/highlight.c
+++ b/src/nvim/highlight.c
@@ -135,11 +135,8 @@ int hl_get_ui_attr(int idx, int final_id, bool optional)
int syn_attr = syn_id2attr(final_id);
if (syn_attr != 0) {
- HlAttrs *aep = syn_attr2entry(syn_attr);
- if (aep) {
- attrs = *aep;
- available = true;
- }
+ attrs = syn_attr2entry(syn_attr);
+ available = true;
}
if (optional && !available) {
return 0;
@@ -232,42 +229,33 @@ int hl_combine_attr(int char_attr, int prim_attr)
return id;
}
- HlAttrs *char_aep, *spell_aep;
- HlAttrs new_en = HLATTRS_INIT;
+ HlAttrs char_aep = syn_attr2entry(char_attr);
+ HlAttrs spell_aep = syn_attr2entry(prim_attr);
+ // start with low-priority attribute, and override colors if present below.
+ HlAttrs new_en = char_aep;
- // Find the entry for char_attr
- char_aep = syn_attr2entry(char_attr);
+ new_en.cterm_ae_attr |= spell_aep.cterm_ae_attr;
+ new_en.rgb_ae_attr |= spell_aep.rgb_ae_attr;
- if (char_aep != NULL) {
- // Copy all attributes from char_aep to the new entry
- new_en = *char_aep;
+ if (spell_aep.cterm_fg_color > 0) {
+ new_en.cterm_fg_color = spell_aep.cterm_fg_color;
}
- spell_aep = syn_attr2entry(prim_attr);
- if (spell_aep != NULL) {
- new_en.cterm_ae_attr |= spell_aep->cterm_ae_attr;
- new_en.rgb_ae_attr |= spell_aep->rgb_ae_attr;
-
- if (spell_aep->cterm_fg_color > 0) {
- new_en.cterm_fg_color = spell_aep->cterm_fg_color;
- }
-
- if (spell_aep->cterm_bg_color > 0) {
- new_en.cterm_bg_color = spell_aep->cterm_bg_color;
- }
+ if (spell_aep.cterm_bg_color > 0) {
+ new_en.cterm_bg_color = spell_aep.cterm_bg_color;
+ }
- if (spell_aep->rgb_fg_color >= 0) {
- new_en.rgb_fg_color = spell_aep->rgb_fg_color;
- }
+ if (spell_aep.rgb_fg_color >= 0) {
+ new_en.rgb_fg_color = spell_aep.rgb_fg_color;
+ }
- if (spell_aep->rgb_bg_color >= 0) {
- new_en.rgb_bg_color = spell_aep->rgb_bg_color;
- }
+ if (spell_aep.rgb_bg_color >= 0) {
+ new_en.rgb_bg_color = spell_aep.rgb_bg_color;
+ }
- if (spell_aep->rgb_sp_color >= 0) {
- new_en.rgb_sp_color = spell_aep->rgb_sp_color;
- }
+ if (spell_aep.rgb_sp_color >= 0) {
+ new_en.rgb_sp_color = spell_aep.rgb_sp_color;
}
id = get_attr_entry((HlEntry){ .attr = new_en, .kind = kHlCombine,
@@ -280,44 +268,41 @@ int hl_combine_attr(int char_attr, int prim_attr)
}
/// Get highlight attributes for a attribute code
-HlAttrs *syn_attr2entry(int attr)
+HlAttrs syn_attr2entry(int attr)
{
if (attr <= 0 || attr >= (int)kv_size(attr_entries)) {
// invalid attribute code, or the tables were cleared
- return NULL;
+ return HLATTRS_INIT;
}
- return &(kv_A(attr_entries, attr).attr);
+ return kv_A(attr_entries, attr).attr;
}
/// Gets highlight description for id `attr_id` as a map.
Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Error *err)
{
- HlAttrs *aep = NULL;
Dictionary dic = ARRAY_DICT_INIT;
if (attr_id == 0) {
return dic;
}
- aep = syn_attr2entry((int)attr_id);
- if (!aep) {
+ if (attr_id <= 0 || attr_id >= (int)kv_size(attr_entries)) {
api_set_error(err, kErrorTypeException,
"Invalid attribute id: %" PRId64, attr_id);
return dic;
}
- return hlattrs2dict(aep, rgb);
+ return hlattrs2dict(syn_attr2entry((int)attr_id), rgb);
}
/// Converts an HlAttrs into Dictionary
///
/// @param[in] aep data to convert
/// @param use_rgb use 'gui*' settings if true, else resorts to 'cterm*'
-Dictionary hlattrs2dict(const HlAttrs *aep, bool use_rgb)
+Dictionary hlattrs2dict(HlAttrs ae, bool use_rgb)
{
- assert(aep);
Dictionary hl = ARRAY_DICT_INIT;
- int mask = use_rgb ? aep->rgb_ae_attr : aep->cterm_ae_attr;
+ int mask = use_rgb ? ae.rgb_ae_attr : ae.cterm_ae_attr;
if (mask & HL_BOLD) {
PUT(hl, "bold", BOOLEAN_OBJ(true));
@@ -344,24 +329,24 @@ Dictionary hlattrs2dict(const HlAttrs *aep, bool use_rgb)
}
if (use_rgb) {
- if (aep->rgb_fg_color != -1) {
- PUT(hl, "foreground", INTEGER_OBJ(aep->rgb_fg_color));
+ if (ae.rgb_fg_color != -1) {
+ PUT(hl, "foreground", INTEGER_OBJ(ae.rgb_fg_color));
}
- if (aep->rgb_bg_color != -1) {
- PUT(hl, "background", INTEGER_OBJ(aep->rgb_bg_color));
+ if (ae.rgb_bg_color != -1) {
+ PUT(hl, "background", INTEGER_OBJ(ae.rgb_bg_color));
}
- if (aep->rgb_sp_color != -1) {
- PUT(hl, "special", INTEGER_OBJ(aep->rgb_sp_color));
+ if (ae.rgb_sp_color != -1) {
+ PUT(hl, "special", INTEGER_OBJ(ae.rgb_sp_color));
}
} else {
- if (cterm_normal_fg_color != aep->cterm_fg_color) {
- PUT(hl, "foreground", INTEGER_OBJ(aep->cterm_fg_color - 1));
+ if (cterm_normal_fg_color != ae.cterm_fg_color) {
+ PUT(hl, "foreground", INTEGER_OBJ(ae.cterm_fg_color - 1));
}
- if (cterm_normal_bg_color != aep->cterm_bg_color) {
- PUT(hl, "background", INTEGER_OBJ(aep->cterm_bg_color - 1));
+ if (cterm_normal_bg_color != ae.cterm_bg_color) {
+ PUT(hl, "background", INTEGER_OBJ(ae.cterm_bg_color - 1));
}
}
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 4308c4e87e..a19e98725a 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -4541,11 +4541,6 @@ void ex_cbuffer(exarg_T *eap)
qf_info_T *qi = &ql_info;
const char *au_name = NULL;
- if (eap->cmdidx == CMD_lbuffer || eap->cmdidx == CMD_lgetbuffer
- || eap->cmdidx == CMD_laddbuffer) {
- qi = ll_get_or_alloc_list(curwin);
- }
-
switch (eap->cmdidx) {
case CMD_cbuffer:
au_name = "cbuffer";
@@ -4576,6 +4571,13 @@ void ex_cbuffer(exarg_T *eap)
}
}
+ // Must come after autocommands.
+ if (eap->cmdidx == CMD_lbuffer
+ || eap->cmdidx == CMD_lgetbuffer
+ || eap->cmdidx == CMD_laddbuffer) {
+ qi = ll_get_or_alloc_list(curwin);
+ }
+
if (*eap->arg == NUL)
buf = curbuf;
else if (*skipwhite(skipdigits(eap->arg)) == NUL)
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 4ed67f1c43..ec48bf5dcf 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -423,7 +423,7 @@ void update_screen(int type)
/* redraw status line after the window to minimize cursor movement */
if (wp->w_redr_status) {
- win_redr_status(wp);
+ win_redr_status(wp, true); // any popup menu will be redrawn below
}
}
end_search_hl();
@@ -589,7 +589,7 @@ void update_debug_sign(const buf_T *const buf, const linenr_T lnum)
win_update(wp);
}
if (wp->w_redr_status) {
- win_redr_status(wp);
+ win_redr_status(wp, false);
}
}
@@ -2424,12 +2424,12 @@ win_line (
if (wp->w_p_cul && lnum == wp->w_cursor.lnum
&& !(wp == curwin && VIsual_active)) {
int cul_attr = win_hl_attr(wp, HLF_CUL);
- HlAttrs *aep = syn_attr2entry(cul_attr);
+ HlAttrs ae = syn_attr2entry(cul_attr);
// We make a compromise here (#7383):
// * low-priority CursorLine if fg is not set
// * high-priority ("same as Vim" priority) CursorLine if fg is set
- if (aep->rgb_fg_color == -1 && aep->cterm_fg_color == 0) {
+ if (ae.rgb_fg_color == -1 && ae.cterm_fg_color == 0) {
line_attr_lowprio = cul_attr;
} else {
if (line_attr != 0 && !(State & INSERT) && bt_quickfix(wp->w_buffer)
@@ -4530,7 +4530,7 @@ void redraw_statuslines(void)
{
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_redr_status) {
- win_redr_status(wp);
+ win_redr_status(wp, false);
}
}
if (redraw_tabline)
@@ -4797,7 +4797,9 @@ win_redr_status_matches (
/// Redraw the status line of window `wp`.
///
/// If inversion is possible we use it. Else '=' characters are used.
-static void win_redr_status(win_T *wp)
+/// If "ignore_pum" is true, also redraw statusline when the popup menu is
+/// displayed.
+static void win_redr_status(win_T *wp, int ignore_pum)
{
int row;
char_u *p;
@@ -4820,7 +4822,7 @@ static void win_redr_status(win_T *wp)
if (wp->w_status_height == 0) {
// no status line, can only be last window
redraw_cmdline = true;
- } else if (!redrawing() || pum_drawn()) {
+ } else if (!redrawing() || (!ignore_pum && pum_drawn())) {
// Don't redraw right now, do it later. Don't update status line when
// popup menu is visible and may be drawn over it
wp->w_redr_status = true;
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index d7c23742ba..3cb998b805 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -16,6 +16,7 @@
#include "nvim/ascii.h"
#include "nvim/syntax.h"
#include "nvim/charset.h"
+#include "nvim/cursor_shape.h"
#include "nvim/eval.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
@@ -7228,7 +7229,6 @@ static void set_hl_attr(int idx)
HlAttrs at_en = HLATTRS_INIT;
struct hl_group *sgp = HL_TABLE() + idx;
-
at_en.cterm_ae_attr = sgp->sg_cterm;
at_en.cterm_fg_color = sgp->sg_cterm_fg;
at_en.cterm_bg_color = sgp->sg_cterm_bg;
@@ -7241,6 +7241,11 @@ static void set_hl_attr(int idx)
at_en.rgb_sp_color = sgp->sg_rgb_sp_name ? sgp->sg_rgb_sp : -1;
sgp->sg_attr = hl_get_syn_attr(idx+1, at_en);
+
+ // a cursor style uses this syn_id, make sure its atribute is updated.
+ if (cursor_mode_uses_syn_id(idx+1)) {
+ ui_mode_info_set();
+ }
}
/// Lookup a highlight group name and return its ID.
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index c09a13edb1..2a980af2a2 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -952,12 +952,12 @@ void do_tags(exarg_T *eap)
continue;
msg_putchar('\n');
- sprintf((char *)IObuff, "%c%2d %2d %-15s %5ld ",
- i == tagstackidx ? '>' : ' ',
- i + 1,
- tagstack[i].cur_match + 1,
- tagstack[i].tagname,
- tagstack[i].fmark.mark.lnum);
+ vim_snprintf((char *)IObuff, IOSIZE, "%c%2d %2d %-15s %5ld ",
+ i == tagstackidx ? '>' : ' ',
+ i + 1,
+ tagstack[i].cur_match + 1,
+ tagstack[i].tagname,
+ tagstack[i].fmark.mark.lnum);
msg_outtrans(IObuff);
msg_outtrans_attr(name, tagstack[i].fmark.fnum == curbuf->b_fnum
? HL_ATTR(HLF_D) : 0);
@@ -2217,6 +2217,16 @@ static bool test_for_static(tagptrs_T *tagp)
return FALSE;
}
+// Returns the length of a matching tag line.
+static size_t matching_line_len(const char_u *const lbuf)
+{
+ const char_u *p = lbuf + 1;
+
+ // does the same thing as parse_match()
+ p += STRLEN(p) + 1;
+ return (p - lbuf) + STRLEN(p);
+}
+
/*
* Parse a line from a matching tag. Does not change the line itself.
*
@@ -2300,11 +2310,10 @@ static char_u *tag_full_fname(tagptrs_T *tagp)
*
* returns OK for success, NOTAGFILE when file not found, FAIL otherwise.
*/
-static int
-jumpto_tag (
- char_u *lbuf, /* line from the tags file for this tag */
- int forceit, /* :ta with ! */
- int keep_help /* keep help flag (FALSE for cscope) */
+static int jumpto_tag(
+ const char_u *lbuf_arg, // line from the tags file for this tag
+ int forceit, // :ta with !
+ int keep_help // keep help flag (FALSE for cscope)
)
{
int save_secure;
@@ -2312,7 +2321,6 @@ jumpto_tag (
bool save_p_ws;
int save_p_scs, save_p_ic;
linenr_T save_lnum;
- int csave = 0;
char_u *str;
char_u *pbuf; /* search pattern buffer */
char_u *pbuf_end;
@@ -2327,6 +2335,9 @@ jumpto_tag (
char_u *full_fname = NULL;
int old_KeyTyped = KeyTyped; /* getting the file may reset it */
const int l_g_do_tagpreview = g_do_tagpreview;
+ const size_t len = matching_line_len(lbuf_arg) + 1;
+ char_u *lbuf = xmalloc(len);
+ memmove(lbuf, lbuf_arg, len);
pbuf = xmalloc(LSIZE);
@@ -2336,8 +2347,7 @@ jumpto_tag (
goto erret;
}
- /* truncate the file name, so it can be used as a string */
- csave = *tagp.fname_end;
+ // truncate the file name, so it can be used as a string
*tagp.fname_end = NUL;
fname = tagp.fname;
@@ -2447,7 +2457,10 @@ jumpto_tag (
else
keep_help_flag = curbuf->b_help;
}
+
if (getfile_result == GETFILE_UNUSED) {
+ // Careful: getfile() may trigger autocommands and call jumpto_tag()
+ // recursively.
getfile_result = getfile(0, fname, NULL, true, (linenr_T)0, forceit);
}
keep_help_flag = false;
@@ -2605,9 +2618,8 @@ jumpto_tag (
}
erret:
- g_do_tagpreview = 0; /* For next time */
- if (tagp.fname_end != NULL)
- *tagp.fname_end = csave;
+ g_do_tagpreview = 0; // For next time
+ xfree(lbuf);
xfree(pbuf);
xfree(tofree_fname);
xfree(full_fname);
diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile
index 361db47fc7..1e3dc04049 100644
--- a/src/nvim/testdir/Makefile
+++ b/src/nvim/testdir/Makefile
@@ -114,6 +114,7 @@ NEW_TESTS ?= \
test_timers.res \
test_undo.res \
test_usercommands.res \
+ test_user_func.res \
test_vimscript.res \
test_visual.res \
test_winbuf_close.res \
diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim
index 238dbe8d90..ace4b377cb 100644
--- a/src/nvim/testdir/test_alot.vim
+++ b/src/nvim/testdir/test_alot.vim
@@ -19,6 +19,7 @@ source test_functions.vim
source test_ga.vim
source test_global.vim
source test_goto.vim
+source test_join.vim
source test_jumps.vim
source test_fileformat.vim
source test_filetype.vim
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index 772b3f721c..b1502eff89 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -1,5 +1,7 @@
" Tests for autocommands
+source shared.vim
+
func! s:cleanup_buffers() abort
for bnr in range(1, bufnr('$'))
if bufloaded(bnr) && bufnr('%') != bnr
@@ -248,6 +250,23 @@ func Test_augroup_warning()
au! VimEnter
endfunc
+func Test_BufReadCmdHelp()
+ " This used to cause access to free memory
+ au BufReadCmd * e +h
+ help
+
+ au! BufReadCmd
+endfunc
+
+func Test_BufReadCmdHelpJump()
+ " This used to cause access to free memory
+ au BufReadCmd * e +h{
+ " } to fix highlighting
+ call assert_fails('help', 'E434:')
+
+ au! BufReadCmd
+endfunc
+
func Test_augroup_deleted()
" This caused a crash before E936 was introduced
augroup x
@@ -567,7 +586,7 @@ func Test_OptionSet()
" Cleanup
au! OptionSet
for opt in ['nu', 'ai', 'acd', 'ar', 'bs', 'backup', 'cul', 'cp']
- exe printf(":set %s&vi", opt)
+ exe printf(":set %s&vim", opt)
endfor
call test_override('starting', 0)
delfunc! AutoCommandOptionSet
@@ -1165,6 +1184,13 @@ func Test_nocatch_wipe_dummy_buffer()
au!
endfunc
+func Test_wipe_cbuffer()
+ sv x
+ au * * bw
+ lb
+ au!
+endfunc
+
" Test TextChangedI and TextChangedP
func Test_ChangedP()
" Nvim does not support test_override().
@@ -1221,3 +1247,46 @@ func Test_ChangedP()
bw!
endfunc
+
+let g:setline_handled = v:false
+func! SetLineOne()
+ if !g:setline_handled
+ call setline(1, "(x)")
+ let g:setline_handled = v:true
+ endif
+endfunc
+
+func Test_TextChangedI_with_setline()
+ throw 'skipped: Nvim does not support test_override()'
+ new
+ call test_override('char_avail', 1)
+ autocmd TextChangedI <buffer> call SetLineOne()
+ call feedkeys("i(\<CR>\<Esc>", 'tx')
+ call assert_equal('(', getline(1))
+ call assert_equal('x)', getline(2))
+ undo
+ call assert_equal('', getline(1))
+ call assert_equal('', getline(2))
+
+ call test_override('starting', 0)
+ bwipe!
+endfunc
+
+func Test_Changed_FirstTime()
+ if !has('terminal') || has('gui_running')
+ return
+ endif
+ " Prepare file for TextChanged event.
+ call writefile([''], 'Xchanged.txt')
+ let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile'], {'term_rows': 3})
+ call assert_equal('running', term_getstatus(buf))
+ " It's only adding autocmd, so that no event occurs.
+ call term_sendkeys(buf, ":au! TextChanged <buffer> call writefile(['No'], 'Xchanged.txt')\<cr>")
+ call term_sendkeys(buf, "\<C-\\>\<C-N>:qa!\<cr>")
+ call WaitFor({-> term_getstatus(buf) == 'finished'})
+ call assert_equal([''], readfile('Xchanged.txt'))
+
+ " clean up
+ call delete('Xchanged.txt')
+ bwipe!
+endfunc
diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
index 6d0a6b9d5e..e2a035b0b2 100644
--- a/src/nvim/testdir/test_functions.vim
+++ b/src/nvim/testdir/test_functions.vim
@@ -926,3 +926,26 @@ func Test_redo_in_nested_functions()
delfunc Operator
delfunc Apply
endfunc
+
+func Test_trim()
+ call assert_equal("Testing", trim(" \t\r\r\x0BTesting \t\n\r\n\t\x0B\x0B"))
+ call assert_equal("Testing", trim(" \t \r\r\n\n\x0BTesting \t\n\r\n\t\x0B\x0B"))
+ call assert_equal("RESERVE", trim("xyz \twwRESERVEzyww \t\t", " wxyz\t"))
+ call assert_equal("wRE \tSERVEzyww", trim("wRE \tSERVEzyww"))
+ call assert_equal("abcd\t xxxx tail", trim(" \tabcd\t xxxx tail"))
+ call assert_equal("\tabcd\t xxxx tail", trim(" \tabcd\t xxxx tail", " "))
+ call assert_equal(" \tabcd\t xxxx tail", trim(" \tabcd\t xxxx tail", "abx"))
+ call assert_equal("RESERVE", trim("你RESERVE好", "你好"))
+ call assert_equal("您R E SER V E早", trim("你好您R E SER V E早好你你", "你好"))
+ call assert_equal("你好您R E SER V E早好你你", trim(" \n\r\r 你好您R E SER V E早好你你 \t \x0B", ))
+ call assert_equal("您R E SER V E早好你你 \t \x0B", trim(" 你好您R E SER V E早好你你 \t \x0B", " 你好"))
+ call assert_equal("您R E SER V E早好你你 \t \x0B", trim(" tteesstttt你好您R E SER V E早好你你 \t \x0B ttestt", " 你好tes"))
+ call assert_equal("您R E SER V E早好你你 \t \x0B", trim(" tteesstttt你好您R E SER V E早好你你 \t \x0B ttestt", " 你你你好好好tttsses"))
+ call assert_equal("留下", trim("这些些不要这些留下这些", "这些不要"))
+ call assert_equal("", trim("", ""))
+ call assert_equal("a", trim("a", ""))
+ call assert_equal("", trim("", "a"))
+
+ let chars = join(map(range(1, 0x20) + [0xa0], {n -> nr2char(n)}), '')
+ call assert_equal("x", trim(chars . "x" . chars))
+endfunc
diff --git a/src/nvim/testdir/test_join.vim b/src/nvim/testdir/test_join.vim
new file mode 100644
index 0000000000..1c97414164
--- /dev/null
+++ b/src/nvim/testdir/test_join.vim
@@ -0,0 +1,35 @@
+" Test for joining lines.
+
+func Test_join_with_count()
+ new
+ call setline(1, ['one', 'two', 'three', 'four'])
+ normal J
+ call assert_equal('one two', getline(1))
+ %del
+ call setline(1, ['one', 'two', 'three', 'four'])
+ normal 10J
+ call assert_equal('one two three four', getline(1))
+ quit!
+endfunc
+
+" Tests for setting the '[,'] marks when joining lines.
+func Test_join_marks()
+ enew
+ call append(0, [
+ \ "\t\tO sodales, ludite, vos qui",
+ \ "attamen consulite per voster honur. Tua pulchra " .
+ \ "facies me fay planszer milies",
+ \ "",
+ \ "This line.",
+ \ "Should be joined with the next line",
+ \ "and with this line"])
+
+ normal gg0gqj
+ call assert_equal([0, 1, 1, 0], getpos("'["))
+ call assert_equal([0, 2, 1, 0], getpos("']"))
+
+ /^This line/;'}-join
+ call assert_equal([0, 4, 11, 0], getpos("'["))
+ call assert_equal([0, 4, 67, 0], getpos("']"))
+ enew!
+endfunc
diff --git a/src/nvim/testdir/test_stat.vim b/src/nvim/testdir/test_stat.vim
index 0a09130b0c..c276df0a92 100644
--- a/src/nvim/testdir/test_stat.vim
+++ b/src/nvim/testdir/test_stat.vim
@@ -43,7 +43,16 @@ func Test_existent_directory()
call assert_equal(0, getfsize(dname))
call assert_equal('dir', getftype(dname))
- call assert_equal('rwx', getfperm(dname)[0:2])
+ call assert_equal(has('win32') ? 'rw-' : 'rwx', getfperm(dname)[0:2])
+endfunc
+
+func SleepForTimestamp()
+ " FAT has a granularity of 2 seconds, otherwise it's usually 1 second
+ if has('win32')
+ sleep 2
+ else
+ sleep 2
+ endif
endfunc
func Test_checktime()
@@ -53,12 +62,7 @@ func Test_checktime()
call writefile(fl, fname)
set autoread
exec 'e' fname
- " FAT has a granularity of 2 seconds, otherwise it's usually 1 second
- if has('win32')
- sleep 2
- else
- sleep 2
- endif
+ call SleepForTimestamp()
let fl = readfile(fname)
let fl[0] .= ' - checktime'
call writefile(fl, fname)
@@ -68,6 +72,46 @@ func Test_checktime()
call delete(fname)
endfunc
+func Test_autoread_file_deleted()
+ new Xautoread
+ set autoread
+ call setline(1, 'original')
+ w!
+
+ call SleepForTimestamp()
+ if has('win32')
+ silent !echo changed > Xautoread
+ else
+ silent !echo 'changed' > Xautoread
+ endif
+ checktime
+ call assert_equal('changed', trim(getline(1)))
+
+ call SleepForTimestamp()
+ messages clear
+ if has('win32')
+ silent !del Xautoread
+ else
+ silent !rm Xautoread
+ endif
+ checktime
+ call assert_match('E211:', execute('messages'))
+ call assert_equal('changed', trim(getline(1)))
+
+ call SleepForTimestamp()
+ if has('win32')
+ silent !echo recreated > Xautoread
+ else
+ silent !echo 'recreated' > Xautoread
+ endif
+ checktime
+ call assert_equal('recreated', trim(getline(1)))
+
+ call delete('Xautoread')
+ bwipe!
+endfunc
+
+
func Test_nonexistent_file()
let fname = 'Xtest.tmp'
@@ -78,6 +122,41 @@ func Test_nonexistent_file()
call assert_equal('', getfperm(fname))
endfunc
+func Test_getftype()
+ call assert_equal('file', getftype(v:progpath))
+ call assert_equal('dir', getftype('.'))
+
+ if !has('unix')
+ return
+ endif
+
+ silent !ln -s Xfile Xlink
+ call assert_equal('link', getftype('Xlink'))
+ call delete('Xlink')
+
+ if executable('mkfifo')
+ silent !mkfifo Xfifo
+ call assert_equal('fifo', getftype('Xfifo'))
+ call delete('Xfifo')
+ endif
+
+ for cdevfile in systemlist('find /dev -type c -maxdepth 2 2>/dev/null')
+ call assert_equal('cdev', getftype(cdevfile))
+ endfor
+
+ for bdevfile in systemlist('find /dev -type b -maxdepth 2 2>/dev/null')
+ call assert_equal('bdev', getftype(bdevfile))
+ endfor
+
+ " The /run/ directory typically contains socket files.
+ " If it does not, test won't fail but will not test socket files.
+ for socketfile in systemlist('find /run -type s -maxdepth 2 2>/dev/null')
+ call assert_equal('socket', getftype(socketfile))
+ endfor
+
+ " TODO: file type 'other' is not tested. How can we test it?
+endfunc
+
func Test_win32_symlink_dir()
" On Windows, non-admin users cannot create symlinks.
" So we use an existing symlink for this test.
diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim
index 268a153077..f9bd8b5246 100644
--- a/src/nvim/testdir/test_tagjump.vim
+++ b/src/nvim/testdir/test_tagjump.vim
@@ -230,4 +230,32 @@ func Test_tag_file_encoding()
call delete('Xtags1')
endfunc
+func Test_tagjump_etags()
+ if !has('emacs_tags')
+ return
+ endif
+ call writefile([
+ \ "void foo() {}",
+ \ "int main(int argc, char **argv)",
+ \ "{",
+ \ "\tfoo();",
+ \ "\treturn 0;",
+ \ "}",
+ \ ], 'Xmain.c')
+
+ call writefile([
+ \ "\x0c",
+ \ "Xmain.c,64",
+ \ "void foo() {}\x7ffoo\x011,0",
+ \ "int main(int argc, char **argv)\x7fmain\x012,14",
+ \ ], 'Xtags')
+ set tags=Xtags
+ ta foo
+ call assert_equal('void foo() {}', getline('.'))
+
+ call delete('Xtags')
+ call delete('Xmain.c')
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_taglist.vim b/src/nvim/testdir/test_taglist.vim
index 2d1557ebd9..3ad2025915 100644
--- a/src/nvim/testdir/test_taglist.vim
+++ b/src/nvim/testdir/test_taglist.vim
@@ -1,4 +1,4 @@
-" test 'taglist' function
+" test 'taglist' function and :tags command
func Test_taglist()
call writefile([
@@ -56,3 +56,8 @@ func Test_taglist_ctags_etags()
call delete('Xtags')
endfunc
+
+func Test_tags_too_long()
+ call assert_fails('tag ' . repeat('x', 1020), 'E426')
+ tags
+endfunc
diff --git a/src/nvim/testdir/test_user_func.vim b/src/nvim/testdir/test_user_func.vim
new file mode 100644
index 0000000000..e7a3701386
--- /dev/null
+++ b/src/nvim/testdir/test_user_func.vim
@@ -0,0 +1,96 @@
+" Test for user functions.
+" Also test an <expr> mapping calling a function.
+" Also test that a builtin function cannot be replaced.
+" Also test for regression when calling arbitrary expression.
+
+func Table(title, ...)
+ let ret = a:title
+ let idx = 1
+ while idx <= a:0
+ exe "let ret = ret . a:" . idx
+ let idx = idx + 1
+ endwhile
+ return ret
+endfunc
+
+func Compute(n1, n2, divname)
+ if a:n2 == 0
+ return "fail"
+ endif
+ exe "let g:" . a:divname . " = ". a:n1 / a:n2
+ return "ok"
+endfunc
+
+func Expr1()
+ silent! normal! v
+ return "111"
+endfunc
+
+func Expr2()
+ call search('XX', 'b')
+ return "222"
+endfunc
+
+func ListItem()
+ let g:counter += 1
+ return g:counter . '. '
+endfunc
+
+func ListReset()
+ let g:counter = 0
+ return ''
+endfunc
+
+func FuncWithRef(a)
+ unlet g:FuncRef
+ return a:a
+endfunc
+
+func Test_user_func()
+ let g:FuncRef=function("FuncWithRef")
+ let g:counter = 0
+ inoremap <expr> ( ListItem()
+ inoremap <expr> [ ListReset()
+ imap <expr> + Expr1()
+ imap <expr> * Expr2()
+ let g:retval = "nop"
+
+ call assert_equal('xxx4asdf', Table("xxx", 4, "asdf"))
+ call assert_equal('fail', Compute(45, 0, "retval"))
+ call assert_equal('nop', g:retval)
+ call assert_equal('ok', Compute(45, 5, "retval"))
+ call assert_equal(9, g:retval)
+ call assert_equal(333, g:FuncRef(333))
+
+ enew
+
+ normal oXX+-XX
+ call assert_equal('XX111-XX', getline('.'))
+ normal o---*---
+ call assert_equal('---222---', getline('.'))
+ normal o(one
+ call assert_equal('1. one', getline('.'))
+ normal o(two
+ call assert_equal('2. two', getline('.'))
+ normal o[(one again
+ call assert_equal('1. one again', getline('.'))
+
+ call assert_equal(3, max([1, 2, 3]))
+ call assert_fails("call extend(g:, {'max': function('min')})", 'E704')
+ call assert_equal(3, max([1, 2, 3]))
+
+ " Regression: the first line below used to throw ?E110: Missing ')'?
+ " Second is here just to prove that this line is correct when not skipping
+ " rhs of &&.
+ call assert_equal(0, (0 && (function('tr'))(1, 2, 3)))
+ call assert_equal(1, (1 && (function('tr'))(1, 2, 3)))
+
+ delfunc Table
+ delfunc Compute
+ delfunc Expr1
+ delfunc Expr2
+ delfunc ListItem
+ delfunc ListReset
+ unlet g:retval g:counter
+ enew!
+endfunc
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index 508d25cd3b..df14ddf988 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -874,7 +874,7 @@ static cursorentry_T decode_cursor_entry(Dictionary args)
r.blinkon = (int)value.data.integer;
} else if (strequal(key, "blinkoff")) {
r.blinkoff = (int)value.data.integer;
- } else if (strequal(key, "hl_id")) {
+ } else if (strequal(key, "attr_id")) {
r.id = (int)value.data.integer;
}
}
@@ -942,13 +942,10 @@ static void tui_set_mode(UI *ui, ModeShape mode)
TUIData *data = ui->data;
cursorentry_T c = data->cursor_shapes[mode];
- if (c.id != 0 && ui->rgb) {
- int attr = syn_id2attr(c.id);
- if (attr > 0) {
- HlAttrs *aep = syn_attr2entry(attr);
- UNIBI_SET_NUM_VAR(data->params[0], aep->rgb_bg_color);
- unibi_out_ext(ui, data->unibi_ext.set_cursor_color);
- }
+ if (c.id != 0 && c.id < (int)kv_size(data->attrs) && ui->rgb) {
+ int color = kv_A(data->attrs, c.id).rgb_bg_color;
+ UNIBI_SET_NUM_VAR(data->params[0], color);
+ unibi_out_ext(ui, data->unibi_ext.set_cursor_color);
}
int shape;
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index ef68b804ba..07aa032a50 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -55,6 +55,7 @@ static int row = 0, col = 0;
static bool pending_cursor_update = false;
static int busy = 0;
static int mode_idx = SHAPE_IDX_N;
+static bool pending_mode_info_update = false;
static bool pending_mode_update = false;
#if MIN_LOG_LEVEL > DEBUG_LOG_LEVEL
@@ -368,10 +369,7 @@ void ui_add_linewrap(int row)
void ui_mode_info_set(void)
{
- Array style = mode_style_array();
- bool enabled = (*p_guicursor != NUL);
- ui_call_mode_info_set(enabled, style);
- api_free_array(style);
+ pending_mode_info_update = true;
}
int ui_current_row(void)
@@ -391,6 +389,13 @@ void ui_flush(void)
ui_call_grid_cursor_goto(1, row, col);
pending_cursor_update = false;
}
+ if (pending_mode_info_update) {
+ Array style = mode_style_array();
+ bool enabled = (*p_guicursor != NUL);
+ ui_call_mode_info_set(enabled, style);
+ api_free_array(style);
+ pending_mode_info_update = false;
+ }
if (pending_mode_update) {
char *full_name = shape_table[mode_idx].full_name;
ui_call_mode_change(cstr_as_string(full_name), mode_idx);
diff --git a/test/functional/legacy/packadd_spec.lua b/test/functional/legacy/packadd_spec.lua
index 67f6006d1d..7c3d48317b 100644
--- a/test/functional/legacy/packadd_spec.lua
+++ b/test/functional/legacy/packadd_spec.lua
@@ -15,7 +15,7 @@ describe('packadd', function()
source([=[
func SetUp()
- let s:topdir = expand(expand('%:p:h') . '/Xdir')
+ let s:topdir = expand(getcwd() . '/Xdir')
exe 'set packpath=' . s:topdir
let s:plugdir = expand(s:topdir . '/pack/mine/opt/mytest')
endfunc
diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua
index 812c095add..d1cddbe06a 100644
--- a/test/functional/ui/cursor_spec.lua
+++ b/test/functional/ui/cursor_spec.lua
@@ -28,6 +28,8 @@ describe('ui/cursor', function()
name = 'normal',
hl_id = 0,
id_lm = 0,
+ attr = {},
+ attr_lm = {},
mouse_shape = 0,
short_name = 'n' },
[2] = {
@@ -39,6 +41,8 @@ describe('ui/cursor', function()
name = 'visual',
hl_id = 0,
id_lm = 0,
+ attr = {},
+ attr_lm = {},
mouse_shape = 0,
short_name = 'v' },
[3] = {
@@ -50,6 +54,8 @@ describe('ui/cursor', function()
name = 'insert',
hl_id = 0,
id_lm = 0,
+ attr = {},
+ attr_lm = {},
mouse_shape = 0,
short_name = 'i' },
[4] = {
@@ -61,6 +67,8 @@ describe('ui/cursor', function()
name = 'replace',
hl_id = 0,
id_lm = 0,
+ attr = {},
+ attr_lm = {},
mouse_shape = 0,
short_name = 'r' },
[5] = {
@@ -72,6 +80,8 @@ describe('ui/cursor', function()
name = 'cmdline_normal',
hl_id = 0,
id_lm = 0,
+ attr = {},
+ attr_lm = {},
mouse_shape = 0,
short_name = 'c' },
[6] = {
@@ -83,6 +93,8 @@ describe('ui/cursor', function()
name = 'cmdline_insert',
hl_id = 0,
id_lm = 0,
+ attr = {},
+ attr_lm = {},
mouse_shape = 0,
short_name = 'ci' },
[7] = {
@@ -94,6 +106,8 @@ describe('ui/cursor', function()
name = 'cmdline_replace',
hl_id = 0,
id_lm = 0,
+ attr = {},
+ attr_lm = {},
mouse_shape = 0,
short_name = 'cr' },
[8] = {
@@ -105,6 +119,8 @@ describe('ui/cursor', function()
name = 'operator',
hl_id = 0,
id_lm = 0,
+ attr = {},
+ attr_lm = {},
mouse_shape = 0,
short_name = 'o' },
[9] = {
@@ -116,6 +132,8 @@ describe('ui/cursor', function()
name = 'visual_select',
hl_id = 0,
id_lm = 0,
+ attr = {},
+ attr_lm = {},
mouse_shape = 0,
short_name = 've' },
[10] = {
@@ -155,6 +173,8 @@ describe('ui/cursor', function()
name = 'showmatch',
hl_id = 0,
id_lm = 0,
+ attr = {},
+ attr_lm = {},
short_name = 'sm' },
}
@@ -179,6 +199,7 @@ describe('ui/cursor', function()
end)
-- Change the cursor style.
+ helpers.command('hi Cursor guibg=DarkGray')
helpers.command('set guicursor=n-v-c:block,i-ci-ve:ver25,r-cr-o:hor20'
..',a:blinkwait700-blinkoff400-blinkon250-Cursor/lCursor'
..',sm:block-blinkwait175-blinkoff150-blinkon175')
@@ -194,7 +215,10 @@ describe('ui/cursor', function()
if m.blinkoff then m.blinkoff = 400 end
if m.blinkwait then m.blinkwait = 700 end
end
- if m.hl_id then m.hl_id = 49 end
+ if m.hl_id then
+ m.hl_id = 49
+ m.attr = {background = Screen.colors.DarkGray}
+ end
if m.id_lm then m.id_lm = 50 end
end
@@ -205,6 +229,26 @@ describe('ui/cursor', function()
eq('normal', screen.mode)
end)
+ -- Change hl groups only, should update the styles
+ helpers.command('hi Cursor guibg=Red')
+ helpers.command('hi lCursor guibg=Green')
+
+ -- Update the expected values.
+ for _, m in ipairs(expected_mode_info) do
+ if m.hl_id then
+ m.attr = {background = Screen.colors.Red}
+ end
+ if m.id_lm then
+ m.attr_lm = {background = Screen.colors.Green}
+ end
+ end
+ -- Assert the new expectation.
+ screen:expect(function()
+ eq(expected_mode_info, screen._mode_info)
+ eq(true, screen._cursor_style_enabled)
+ eq('normal', screen.mode)
+ end)
+
-- Another cursor style.
meths.set_option('guicursor', 'n-v-c:ver35-blinkwait171-blinkoff172-blinkon173'
..',ve:hor35,o:ver50,i-ci:block,r-cr:hor90,sm:ver42')
diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua
index c40b2210ff..872a9d3200 100644
--- a/test/functional/ui/screen.lua
+++ b/test/functional/ui/screen.lua
@@ -386,6 +386,17 @@ end
function Screen:_handle_mode_info_set(cursor_style_enabled, mode_info)
self._cursor_style_enabled = cursor_style_enabled
+ for _, item in pairs(mode_info) do
+ -- attr IDs are not stable, but their value should be
+ if item.attr_id ~= nil then
+ item.attr = self._attr_table[item.attr_id][1]
+ item.attr_id = nil
+ end
+ if item.attr_id_lm ~= nil then
+ item.attr_lm = self._attr_table[item.attr_id_lm][1]
+ item.attr_id_lm = nil
+ end
+ end
self._mode_info = mode_info
end