diff options
| -rw-r--r-- | runtime/doc/ui.txt | 9 | ||||
| -rw-r--r-- | src/nvim/api/ui.c | 22 | ||||
| -rw-r--r-- | src/nvim/buffer.c | 5 | ||||
| -rw-r--r-- | src/nvim/cursor_shape.c | 24 | ||||
| -rw-r--r-- | src/nvim/edit.c | 64 | ||||
| -rw-r--r-- | src/nvim/highlight.c | 91 | ||||
| -rw-r--r-- | src/nvim/quickfix.c | 12 | ||||
| -rw-r--r-- | src/nvim/screen.c | 16 | ||||
| -rw-r--r-- | src/nvim/syntax.c | 7 | ||||
| -rw-r--r-- | src/nvim/tag.c | 46 | ||||
| -rw-r--r-- | src/nvim/testdir/Makefile | 1 | ||||
| -rw-r--r-- | src/nvim/testdir/test_alot.vim | 1 | ||||
| -rw-r--r-- | src/nvim/testdir/test_autocmd.vim | 71 | ||||
| -rw-r--r-- | src/nvim/testdir/test_join.vim | 35 | ||||
| -rw-r--r-- | src/nvim/testdir/test_tagjump.vim | 28 | ||||
| -rw-r--r-- | src/nvim/testdir/test_taglist.vim | 7 | ||||
| -rw-r--r-- | src/nvim/testdir/test_user_func.vim | 96 | ||||
| -rw-r--r-- | src/nvim/tui/tui.c | 13 | ||||
| -rw-r--r-- | src/nvim/ui.c | 13 | ||||
| -rw-r--r-- | test/functional/legacy/packadd_spec.lua | 2 | ||||
| -rw-r--r-- | test/functional/ui/cursor_spec.lua | 46 | ||||
| -rw-r--r-- | test/functional/ui/screen.lua | 11 | 
22 files changed, 485 insertions, 135 deletions
| 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/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 f7fdc6060d..bcfef89cc2 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) @@ -4542,7 +4542,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) @@ -4809,7 +4809,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; @@ -4832,7 +4834,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_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_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 | 
