From e47622f26b40d88bb8582c391df30474a64a082c Mon Sep 17 00:00:00 2001 From: Jakob Schnitzer Date: Sun, 26 Mar 2017 13:20:44 +0200 Subject: options: setlocal should only set local value For 'iminsert' and 'imsearch' the global value was always changed. --- src/nvim/option.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'src') diff --git a/src/nvim/option.c b/src/nvim/option.c index 0bf81b4d3a..f622efeb0b 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4120,7 +4120,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, errmsg = e_invarg; curbuf->b_p_iminsert = B_IMODE_NONE; } - p_iminsert = curbuf->b_p_iminsert; showmode(); /* Show/unshow value of 'keymap' in status lines. */ status_redraw_curbuf(); @@ -4134,7 +4133,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, errmsg = e_invarg; curbuf->b_p_imsearch = B_IMODE_NONE; } - p_imsearch = curbuf->b_p_imsearch; } /* if 'titlelen' has changed, redraw the title */ else if (pp == &p_titlelen) { -- cgit From 79d3e9494299ae91f297c5a06dc3fd4f2f71f13e Mon Sep 17 00:00:00 2001 From: Jakob Schnitzer Date: Mon, 27 Mar 2017 18:52:16 +0200 Subject: options: move code around in set_num_option handle side-effects after validation --- src/nvim/option.c | 175 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 101 insertions(+), 74 deletions(-) (limited to 'src') diff --git a/src/nvim/option.c b/src/nvim/option.c index f622efeb0b..248b5205ec 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4004,9 +4004,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, curbuf->b_p_sw = curbuf->b_p_ts; } - /* - * Number options that need some action when changed - */ + // Number options that need some validation when changed. if (pp == &p_wh || pp == &p_hh) { if (p_wh < 1) { errmsg = e_positive; @@ -4020,14 +4018,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, errmsg = e_positive; p_hh = 0; } - - /* Change window height NOW */ - if (lastwin != firstwin) { - if (pp == &p_wh && curwin->w_height < p_wh) - win_setheight((int)p_wh); - if (pp == &p_hh && curbuf->b_help && curwin->w_height < p_hh) - win_setheight((int)p_hh); - } } /* 'winminheight' */ else if (pp == &p_wmh) { @@ -4039,7 +4029,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, errmsg = e_winheight; p_wmh = p_wh; } - win_setminheight(); } else if (pp == &p_wiw) { if (p_wiw < 1) { errmsg = e_positive; @@ -4049,10 +4038,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, errmsg = e_winwidth; p_wiw = p_wmw; } - - /* Change window width NOW */ - if (lastwin != firstwin && curwin->w_width < p_wiw) - win_setwidth((int)p_wiw); } /* 'winminwidth' */ else if (pp == &p_wmw) { @@ -4064,29 +4049,11 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, errmsg = e_winwidth; p_wmw = p_wiw; } - win_setminheight(); - } else if (pp == &p_ls) { - /* (re)set last window status line */ - last_status(false); - } - /* (re)set tab page line */ - else if (pp == &p_stal) { - shell_new_rows(); /* recompute window positions and heights */ } /* 'foldlevel' */ else if (pp == &curwin->w_p_fdl) { if (curwin->w_p_fdl < 0) curwin->w_p_fdl = 0; - newFoldLevel(); - } - /* 'foldminlines' */ - else if (pp == &curwin->w_p_fml) { - foldUpdateAll(curwin); - } - /* 'foldnestmax' */ - else if (pp == &curwin->w_p_fdn) { - if (foldmethodIsSyntax(curwin) || foldmethodIsIndent(curwin)) - foldUpdateAll(curwin); } /* 'foldcolumn' */ else if (pp == &curwin->w_p_fdc) { @@ -4097,16 +4064,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, errmsg = e_invarg; curwin->w_p_fdc = 12; } - // 'shiftwidth' or 'tabstop' - } else if (pp == &curbuf->b_p_sw || pp == (long *)&curbuf->b_p_ts) { - if (foldmethodIsIndent(curwin)) { - foldUpdateAll(curwin); - } - // When 'shiftwidth' changes, or it's zero and 'tabstop' changes: - // parse 'cinoptions'. - if (pp == &curbuf->b_p_sw || curbuf->b_p_sw == 0) { - parse_cino(curbuf); - } } /* 'maxcombine' */ else if (pp == &p_mco) { @@ -4114,15 +4071,11 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, p_mco = MAX_MCO; else if (p_mco < 0) p_mco = 0; - screenclear(); /* will re-allocate the screen */ } else if (pp == &curbuf->b_p_iminsert) { if (curbuf->b_p_iminsert < 0 || curbuf->b_p_iminsert > B_IMODE_LAST) { errmsg = e_invarg; curbuf->b_p_iminsert = B_IMODE_NONE; } - showmode(); - /* Show/unshow value of 'keymap' in status lines. */ - status_redraw_curbuf(); } else if (pp == &p_window) { if (p_window < 1) p_window = 1; @@ -4140,8 +4093,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, errmsg = e_positive; p_titlelen = 85; } - if (starting != NO_SCREEN && old_value != p_titlelen) - need_maketitle = TRUE; } /* if p_ch changed value, change the command line height */ else if (pp == &p_ch) { @@ -4151,12 +4102,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } if (p_ch > Rows - min_rows() + 1) p_ch = Rows - min_rows() + 1; - - /* Only compute the new window layout when startup has been - * completed. Otherwise the frame sizes may be wrong. */ - if (p_ch != old_value && full_screen - ) - command_height(); } /* when 'updatecount' changes from zero to non-zero, open swap files */ else if (pp == &p_uc) { @@ -4164,8 +4109,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, errmsg = e_positive; p_uc = 100; } - if (p_uc && !old_value) - ml_open_files(); } else if (pp == &curwin->w_p_cole) { if (curwin->w_p_cole < 0) { errmsg = e_positive; @@ -4175,18 +4118,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, curwin->w_p_cole = 3; } } - /* sync undo before 'undolevels' changes */ - else if (pp == &p_ul) { - /* use the old value, otherwise u_sync() may not work properly */ - p_ul = old_value; - u_sync(TRUE); - p_ul = value; - } else if (pp == &curbuf->b_p_ul) { - /* use the old value, otherwise u_sync() may not work properly */ - curbuf->b_p_ul = old_value; - u_sync(TRUE); - curbuf->b_p_ul = value; - } /* 'numberwidth' must be positive */ else if (pp == &curwin->w_p_nuw) { if (curwin->w_p_nuw < 1) { @@ -4203,10 +4134,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, errmsg = e_positive; curbuf->b_p_tw = 0; } - - FOR_ALL_TAB_WINDOWS(tp, wp) { - check_colorcolumn(wp); - } } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { // 'scrollback' if (*pp < -1 || *pp > SB_MAX @@ -4219,6 +4146,106 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } } + // Number options that need some action when changed + if (pp == &p_wh || pp == &p_hh) { + /* Change window height NOW */ + if (lastwin != firstwin) { + if (pp == &p_wh && curwin->w_height < p_wh) + win_setheight((int)p_wh); + if (pp == &p_hh && curbuf->b_help && curwin->w_height < p_hh) + win_setheight((int)p_hh); + } + } + else if (pp == &p_wmh) { + win_setminheight(); + } else if (pp == &p_wiw) { + /* Change window width NOW */ + if (lastwin != firstwin && curwin->w_width < p_wiw) + win_setwidth((int)p_wiw); + } + else if (pp == &p_wmw) { + win_setminheight(); + } else if (pp == &p_ls) { + /* (re)set last window status line */ + last_status(false); + } + /* (re)set tab page line */ + else if (pp == &p_stal) { + shell_new_rows(); /* recompute window positions and heights */ + } + else if (pp == &curwin->w_p_fdl) { + newFoldLevel(); + } + /* 'foldminlines' */ + else if (pp == &curwin->w_p_fml) { + foldUpdateAll(curwin); + } + /* 'foldnestmax' */ + else if (pp == &curwin->w_p_fdn) { + if (foldmethodIsSyntax(curwin) || foldmethodIsIndent(curwin)) + foldUpdateAll(curwin); + // 'shiftwidth' or 'tabstop' + } else if (pp == &curbuf->b_p_sw || pp == (long *)&curbuf->b_p_ts) { + if (foldmethodIsIndent(curwin)) { + foldUpdateAll(curwin); + } + // When 'shiftwidth' changes, or it's zero and 'tabstop' changes: + // parse 'cinoptions'. + if (pp == &curbuf->b_p_sw || curbuf->b_p_sw == 0) { + parse_cino(curbuf); + } + } + /* 'maxcombine' */ + else if (pp == &p_mco) { + screenclear(); /* will re-allocate the screen */ + } else if (pp == &curbuf->b_p_iminsert) { + showmode(); + /* Show/unshow value of 'keymap' in status lines. */ + status_redraw_curbuf(); + } + /* if 'titlelen' has changed, redraw the title */ + else if (pp == &p_titlelen) { + if (starting != NO_SCREEN && old_value != p_titlelen) + need_maketitle = TRUE; + } + /* if p_ch changed value, change the command line height */ + else if (pp == &p_ch) { + + /* Only compute the new window layout when startup has been + * completed. Otherwise the frame sizes may be wrong. */ + if (p_ch != old_value && full_screen + ) + command_height(); + } + /* when 'updatecount' changes from zero to non-zero, open swap files */ + else if (pp == &p_uc) { + if (p_uc && !old_value) + ml_open_files(); + } + /* sync undo before 'undolevels' changes */ + else if (pp == &p_ul) { + /* use the old value, otherwise u_sync() may not work properly */ + p_ul = old_value; + u_sync(TRUE); + p_ul = value; + } else if (pp == &curbuf->b_p_ul) { + /* use the old value, otherwise u_sync() may not work properly */ + curbuf->b_p_ul = old_value; + u_sync(TRUE); + curbuf->b_p_ul = value; + } else if (pp == &curbuf->b_p_tw) { + FOR_ALL_TAB_WINDOWS(tp, wp) { + check_colorcolumn(wp); + } + } + else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { + if (curbuf->terminal) { + // Force the scrollback to take effect. + terminal_resize(curbuf->terminal, UINT16_MAX, UINT16_MAX); + } + } + + /* * Check the bounds for numeric options here */ -- cgit From f4920fb485da92d546c1e01f5f10766d00c3791d Mon Sep 17 00:00:00 2001 From: Jakob Schnitzer Date: Mon, 27 Mar 2017 19:03:46 +0200 Subject: options: if invalid value is given, reset to old value --- src/nvim/option.c | 112 ++++++++++++++++++++---------------------------------- 1 file changed, 42 insertions(+), 70 deletions(-) (limited to 'src') diff --git a/src/nvim/option.c b/src/nvim/option.c index 248b5205ec..d069cceb39 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4005,144 +4005,114 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } // Number options that need some validation when changed. - if (pp == &p_wh || pp == &p_hh) { + if (pp == &p_wh) { if (p_wh < 1) { errmsg = e_positive; - p_wh = 1; } if (p_wmh > p_wh) { errmsg = e_winheight; - p_wh = p_wmh; } + } else if (pp == &p_hh) { if (p_hh < 0) { errmsg = e_positive; - p_hh = 0; } - } - /* 'winminheight' */ - else if (pp == &p_wmh) { + } else if (pp == &p_wmh) { if (p_wmh < 0) { errmsg = e_positive; - p_wmh = 0; } if (p_wmh > p_wh) { errmsg = e_winheight; - p_wmh = p_wh; } } else if (pp == &p_wiw) { if (p_wiw < 1) { errmsg = e_positive; - p_wiw = 1; } if (p_wmw > p_wiw) { errmsg = e_winwidth; - p_wiw = p_wmw; } - } - /* 'winminwidth' */ - else if (pp == &p_wmw) { + } else if (pp == &p_wmw) { if (p_wmw < 0) { errmsg = e_positive; - p_wmw = 0; } if (p_wmw > p_wiw) { errmsg = e_winwidth; - p_wmw = p_wiw; } - } - /* 'foldlevel' */ - else if (pp == &curwin->w_p_fdl) { - if (curwin->w_p_fdl < 0) - curwin->w_p_fdl = 0; - } - /* 'foldcolumn' */ - else if (pp == &curwin->w_p_fdc) { + } else if (pp == &curwin->w_p_fdl) { + if (curwin->w_p_fdl < 0) { + errmsg = e_positive; + } + } else if (pp == &curwin->w_p_fdc) { if (curwin->w_p_fdc < 0) { errmsg = e_positive; - curwin->w_p_fdc = 0; } else if (curwin->w_p_fdc > 12) { errmsg = e_invarg; - curwin->w_p_fdc = 12; } - } - /* 'maxcombine' */ - else if (pp == &p_mco) { - if (p_mco > MAX_MCO) - p_mco = MAX_MCO; - else if (p_mco < 0) - p_mco = 0; + } else if (pp == &p_mco) { + if (p_mco > MAX_MCO) { + errmsg = e_invarg; + } else if (p_mco < 0) { + errmsg = e_positive; + } } else if (pp == &curbuf->b_p_iminsert) { if (curbuf->b_p_iminsert < 0 || curbuf->b_p_iminsert > B_IMODE_LAST) { errmsg = e_invarg; - curbuf->b_p_iminsert = B_IMODE_NONE; } - } else if (pp == &p_window) { - if (p_window < 1) - p_window = 1; - else if (p_window >= Rows) - p_window = Rows - 1; } else if (pp == &curbuf->b_p_imsearch) { if (curbuf->b_p_imsearch < -1 || curbuf->b_p_imsearch > B_IMODE_LAST) { errmsg = e_invarg; - curbuf->b_p_imsearch = B_IMODE_NONE; } - } - /* if 'titlelen' has changed, redraw the title */ - else if (pp == &p_titlelen) { + } else if (pp == &p_titlelen) { if (p_titlelen < 0) { errmsg = e_positive; - p_titlelen = 85; - } - } - /* if p_ch changed value, change the command line height */ - else if (pp == &p_ch) { - if (p_ch < 1) { - errmsg = e_positive; - p_ch = 1; } - if (p_ch > Rows - min_rows() + 1) - p_ch = Rows - min_rows() + 1; - } - /* when 'updatecount' changes from zero to non-zero, open swap files */ - else if (pp == &p_uc) { + } else if (pp == &p_uc) { if (p_uc < 0) { errmsg = e_positive; - p_uc = 100; } } else if (pp == &curwin->w_p_cole) { if (curwin->w_p_cole < 0) { errmsg = e_positive; - curwin->w_p_cole = 0; } else if (curwin->w_p_cole > 3) { errmsg = e_invarg; - curwin->w_p_cole = 3; } - } - /* 'numberwidth' must be positive */ - else if (pp == &curwin->w_p_nuw) { + } else if (pp == &curwin->w_p_nuw) { if (curwin->w_p_nuw < 1) { errmsg = e_positive; - curwin->w_p_nuw = 1; } if (curwin->w_p_nuw > 10) { errmsg = e_invarg; - curwin->w_p_nuw = 10; } curwin->w_nrwidth_line_count = 0; } else if (pp == &curbuf->b_p_tw) { if (curbuf->b_p_tw < 0) { errmsg = e_positive; - curbuf->b_p_tw = 0; } } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { - // 'scrollback' if (*pp < -1 || *pp > SB_MAX || (opt_flags == OPT_LOCAL && !curbuf->terminal)) { errmsg = e_invarg; - *pp = old_value; - } else if (curbuf->terminal) { - // Force the scrollback to take effect. - terminal_resize(curbuf->terminal, UINT16_MAX, UINT16_MAX); + } + } else if (pp == &p_ch) { + if (p_ch < 1) { + errmsg = e_positive; + } + } + + if (errmsg != NULL) { + *pp = old_value; + return errmsg; + } + + // For these options we want to fix some invalid values. + if (pp == &p_window) { + if (p_window < 1) { + p_window = 1; + } else if (p_window >= Rows) { + p_window = Rows - 1; + } + } else if (pp == &p_ch) { + if (p_ch > Rows - min_rows() + 1) { + p_ch = Rows - min_rows() + 1; } } @@ -4243,6 +4213,8 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, // Force the scrollback to take effect. terminal_resize(curbuf->terminal, UINT16_MAX, UINT16_MAX); } + } else if (pp == &curwin->w_p_nuw) { + curwin->w_nrwidth_line_count = 0; } -- cgit From 1a56a032fe6060c3b4c8532c209ccfaa90fdf74e Mon Sep 17 00:00:00 2001 From: Jakob Schnitzer Date: Mon, 27 Mar 2017 19:17:58 +0200 Subject: options: clean up num_options side-effects --- src/nvim/option.c | 118 +++++++++++++++++++++++------------------------------- 1 file changed, 50 insertions(+), 68 deletions(-) (limited to 'src') diff --git a/src/nvim/option.c b/src/nvim/option.c index d069cceb39..f6639d4f6a 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -3999,11 +3999,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, /* Remember where the option was set. */ set_option_scriptID_idx(opt_idx, opt_flags, current_SID); - if (curbuf->b_p_sw < 0) { - errmsg = e_positive; - curbuf->b_p_sw = curbuf->b_p_ts; - } - // Number options that need some validation when changed. if (pp == &p_wh) { if (p_wh < 1) { @@ -4091,13 +4086,18 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, if (*pp < -1 || *pp > SB_MAX || (opt_flags == OPT_LOCAL && !curbuf->terminal)) { errmsg = e_invarg; - } + } + } else if (pp == &curbuf->b_p_sw) { + if (curbuf->b_p_sw < 0) { + errmsg = e_positive; + } } else if (pp == &p_ch) { if (p_ch < 1) { errmsg = e_positive; } } + // If validation failed, reset to old value and return. if (errmsg != NULL) { *pp = old_value; return errmsg; @@ -4117,45 +4117,38 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } // Number options that need some action when changed - if (pp == &p_wh || pp == &p_hh) { - /* Change window height NOW */ - if (lastwin != firstwin) { - if (pp == &p_wh && curwin->w_height < p_wh) - win_setheight((int)p_wh); - if (pp == &p_hh && curbuf->b_help && curwin->w_height < p_hh) - win_setheight((int)p_hh); + if (pp == &p_wh) { + if (lastwin != firstwin && curwin->w_height < p_wh) { + win_setheight((int)p_wh); } - } - else if (pp == &p_wmh) { + } else if (pp == &p_hh) { + if (lastwin != firstwin && curbuf->b_help && curwin->w_height < p_hh) { + win_setheight((int)p_hh); + } + } else if (pp == &p_wmh) { win_setminheight(); } else if (pp == &p_wiw) { - /* Change window width NOW */ - if (lastwin != firstwin && curwin->w_width < p_wiw) + if (lastwin != firstwin && curwin->w_width < p_wiw) { win_setwidth((int)p_wiw); - } - else if (pp == &p_wmw) { + } + } else if (pp == &p_wmw) { win_setminheight(); } else if (pp == &p_ls) { - /* (re)set last window status line */ + // (re)set last window status line. last_status(false); - } - /* (re)set tab page line */ - else if (pp == &p_stal) { - shell_new_rows(); /* recompute window positions and heights */ - } - else if (pp == &curwin->w_p_fdl) { + } else if (pp == &p_stal) { + // (re)set tab page line + shell_new_rows(); // recompute window positions and heights + } else if (pp == &curwin->w_p_fdl) { newFoldLevel(); - } - /* 'foldminlines' */ - else if (pp == &curwin->w_p_fml) { + } else if (pp == &curwin->w_p_fml) { foldUpdateAll(curwin); - } - /* 'foldnestmax' */ - else if (pp == &curwin->w_p_fdn) { - if (foldmethodIsSyntax(curwin) || foldmethodIsIndent(curwin)) + } else if (pp == &curwin->w_p_fdn) { + if (foldmethodIsSyntax(curwin) || foldmethodIsIndent(curwin)) { foldUpdateAll(curwin); - // 'shiftwidth' or 'tabstop' + } } else if (pp == &curbuf->b_p_sw || pp == (long *)&curbuf->b_p_ts) { + // 'shiftwidth' or 'tabstop' if (foldmethodIsIndent(curwin)) { foldUpdateAll(curwin); } @@ -4164,51 +4157,40 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, if (pp == &curbuf->b_p_sw || curbuf->b_p_sw == 0) { parse_cino(curbuf); } - } - /* 'maxcombine' */ - else if (pp == &p_mco) { - screenclear(); /* will re-allocate the screen */ + } else if (pp == &p_mco) { + screenclear(); // will re-allocate the screen } else if (pp == &curbuf->b_p_iminsert) { showmode(); - /* Show/unshow value of 'keymap' in status lines. */ + // Show/unshow value of 'keymap' in status lines. status_redraw_curbuf(); - } - /* if 'titlelen' has changed, redraw the title */ - else if (pp == &p_titlelen) { - if (starting != NO_SCREEN && old_value != p_titlelen) + } else if (pp == &p_titlelen) { + // if 'titlelen' has changed, redraw the title + if (starting != NO_SCREEN && old_value != p_titlelen) { need_maketitle = TRUE; - } - /* if p_ch changed value, change the command line height */ - else if (pp == &p_ch) { - - /* Only compute the new window layout when startup has been - * completed. Otherwise the frame sizes may be wrong. */ - if (p_ch != old_value && full_screen - ) + } + } else if (pp == &p_ch) { + // if p_ch changed value, change the command line height + // Only compute the new window layout when startup has been + // completed. Otherwise the frame sizes may be wrong. + if (p_ch != old_value && full_screen) { command_height(); - } - /* when 'updatecount' changes from zero to non-zero, open swap files */ - else if (pp == &p_uc) { - if (p_uc && !old_value) + } + } else if (pp == &p_uc) { + // when 'updatecount' changes from zero to non-zero, open swap files + if (p_uc && !old_value) { ml_open_files(); - } - /* sync undo before 'undolevels' changes */ - else if (pp == &p_ul) { - /* use the old value, otherwise u_sync() may not work properly */ - p_ul = old_value; - u_sync(TRUE); - p_ul = value; - } else if (pp == &curbuf->b_p_ul) { - /* use the old value, otherwise u_sync() may not work properly */ - curbuf->b_p_ul = old_value; + } + } else if (pp == &p_ul || pp == &curbuf->b_p_ul) { + // sync undo before 'undolevels' changes + // use the old value, otherwise u_sync() may not work properly + *pp = old_value; u_sync(TRUE); - curbuf->b_p_ul = value; + *pp = value; } else if (pp == &curbuf->b_p_tw) { FOR_ALL_TAB_WINDOWS(tp, wp) { check_colorcolumn(wp); } - } - else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { + } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { if (curbuf->terminal) { // Force the scrollback to take effect. terminal_resize(curbuf->terminal, UINT16_MAX, UINT16_MAX); -- cgit From 0273f96ef69d2a70a9c2e77b7f5da3a811bca460 Mon Sep 17 00:00:00 2001 From: Jakob Schnitzer Date: Mon, 27 Mar 2017 19:26:41 +0200 Subject: options: move more validation together --- src/nvim/option.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/nvim/option.c b/src/nvim/option.c index f6639d4f6a..65457dec9f 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4095,6 +4095,14 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, if (p_ch < 1) { errmsg = e_positive; } + } else if (pp == &curbuf->b_p_ts) { + if (curbuf->b_p_ts <= 0) { + errmsg = e_positive; + } + } else if (pp == &p_tm) { + if (p_tm < 0) { + errmsg = e_positive; + } } // If validation failed, reset to old value and return. @@ -4246,14 +4254,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } } - if (curbuf->b_p_ts <= 0) { - errmsg = e_positive; - curbuf->b_p_ts = 8; - } - if (p_tm < 0) { - errmsg = e_positive; - p_tm = 0; - } if ((curwin->w_p_scr <= 0 || (curwin->w_p_scr > curwin->w_height && curwin->w_height > 0)) -- cgit From 2b0abdbd9adcdaee5fb4f8d056c6385749c45579 Mon Sep 17 00:00:00 2001 From: Jakob Schnitzer Date: Mon, 27 Mar 2017 19:29:51 +0200 Subject: options: more of the same --- src/nvim/option.c | 69 +++++++++++++++++++++++++++---------------------------- 1 file changed, 34 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/src/nvim/option.c b/src/nvim/option.c index 65457dec9f..24c9b9b72e 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4103,6 +4103,40 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, if (p_tm < 0) { errmsg = e_positive; } + } else if (pp == &p_hi) { + if (p_hi < 0) { + errmsg = e_positive; + } else if (p_hi > 10000) { + errmsg = e_invarg; + } + } else if (pp == &p_re) { + if (p_re < 0 || p_re > 2) { + errmsg = e_invarg; + } + } else if (pp == &p_report) { + if (p_report < 0) { + errmsg = e_positive; + } + } else if (pp == &p_so) { + if (p_so < 0 && full_screen) { + errmsg = e_scroll; + } + } else if (pp == &p_siso) { + if (p_siso < 0 && full_screen) { + errmsg = e_positive; + } + } else if (pp == &p_cwh) { + if (p_cwh < 1) { + errmsg = e_positive; + } + } else if (pp == &p_ut) { + if (p_ut < 0) { + errmsg = e_positive; + } + } else if (pp == &p_ss) { + if (p_ss < 0) { + errmsg = e_positive; + } } // If validation failed, reset to old value and return. @@ -4270,21 +4304,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, else /* curwin->w_p_scr > curwin->w_height */ curwin->w_p_scr = curwin->w_height; } - if (p_hi < 0) { - errmsg = e_positive; - p_hi = 0; - } else if (p_hi > 10000) { - errmsg = e_invarg; - p_hi = 10000; - } - if (p_re < 0 || p_re > 2) { - errmsg = e_invarg; - p_re = 0; - } - if (p_report < 0) { - errmsg = e_positive; - p_report = 1; - } if ((p_sj < -100 || p_sj >= Rows) && full_screen) { if (Rows != old_Rows) /* Rows changed, just adjust p_sj */ p_sj = Rows / 2; @@ -4293,26 +4312,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, p_sj = 1; } } - if (p_so < 0 && full_screen) { - errmsg = e_scroll; - p_so = 0; - } - if (p_siso < 0 && full_screen) { - errmsg = e_positive; - p_siso = 0; - } - if (p_cwh < 1) { - errmsg = e_positive; - p_cwh = 1; - } - if (p_ut < 0) { - errmsg = e_positive; - p_ut = 2000; - } - if (p_ss < 0) { - errmsg = e_positive; - p_ss = 0; - } /* May set global value for local option. */ if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) -- cgit From 2290a7a1b1f8ea3c04365667b751fdfff62b43f5 Mon Sep 17 00:00:00 2001 From: Jakob Schnitzer Date: Mon, 27 Mar 2017 19:37:39 +0200 Subject: options: group num_option validation by type --- src/nvim/option.c | 96 +++++++++++++++++++++++++++---------------------------- 1 file changed, 47 insertions(+), 49 deletions(-) (limited to 'src') diff --git a/src/nvim/option.c b/src/nvim/option.c index 24c9b9b72e..976aadbc9c 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4032,30 +4032,12 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, if (p_wmw > p_wiw) { errmsg = e_winwidth; } - } else if (pp == &curwin->w_p_fdl) { - if (curwin->w_p_fdl < 0) { - errmsg = e_positive; - } - } else if (pp == &curwin->w_p_fdc) { - if (curwin->w_p_fdc < 0) { - errmsg = e_positive; - } else if (curwin->w_p_fdc > 12) { - errmsg = e_invarg; - } } else if (pp == &p_mco) { if (p_mco > MAX_MCO) { errmsg = e_invarg; } else if (p_mco < 0) { errmsg = e_positive; } - } else if (pp == &curbuf->b_p_iminsert) { - if (curbuf->b_p_iminsert < 0 || curbuf->b_p_iminsert > B_IMODE_LAST) { - errmsg = e_invarg; - } - } else if (pp == &curbuf->b_p_imsearch) { - if (curbuf->b_p_imsearch < -1 || curbuf->b_p_imsearch > B_IMODE_LAST) { - errmsg = e_invarg; - } } else if (pp == &p_titlelen) { if (p_titlelen < 0) { errmsg = e_positive; @@ -4064,41 +4046,10 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, if (p_uc < 0) { errmsg = e_positive; } - } else if (pp == &curwin->w_p_cole) { - if (curwin->w_p_cole < 0) { - errmsg = e_positive; - } else if (curwin->w_p_cole > 3) { - errmsg = e_invarg; - } - } else if (pp == &curwin->w_p_nuw) { - if (curwin->w_p_nuw < 1) { - errmsg = e_positive; - } - if (curwin->w_p_nuw > 10) { - errmsg = e_invarg; - } - curwin->w_nrwidth_line_count = 0; - } else if (pp == &curbuf->b_p_tw) { - if (curbuf->b_p_tw < 0) { - errmsg = e_positive; - } - } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { - if (*pp < -1 || *pp > SB_MAX - || (opt_flags == OPT_LOCAL && !curbuf->terminal)) { - errmsg = e_invarg; - } - } else if (pp == &curbuf->b_p_sw) { - if (curbuf->b_p_sw < 0) { - errmsg = e_positive; - } } else if (pp == &p_ch) { if (p_ch < 1) { errmsg = e_positive; } - } else if (pp == &curbuf->b_p_ts) { - if (curbuf->b_p_ts <= 0) { - errmsg = e_positive; - } } else if (pp == &p_tm) { if (p_tm < 0) { errmsg = e_positive; @@ -4137,6 +4088,53 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, if (p_ss < 0) { errmsg = e_positive; } + } else if (pp == &curwin->w_p_fdl) { + if (curwin->w_p_fdl < 0) { + errmsg = e_positive; + } + } else if (pp == &curwin->w_p_fdc) { + if (curwin->w_p_fdc < 0) { + errmsg = e_positive; + } else if (curwin->w_p_fdc > 12) { + errmsg = e_invarg; + } + } else if (pp == &curwin->w_p_cole) { + if (curwin->w_p_cole < 0) { + errmsg = e_positive; + } else if (curwin->w_p_cole > 3) { + errmsg = e_invarg; + } + } else if (pp == &curwin->w_p_nuw) { + if (curwin->w_p_nuw < 1) { + errmsg = e_positive; + } else if (curwin->w_p_nuw > 10) { + errmsg = e_invarg; + } + } else if (pp == &curbuf->b_p_iminsert) { + if (curbuf->b_p_iminsert < 0 || curbuf->b_p_iminsert > B_IMODE_LAST) { + errmsg = e_invarg; + } + } else if (pp == &curbuf->b_p_imsearch) { + if (curbuf->b_p_imsearch < -1 || curbuf->b_p_imsearch > B_IMODE_LAST) { + errmsg = e_invarg; + } + } else if (pp == &curbuf->b_p_tw) { + if (curbuf->b_p_tw < 0) { + errmsg = e_positive; + } + } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { + if (*pp < -1 || *pp > SB_MAX + || (opt_flags == OPT_LOCAL && !curbuf->terminal)) { + errmsg = e_invarg; + } + } else if (pp == &curbuf->b_p_sw) { + if (curbuf->b_p_sw < 0) { + errmsg = e_positive; + } + } else if (pp == &curbuf->b_p_ts) { + if (curbuf->b_p_ts <= 0) { + errmsg = e_positive; + } } // If validation failed, reset to old value and return. -- cgit From 44f039a1c8bf1a3111825f2d3385730e31536d9a Mon Sep 17 00:00:00 2001 From: Jakob Schnitzer Date: Mon, 27 Mar 2017 19:50:04 +0200 Subject: options: fix setglobal for buf-local number options --- src/nvim/option.c | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/nvim/option.c b/src/nvim/option.c index 976aadbc9c..637591d7ca 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4014,22 +4014,19 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } else if (pp == &p_wmh) { if (p_wmh < 0) { errmsg = e_positive; - } - if (p_wmh > p_wh) { + } else if (p_wmh > p_wh) { errmsg = e_winheight; } } else if (pp == &p_wiw) { if (p_wiw < 1) { errmsg = e_positive; - } - if (p_wmw > p_wiw) { + } else if (p_wmw > p_wiw) { errmsg = e_winwidth; } } else if (pp == &p_wmw) { if (p_wmw < 0) { errmsg = e_positive; - } - if (p_wmw > p_wiw) { + } else if (p_wmw > p_wiw) { errmsg = e_winwidth; } } else if (pp == &p_mco) { @@ -4110,16 +4107,16 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } else if (curwin->w_p_nuw > 10) { errmsg = e_invarg; } - } else if (pp == &curbuf->b_p_iminsert) { - if (curbuf->b_p_iminsert < 0 || curbuf->b_p_iminsert > B_IMODE_LAST) { + } else if (pp == &curbuf->b_p_iminsert || pp == &p_iminsert) { + if (*pp < 0 || *pp > B_IMODE_LAST) { errmsg = e_invarg; } - } else if (pp == &curbuf->b_p_imsearch) { - if (curbuf->b_p_imsearch < -1 || curbuf->b_p_imsearch > B_IMODE_LAST) { + } else if (pp == &curbuf->b_p_imsearch || pp == &p_imsearch) { + if (*pp < -1 || *pp > B_IMODE_LAST) { errmsg = e_invarg; } - } else if (pp == &curbuf->b_p_tw) { - if (curbuf->b_p_tw < 0) { + } else if (pp == &curbuf->b_p_tw || pp == &p_tw) { + if (*pp < 0) { errmsg = e_positive; } } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { @@ -4127,12 +4124,12 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, || (opt_flags == OPT_LOCAL && !curbuf->terminal)) { errmsg = e_invarg; } - } else if (pp == &curbuf->b_p_sw) { - if (curbuf->b_p_sw < 0) { + } else if (pp == &curbuf->b_p_sw || pp == &p_sw) { + if (*pp < 0) { errmsg = e_positive; } - } else if (pp == &curbuf->b_p_ts) { - if (curbuf->b_p_ts <= 0) { + } else if (pp == &curbuf->b_p_ts || pp == &p_ts) { + if (*pp <= 0) { errmsg = e_positive; } } @@ -4206,7 +4203,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } else if (pp == &p_titlelen) { // if 'titlelen' has changed, redraw the title if (starting != NO_SCREEN && old_value != p_titlelen) { - need_maketitle = TRUE; + need_maketitle = true; } } else if (pp == &p_ch) { // if p_ch changed value, change the command line height @@ -4224,7 +4221,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, // sync undo before 'undolevels' changes // use the old value, otherwise u_sync() may not work properly *pp = old_value; - u_sync(TRUE); + u_sync(true); *pp = value; } else if (pp == &curbuf->b_p_tw) { FOR_ALL_TAB_WINDOWS(tp, wp) { -- cgit From db095f65636664afb4b09a3920571bf0565c7763 Mon Sep 17 00:00:00 2001 From: Jakob Schnitzer Date: Tue, 28 Mar 2017 16:17:53 +0200 Subject: options: more tests; check first set later; stricter validation --- src/nvim/option.c | 141 +++++++++++++++++++++++++++--------------------------- 1 file changed, 71 insertions(+), 70 deletions(-) (limited to 'src') diff --git a/src/nvim/option.c b/src/nvim/option.c index 637591d7ca..d5bc3c1765 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -7,7 +7,8 @@ // - For a window option, add some code to copy_winopt(). // - For a buffer option, add some code to buf_copy_options(). // - For a buffer string option, add code to check_buf_options(). -// - If it's a numeric option, add any necessary bounds checks to do_set(). +// - If it's a numeric option, add any necessary bounds checks to +// set_num_option(). // - If it's a list of flags, add some code in do_set(), search for WW_ALL. // - When adding an option with expansion (P_EXPAND), but with a different // default for Vi and Vim (no P_VI_DEF), add some code at VIMEXP. @@ -1445,8 +1446,7 @@ do_set ( goto skip; } } else if (*arg == '-' || ascii_isdigit(*arg)) { - // Allow negative (for 'undolevels'), octal and - // hex numbers. + // Allow negative, octal and hex numbers. vim_str2nr(arg, NULL, &i, STR2NR_ALL, &value, NULL, 0); if (arg[i] != NUL && !ascii_iswhite(arg[i])) { errmsg = e_invarg; @@ -3995,151 +3995,158 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, return (char *)e_secure; } - *pp = value; - /* Remember where the option was set. */ - set_option_scriptID_idx(opt_idx, opt_flags, current_SID); + // Many number options assume their value is in the signed int range. + if (value < INT_MIN || value > INT_MAX) { + return e_invarg; + } - // Number options that need some validation when changed. + // Options that need some validation. if (pp == &p_wh) { - if (p_wh < 1) { + if (value < 1) { errmsg = e_positive; - } - if (p_wmh > p_wh) { + } else if (p_wmh > value) { errmsg = e_winheight; } } else if (pp == &p_hh) { - if (p_hh < 0) { + if (value < 0) { errmsg = e_positive; } } else if (pp == &p_wmh) { - if (p_wmh < 0) { + if (value < 0) { errmsg = e_positive; - } else if (p_wmh > p_wh) { + } else if (value > p_wh) { errmsg = e_winheight; } } else if (pp == &p_wiw) { - if (p_wiw < 1) { + if (value < 1) { errmsg = e_positive; - } else if (p_wmw > p_wiw) { + } else if (p_wmw > value) { errmsg = e_winwidth; } } else if (pp == &p_wmw) { - if (p_wmw < 0) { + if (value < 0) { errmsg = e_positive; - } else if (p_wmw > p_wiw) { + } else if (value > p_wiw) { errmsg = e_winwidth; } } else if (pp == &p_mco) { - if (p_mco > MAX_MCO) { + if (value > MAX_MCO) { errmsg = e_invarg; - } else if (p_mco < 0) { + } else if (value < 0) { errmsg = e_positive; } } else if (pp == &p_titlelen) { - if (p_titlelen < 0) { + if (value < 0) { errmsg = e_positive; } } else if (pp == &p_uc) { - if (p_uc < 0) { + if (value < 0) { errmsg = e_positive; } } else if (pp == &p_ch) { - if (p_ch < 1) { + if (value < 1) { errmsg = e_positive; } } else if (pp == &p_tm) { - if (p_tm < 0) { + if (value < 0) { errmsg = e_positive; } } else if (pp == &p_hi) { - if (p_hi < 0) { + if (value < 0) { errmsg = e_positive; - } else if (p_hi > 10000) { + } else if (value > 10000) { errmsg = e_invarg; } } else if (pp == &p_re) { - if (p_re < 0 || p_re > 2) { + if (value < 0 || value > 2) { errmsg = e_invarg; } } else if (pp == &p_report) { - if (p_report < 0) { + if (value < 0) { errmsg = e_positive; } } else if (pp == &p_so) { - if (p_so < 0 && full_screen) { + if (value < 0 && full_screen) { errmsg = e_scroll; } } else if (pp == &p_siso) { - if (p_siso < 0 && full_screen) { + if (value < 0 && full_screen) { errmsg = e_positive; } } else if (pp == &p_cwh) { - if (p_cwh < 1) { + if (value < 1) { errmsg = e_positive; } } else if (pp == &p_ut) { - if (p_ut < 0) { + if (value < 0) { errmsg = e_positive; } } else if (pp == &p_ss) { - if (p_ss < 0) { + if (value < 0) { errmsg = e_positive; } - } else if (pp == &curwin->w_p_fdl) { - if (curwin->w_p_fdl < 0) { + } else if (pp == &curwin->w_p_fdl + || pp == (long *)GLOBAL_WO(&curwin->w_p_fdl)) { + if (value < 0) { errmsg = e_positive; } - } else if (pp == &curwin->w_p_fdc) { - if (curwin->w_p_fdc < 0) { + } else if (pp == &curwin->w_p_fdc + || pp == (long *)GLOBAL_WO(&curwin->w_p_fdc)) { + if (value < 0) { errmsg = e_positive; - } else if (curwin->w_p_fdc > 12) { + } else if (value > 12) { errmsg = e_invarg; } - } else if (pp == &curwin->w_p_cole) { - if (curwin->w_p_cole < 0) { + } else if (pp == &curwin->w_p_cole + || pp == (long *)GLOBAL_WO(&curwin->w_p_cole)) { + if (value < 0) { errmsg = e_positive; - } else if (curwin->w_p_cole > 3) { + } else if (value > 3) { errmsg = e_invarg; } - } else if (pp == &curwin->w_p_nuw) { - if (curwin->w_p_nuw < 1) { + } else if (pp == &curwin->w_p_nuw + || pp == (long *)GLOBAL_WO(&curwin->w_p_nuw)) { + if (value < 1) { errmsg = e_positive; - } else if (curwin->w_p_nuw > 10) { + } else if (value > 10) { errmsg = e_invarg; } } else if (pp == &curbuf->b_p_iminsert || pp == &p_iminsert) { - if (*pp < 0 || *pp > B_IMODE_LAST) { + if (value < 0 || value > B_IMODE_LAST) { errmsg = e_invarg; } } else if (pp == &curbuf->b_p_imsearch || pp == &p_imsearch) { - if (*pp < -1 || *pp > B_IMODE_LAST) { + if (value < -1 || value > B_IMODE_LAST) { errmsg = e_invarg; } - } else if (pp == &curbuf->b_p_tw || pp == &p_tw) { - if (*pp < 0) { - errmsg = e_positive; - } } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { - if (*pp < -1 || *pp > SB_MAX + if (value < -1 || value > SB_MAX || (opt_flags == OPT_LOCAL && !curbuf->terminal)) { errmsg = e_invarg; } } else if (pp == &curbuf->b_p_sw || pp == &p_sw) { - if (*pp < 0) { + if (value < 0) { errmsg = e_positive; } } else if (pp == &curbuf->b_p_ts || pp == &p_ts) { - if (*pp <= 0) { + if (value <= 0) { + errmsg = e_positive; + } + } else if (pp == &curbuf->b_p_tw || pp == &p_tw) { + if (value < 0) { errmsg = e_positive; } } - // If validation failed, reset to old value and return. + // Don't change the value and return early if validation failed. if (errmsg != NULL) { - *pp = old_value; return errmsg; } + *pp = value; + // Remember where the option was set. + set_option_scriptID_idx(opt_idx, opt_flags, current_SID); + // For these options we want to fix some invalid values. if (pp == &p_window) { if (p_window < 1) { @@ -4168,11 +4175,8 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, if (lastwin != firstwin && curwin->w_width < p_wiw) { win_setwidth((int)p_wiw); } - } else if (pp == &p_wmw) { - win_setminheight(); } else if (pp == &p_ls) { - // (re)set last window status line. - last_status(false); + last_status(false); // (re)set last window status line. } else if (pp == &p_stal) { // (re)set tab page line shell_new_rows(); // recompute window positions and heights @@ -4237,9 +4241,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } - /* - * Check the bounds for numeric options here - */ + // Check the (new) bounds for Rows and Columns here. if (Rows < min_rows() && full_screen) { if (errbuf != NULL) { vim_snprintf((char *)errbuf, errbuflen, @@ -4259,19 +4261,17 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, limit_screen_size(); - /* - * If the screen (shell) height has been changed, assume it is the - * physical screenheight. - */ + // If the screen (shell) height has been changed, assume it is the + // physical screenheight. if (old_Rows != Rows || old_Columns != Columns) { - /* Changing the screen size is not allowed while updating the screen. */ + // Changing the screen size is not allowed while updating the screen. if (updating_screen) { *pp = old_value; } else if (full_screen) { screen_resize((int)Columns, (int)Rows); } else { - /* Postpone the resizing; check the size and cmdline position for - * messages. */ + // Postpone the resizing; check the size and cmdline position for + // messages. check_shellsize(); if (cmdline_row > Rows - p_ch && Rows > p_ch) { assert(p_ch >= 0 && Rows - p_ch <= INT_MAX); @@ -4308,9 +4308,10 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } } - /* May set global value for local option. */ - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) + // May set global value for local option. + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { *(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL) = *pp; + } if (pp == &curbuf->b_p_scbk && !curbuf->terminal) { // Normal buffer: reset local 'scrollback' after updating the global value. -- cgit From 8a55f9b1c8bab2cf22e266d7e1eecde85119d75d Mon Sep 17 00:00:00 2001 From: Jakob Schnitzer Date: Fri, 31 Mar 2017 18:30:06 +0200 Subject: update for changes in master; fix 'window'; tests --- src/nvim/option.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/option.c b/src/nvim/option.c index d5bc3c1765..eddfdd6218 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -3997,7 +3997,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, // Many number options assume their value is in the signed int range. if (value < INT_MIN || value > INT_MAX) { - return e_invarg; + return (char *)e_invarg; } // Options that need some validation. @@ -4129,7 +4129,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, errmsg = e_positive; } } else if (pp == &curbuf->b_p_ts || pp == &p_ts) { - if (value <= 0) { + if (value < 1) { errmsg = e_positive; } } else if (pp == &curbuf->b_p_tw || pp == &p_tw) { @@ -4140,7 +4140,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, // Don't change the value and return early if validation failed. if (errmsg != NULL) { - return errmsg; + return (char *)errmsg; } *pp = value; @@ -4150,7 +4150,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, // For these options we want to fix some invalid values. if (pp == &p_window) { if (p_window < 1) { - p_window = 1; + p_window = Rows - 1; } else if (p_window >= Rows) { p_window = Rows - 1; } @@ -4188,7 +4188,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, if (foldmethodIsSyntax(curwin) || foldmethodIsIndent(curwin)) { foldUpdateAll(curwin); } - } else if (pp == &curbuf->b_p_sw || pp == (long *)&curbuf->b_p_ts) { + } else if (pp == &curbuf->b_p_sw || pp == &curbuf->b_p_ts) { // 'shiftwidth' or 'tabstop' if (foldmethodIsIndent(curwin)) { foldUpdateAll(curwin); -- cgit From e479f3b944614f28c42ec597ec473652f3ac9912 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Sep 2017 21:38:43 +0300 Subject: kvec: Add kv_drop() which is to be used like `(void)kv_pop(kvec)` --- src/nvim/lib/kvec.h | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src') diff --git a/src/nvim/lib/kvec.h b/src/nvim/lib/kvec.h index 584282d773..4e5fb0d794 100644 --- a/src/nvim/lib/kvec.h +++ b/src/nvim/lib/kvec.h @@ -64,6 +64,14 @@ #define kv_max(v) ((v).capacity) #define kv_last(v) kv_A(v, kv_size(v) - 1) +/// Drop last n items from kvec without resizing +/// +/// Previously spelled as `(void)kv_pop(v)`, repeated n times. +/// +/// @param[out] v Kvec to drop items from. +/// @param[in] n Number of elements to drop. +#define kv_drop(v, n) ((v).size -= (n)) + #define kv_resize(v, s) \ ((v).capacity = (s), \ (v).items = xrealloc((v).items, sizeof((v).items[0]) * (v).capacity)) -- cgit From ad58e50b45ddb73f0582590b9e96da49f34174d0 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 8 Oct 2017 22:09:12 +0300 Subject: kvec: Add kv_Z which is like kv_A, but zero is the last value --- src/nvim/lib/kvec.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/lib/kvec.h b/src/nvim/lib/kvec.h index 4e5fb0d794..ee1b890cb9 100644 --- a/src/nvim/lib/kvec.h +++ b/src/nvim/lib/kvec.h @@ -62,7 +62,8 @@ #define kv_pop(v) ((v).items[--(v).size]) #define kv_size(v) ((v).size) #define kv_max(v) ((v).capacity) -#define kv_last(v) kv_A(v, kv_size(v) - 1) +#define kv_Z(v, i) kv_A(v, kv_size(v) - (i) - 1) +#define kv_last(v) kv_Z(v, 0) /// Drop last n items from kvec without resizing /// -- cgit From 0300c4d10991fb6ce218d45c4fe6d71a73f07d62 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 20 Aug 2017 18:40:22 +0300 Subject: viml/expressions: Add lexer with some basic tests --- src/nvim/CMakeLists.txt | 2 + src/nvim/viml/parser/expressions.c | 367 +++++++++++++++++++++++++++++++++++++ src/nvim/viml/parser/expressions.h | 118 ++++++++++++ src/nvim/viml/parser/parser.h | 129 +++++++++++++ 4 files changed, 616 insertions(+) create mode 100644 src/nvim/viml/parser/expressions.c create mode 100644 src/nvim/viml/parser/expressions.h create mode 100644 src/nvim/viml/parser/parser.h (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 688912eda6..77e4721853 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -81,6 +81,8 @@ foreach(subdir event eval lua + viml + viml/parser ) if(${subdir} MATCHES "tui" AND NOT FEAT_TUI) continue() diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c new file mode 100644 index 0000000000..0164de3a14 --- /dev/null +++ b/src/nvim/viml/parser/expressions.c @@ -0,0 +1,367 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +/// VimL expression parser + +#include +#include +#include +#include + +#include "nvim/vim.h" +#include "nvim/memory.h" +#include "nvim/types.h" +#include "nvim/charset.h" +#include "nvim/ascii.h" + +#include "nvim/viml/parser/expressions.h" +#include "nvim/viml/parser/parser.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "viml/parser/expressions.c.generated.h" +#endif + +/// Character used as a separator in autoload function/variable names. +#define AUTOLOAD_CHAR '#' + +/// Get next token for the VimL expression input +LexExprToken viml_pexpr_next_token(ParserState *const pstate) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + LexExprToken ret = { + .type = kExprLexInvalid, + .start = pstate->pos, + }; + ParserLine pline; + if (!viml_parser_get_remaining_line(pstate, &pline)) { + ret.type = kExprLexEOC; + return ret; + } + if (pline.size <= 0) { + ret.len = 0; + ret.type = kExprLexEOC; + goto viml_pexpr_next_token_adv_return; + } + ret.len = 1; + const uint8_t schar = (uint8_t)pline.data[0]; +#define GET_CCS(ret, pline) \ + do { \ + if (ret.len < pline.size \ + && strchr("?#", pline.data[ret.len]) != NULL) { \ + ret.data.cmp.ccs = \ + (CaseCompareStrategy)pline.data[ret.len]; \ + ret.len++; \ + } else { \ + ret.data.cmp.ccs = kCCStrategyUseOption; \ + } \ + } while (0) + switch (schar) { + // Paired brackets. +#define BRACKET(typ, opning, clsing) \ + case opning: \ + case clsing: { \ + ret.type = typ; \ + ret.data.brc.closing = (schar == clsing); \ + break; \ + } + BRACKET(kExprLexParenthesis, '(', ')') + BRACKET(kExprLexBracket, '[', ']') + BRACKET(kExprLexFigureBrace, '{', '}') +#undef BRACKET + + // Single character tokens without data. +#define CHAR(typ, ch) \ + case ch: { \ + ret.type = typ; \ + break; \ + } + CHAR(kExprLexQuestion, '?') + CHAR(kExprLexColon, ':') + CHAR(kExprLexDot, '.') + CHAR(kExprLexPlus, '+') + CHAR(kExprLexComma, ',') +#undef CHAR + + // Multiplication/division/modulo. +#define MUL(mul_type, ch) \ + case ch: { \ + ret.type = kExprLexMultiplication; \ + ret.data.mul.type = mul_type; \ + break; \ + } + MUL(kExprLexMulMul, '*') + MUL(kExprLexMulDiv, '/') + MUL(kExprLexMulMod, '%') +#undef MUL + +#define CHARREG(typ, cond) \ + do { \ + ret.type = typ; \ + for (; (ret.len < pline.size \ + && cond(pline.data[ret.len])) \ + ; ret.len++) { \ + } \ + } while (0) + + // Whitespace. + case ' ': + case TAB: { + CHARREG(kExprLexSpacing, ascii_iswhite); + break; + } + + // Control character, except for NUL, NL and TAB. + case Ctrl_A: case Ctrl_B: case Ctrl_C: case Ctrl_D: case Ctrl_E: + case Ctrl_F: case Ctrl_G: case Ctrl_H: + + case Ctrl_K: case Ctrl_L: case Ctrl_M: case Ctrl_N: case Ctrl_O: + case Ctrl_P: case Ctrl_Q: case Ctrl_R: case Ctrl_S: case Ctrl_T: + case Ctrl_U: case Ctrl_V: case Ctrl_W: case Ctrl_X: case Ctrl_Y: + case Ctrl_Z: { +#define ISCTRL(schar) (schar < ' ') + CHARREG(kExprLexInvalid, ISCTRL); + ret.data.err.type = kExprLexSpacing; + ret.data.err.msg = + _("E15: Invalid control character present in input: %.*s"); + break; +#undef ISCTRL + } + + // Number. + // Note: determining whether dot is (not) a part of a float needs more + // context, so lexer does not do this. + // FIXME: Resolve ambiguity by additional argument. + case '0': case '1': case '2': case '3': case '4': case '5': case '6': + case '7': case '8': case '9': { + CHARREG(kExprLexNumber, ascii_isdigit); + break; + } + + // Environment variable. + case '$': { + CHARREG(kExprLexEnv, vim_isIDc); + break; + } + + // Normal variable/function name. + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': + case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': + case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': + case 'v': case 'w': case 'x': case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': + case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': + case 'V': case 'W': case 'X': case 'Y': case 'Z': + case '_': { +#define ISWORD_OR_AUTOLOAD(x) \ + (ASCII_ISALNUM(x) || (x) == AUTOLOAD_CHAR || (x) == '_') +#define ISWORD(x) \ + (ASCII_ISALNUM(x) || (x) == '_') + ret.data.var.scope = 0; + ret.data.var.autoload = false; + CHARREG(kExprLexPlainIdentifier, ISWORD); + // "is" and "isnot" operators. + if ((ret.len == 2 && memcmp(pline.data, "is", 2) == 0) + || (ret.len == 5 && memcmp(pline.data, "isnot", 5) == 0)) { + ret.type = kExprLexComparison; + ret.data.cmp.type = kExprLexCmpIdentical; + ret.data.cmp.inv = (ret.len == 5); + GET_CCS(ret, pline); + // Scope: `s:`, etc. + } else if (ret.len == 1 + && pline.size > 1 + && strchr("sgvbwtla", schar) != NULL + && pline.data[ret.len] == ':') { + ret.len++; + ret.data.var.scope = schar; + CHARREG(kExprLexPlainIdentifier, ISWORD_OR_AUTOLOAD); + ret.data.var.autoload = ( + memchr(pline.data + 2, AUTOLOAD_CHAR, ret.len - 2) + != NULL); + // Previous CHARREG stopped at autoload character in order to make it + // possible to detect `is#`. Continue now with autoload characters + // included. + // + // Warning: there is ambiguity for the lexer: `is#Foo(1)` is a call of + // function `is#Foo()`, `1is#Foo(1)` is a comparison `1 is# Foo(1)`. This + // needs to be resolved on the higher level where context is available. + } else if (pline.size > ret.len + && pline.data[ret.len] == AUTOLOAD_CHAR) { + ret.data.var.autoload = true; + CHARREG(kExprLexPlainIdentifier, ISWORD_OR_AUTOLOAD); + } + break; +#undef ISWORD_OR_AUTOLOAD +#undef ISWORD + } +#undef CHARREG + + // Option. + case '&': { +#define OPTNAMEMISS(ret) \ + do { \ + ret.type = kExprLexInvalid; \ + ret.data.err.type = kExprLexOption; \ + ret.data.err.msg = _("E112: Option name missing: %.*s"); \ + } while (0) + if (pline.size > 1 && pline.data[1] == '&') { + ret.type = kExprLexAnd; + ret.len++; + break; + } + if (pline.size == 1 || !ASCII_ISALPHA(pline.data[1])) { + OPTNAMEMISS(ret); + break; + } + ret.type = kExprLexOption; + if (pline.size > 2 + && pline.data[2] == ':' + && strchr("gl", pline.data[1]) != NULL) { + ret.len += 2; + ret.data.opt.scope = (pline.data[1] == 'g' + ? kExprLexOptGlobal + : kExprLexOptLocal); + ret.data.opt.name = pline.data + 3; + } else { + ret.data.opt.scope = kExprLexOptUnspecified; + ret.data.opt.name = pline.data + 1; + } + const char *p = ret.data.opt.name; + const char *const e = pline.data + pline.size; + if (e - p >= 4 && p[0] == 't' && p[1] == '_') { + ret.data.opt.len = 4; + ret.len += 4; + } else { + for (; p < e && ASCII_ISALPHA(*p); p++) { + } + ret.data.opt.len = (size_t)(p - ret.data.opt.name); + if (ret.data.opt.len == 0) { + OPTNAMEMISS(ret); + } else { + ret.len += ret.data.opt.len; + } + } + break; +#undef OPTNAMEMISS + } + + // Register. + case '@': { + ret.type = kExprLexRegister; + if (pline.size > 1) { + ret.len++; + ret.data.reg.name = (uint8_t)pline.data[1]; + } else { + ret.data.reg.name = -1; + } + break; + } + + // Single quoted string. + case '\'': { + ret.type = kExprLexSingleQuotedString; + ret.data.str.closed = false; + for (; ret.len < pline.size && !ret.data.str.closed; ret.len++) { + if (pline.data[ret.len] == '\'') { + if (ret.len + 1 < pline.size && pline.data[ret.len + 1] == '\'') { + ret.len++; + } else { + ret.data.str.closed = true; + } + } + } + break; + } + + // Double quoted string. + case '"': { + ret.type = kExprLexDoubleQuotedString; + ret.data.str.closed = false; + for (; ret.len < pline.size && !ret.data.str.closed; ret.len++) { + if (pline.data[ret.len] == '\\') { + if (ret.len + 1 < pline.size) { + ret.len++; + } + } else if (pline.data[ret.len] == '"') { + ret.data.str.closed = true; + } + } + break; + } + + // Unary not, (un)equality and regex (not) match comparison operators. + case '!': + case '=': { + if (pline.size == 1) { +viml_pexpr_next_token_invalid_comparison: + ret.type = (schar == '!' ? kExprLexNot : kExprLexInvalid); + if (ret.type == kExprLexInvalid) { + ret.data.err.msg = _("E15: Expected == or =~: %.*s"); + ret.data.err.type = kExprLexComparison; + } + break; + } + ret.type = kExprLexComparison; + ret.data.cmp.inv = (schar == '!'); + if (pline.data[1] == '=') { + ret.data.cmp.type = kExprLexCmpEqual; + ret.len++; + } else if (pline.data[1] == '~') { + ret.data.cmp.type = kExprLexCmpMatches; + ret.len++; + } else { + goto viml_pexpr_next_token_invalid_comparison; + } + GET_CCS(ret, pline); + break; + } + + // Less/greater [or equal to] comparison operators. + case '>': + case '<': { + ret.type = kExprLexComparison; + const bool haseqsign = (pline.size > 1 && pline.data[1] == '='); + if (haseqsign) { + ret.len++; + } + GET_CCS(ret, pline); + ret.data.cmp.inv = (schar == '<'); + ret.data.cmp.type = ((ret.data.cmp.inv ^ haseqsign) + ? kExprLexCmpGreaterOrEqual + : kExprLexCmpGreater); + break; + } + + // Minus sign or arrow from lambdas. + case '-': { + if (pline.size > 1 && pline.data[1] == '>') { + ret.len++; + ret.type = kExprLexArrow; + } else { + ret.type = kExprLexMinus; + } + break; + } + + // Expression end because Ex command ended. + case NUL: + case NL: { + ret.type = kExprLexEOC; + break; + } + + // Everything else is not valid. + default: { + ret.len = (size_t)utfc_ptr2len_len((const char_u *)pline.data, + (int)pline.size); + ret.type = kExprLexInvalid; + ret.data.err.type = kExprLexPlainIdentifier; + ret.data.err.msg = _("E15: Unidentified character: %.*s"); + break; + } + } +#undef GET_CCS +viml_pexpr_next_token_adv_return: + viml_parser_advance(pstate, ret.len); + return ret; +} diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h new file mode 100644 index 0000000000..52354760a5 --- /dev/null +++ b/src/nvim/viml/parser/expressions.h @@ -0,0 +1,118 @@ +#ifndef NVIM_VIML_PARSER_EXPRESSIONS_H +#define NVIM_VIML_PARSER_EXPRESSIONS_H + +#include +#include + +#include "nvim/types.h" +#include "nvim/viml/parser/parser.h" + +// Defines whether to ignore case: +// == kCCStrategyUseOption +// ==# kCCStrategyMatchCase +// ==? kCCStrategyIgnoreCase +typedef enum { + kCCStrategyUseOption = 0, // 0 for xcalloc + kCCStrategyMatchCase = '#', + kCCStrategyIgnoreCase = '?', +} CaseCompareStrategy; + +/// Lexer token type +typedef enum { + kExprLexInvalid = 0, ///< Invalid token, indicaten an error. + kExprLexMissing, ///< Missing token, for use in parser. + kExprLexSpacing, ///< Spaces, tabs, newlines, etc. + kExprLexEOC, ///< End of command character: NL, |, just end of stream. + + kExprLexQuestion, ///< Question mark, for use in ternary. + kExprLexColon, ///< Colon, for use in ternary. + kExprLexOr, ///< Logical or operator. + kExprLexAnd, ///< Logical and operator. + kExprLexComparison, ///< One of the comparison operators. + kExprLexPlus, ///< Plus sign. + kExprLexMinus, ///< Minus sign. + kExprLexDot, ///< Dot: either concat or subscript, also part of the float. + kExprLexMultiplication, ///< Multiplication, division or modulo operator. + + kExprLexNot, ///< Not: !. + + kExprLexNumber, ///< Integer number literal, or part of a float. + kExprLexSingleQuotedString, ///< Single quoted string literal. + kExprLexDoubleQuotedString, ///< Double quoted string literal. + kExprLexOption, ///< &optionname option value. + kExprLexRegister, ///< @r register value. + kExprLexEnv, ///< Environment $variable value. + kExprLexPlainIdentifier, ///< Identifier without scope: `abc`, `foo#bar`. + + kExprLexBracket, ///< Bracket, either opening or closing. + kExprLexFigureBrace, ///< Figure brace, either opening or closing. + kExprLexParenthesis, ///< Parenthesis, either opening or closing. + kExprLexComma, ///< Comma. + kExprLexArrow, ///< Arrow, like from lambda expressions. +} LexExprTokenType; + +/// Lexer token +typedef struct { + ParserPosition start; + size_t len; + LexExprTokenType type; + union { + struct { + enum { + kExprLexCmpEqual, ///< Equality, unequality. + kExprLexCmpMatches, ///< Matches regex, not matches regex. + kExprLexCmpGreater, ///< `>` or `<=` + kExprLexCmpGreaterOrEqual, ///< `>=` or `<`. + kExprLexCmpIdentical, ///< `is` or `isnot` + } type; ///< Comparison type. + CaseCompareStrategy ccs; ///< Case comparison strategy. + bool inv; ///< True if comparison is to be inverted. + } cmp; ///< For kExprLexComparison. + + struct { + enum { + kExprLexMulMul, ///< Real multiplication. + kExprLexMulDiv, ///< Division. + kExprLexMulMod, ///< Modulo. + } type; ///< Multiplication type. + } mul; ///< For kExprLexMultiplication. + + struct { + bool closing; ///< True if bracket/etc is a closing one. + } brc; ///< For brackets/braces/parenthesis. + + struct { + int name; ///< Register name, may be -1 if name not present. + } reg; ///< For kExprLexRegister. + + struct { + bool closed; ///< True if quote was closed. + } str; ///< For kExprLexSingleQuotedString and kExprLexDoubleQuotedString. + + struct { + const char *name; ///< Option name start. + size_t len; ///< Option name length. + enum { + kExprLexOptUnspecified = 0, + kExprLexOptGlobal = 1, + kExprLexOptLocal = 2, + } scope; ///< Option scope: &l:, &g: or not specified. + } opt; ///< Option properties. + + struct { + int scope; ///< Scope character or 0 if not present. + bool autoload; ///< Has autoload characters. + } var; ///< For kExprLexPlainIdentifier + + struct { + LexExprTokenType type; ///< Suggested type for parsing incorrect code. + const char *msg; ///< Error message. + } err; ///< For kExprLexInvalid + } data; ///< Additional data, if needed. +} LexExprToken; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "viml/parser/expressions.h.generated.h" +#endif + +#endif // NVIM_VIML_PARSER_EXPRESSIONS_H diff --git a/src/nvim/viml/parser/parser.h b/src/nvim/viml/parser/parser.h new file mode 100644 index 0000000000..ec582294e1 --- /dev/null +++ b/src/nvim/viml/parser/parser.h @@ -0,0 +1,129 @@ +#ifndef NVIM_VIML_PARSER_PARSER_H +#define NVIM_VIML_PARSER_PARSER_H + +#include +#include +#include + +#include "nvim/lib/kvec.h" +#include "nvim/func_attr.h" + +/// One parsed line +typedef struct { + const char *data; ///< Parsed line pointer + size_t size; ///< Parsed line size +} ParserLine; + +/// Line getter type for parser +/// +/// Line getter must return {NULL, 0} for EOF. +typedef void (*ParserLineGetter)(void *cookie, ParserLine *ret_pline); + +/// Parser position in the input +typedef struct { + size_t line; ///< Line index in ParserInputReader.lines. + size_t col; ///< Byte index in the line. +} ParserPosition; + +/// Parser state item. +typedef struct { + enum { + kPTopStateParsingCommand = 0, + kPTopStateParsingExpression, + } type; + union { + struct { + enum { + kExprUnknown = 0, + } type; + } expr; + } data; +} ParserStateItem; + +/// Structure defining input reader +typedef struct { + /// Function used to get next line. + ParserLineGetter get_line; + /// Data for get_line function. + void *cookie; + /// All lines obtained by get_line. + kvec_withinit_t(ParserLine, 4) lines; +} ParserInputReader; + +/// Highlighted region definition +/// +/// Note: one chunk may highlight only one line. +typedef struct { + ParserPosition start; ///< Start of the highlight: line and column. + size_t end_col; ///< End column, points to the start of the next character. + const char *group; ///< Highlight group. +} ParserHighlightChunk; + +/// Highlighting defined by a parser +typedef kvec_withinit_t(ParserHighlightChunk, 16) ParserHighlight; + +/// Structure defining parser state +typedef struct { + /// Line reader. + ParserInputReader reader; + /// Position up to which input was parsed. + ParserPosition pos; + /// Parser state stack. + kvec_withinit_t(ParserStateItem, 16) stack; + /// Highlighting support. + ParserHighlight *colors; + /// True if line continuation can be used. + bool can_continuate; +} ParserState; + +static inline bool viml_parser_get_remaining_line(ParserState *const pstate, + ParserLine *const ret_pline) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; + +/// Get currently parsed line, shifted to pstate->pos.col +/// +/// @param pstate Parser state to operate on. +/// +/// @return True if there is a line, false in case of EOF. +static inline bool viml_parser_get_remaining_line(ParserState *const pstate, + ParserLine *const ret_pline) +{ + const size_t num_lines = kv_size(pstate->reader.lines); + if (pstate->pos.line == num_lines) { + pstate->reader.get_line(pstate->reader.cookie, ret_pline); + kvi_push(pstate->reader.lines, *ret_pline); + } else { + *ret_pline = kv_last(pstate->reader.lines); + } + assert(pstate->pos.line == kv_size(pstate->reader.lines) - 1); + if (ret_pline->data != NULL) { + ret_pline->data += pstate->pos.col; + ret_pline->size -= pstate->pos.col; + } + return ret_pline->data != NULL; +} + +static inline void viml_parser_advance(ParserState *const pstate, + const size_t len) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL; + +/// Advance position by a given number of bytes +/// +/// At maximum advances to the next line. +/// +/// @param pstate Parser state to advance. +/// @param[in] len Number of bytes to advance. +static inline void viml_parser_advance(ParserState *const pstate, + const size_t len) +{ + assert(pstate->pos.line == kv_size(pstate->reader.lines) - 1); + const ParserLine pline = kv_last(pstate->reader.lines); + if (pstate->pos.col + len >= pline.size) { + pstate->pos.line++; + pstate->pos.col = 0; + } else { + pstate->pos.col += len; + } +} + +#endif // NVIM_VIML_PARSER_PARSER_H -- cgit From 2d8b9937deae3731143f4ea44e5c41715fe1363a Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 20 Aug 2017 20:40:59 +0300 Subject: viml/parser: Handle encoding conversions --- src/nvim/viml/parser/expressions.c | 13 ++++++++++--- src/nvim/viml/parser/parser.h | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 0164de3a14..c29fac9cb4 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -25,8 +25,13 @@ #define AUTOLOAD_CHAR '#' /// Get next token for the VimL expression input -LexExprToken viml_pexpr_next_token(ParserState *const pstate) - FUNC_ATTR_WARN_UNUSED_RESULT +/// +/// @param pstate Parser state. +/// @param[in] peek If true, do not advance pstate cursor. +/// +/// @return Next token. +LexExprToken viml_pexpr_next_token(ParserState *const pstate, const bool peek) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { LexExprToken ret = { .type = kExprLexInvalid, @@ -362,6 +367,8 @@ viml_pexpr_next_token_invalid_comparison: } #undef GET_CCS viml_pexpr_next_token_adv_return: - viml_parser_advance(pstate, ret.len); + if (!peek) { + viml_parser_advance(pstate, ret.len); + } return ret; } diff --git a/src/nvim/viml/parser/parser.h b/src/nvim/viml/parser/parser.h index ec582294e1..231e43b4c7 100644 --- a/src/nvim/viml/parser/parser.h +++ b/src/nvim/viml/parser/parser.h @@ -7,11 +7,13 @@ #include "nvim/lib/kvec.h" #include "nvim/func_attr.h" +#include "nvim/mbyte.h" /// One parsed line typedef struct { const char *data; ///< Parsed line pointer size_t size; ///< Parsed line size + bool allocated; ///< True if line may be freed. } ParserLine; /// Line getter type for parser @@ -48,6 +50,8 @@ typedef struct { void *cookie; /// All lines obtained by get_line. kvec_withinit_t(ParserLine, 4) lines; + /// Conversion, for :scriptencoding. + vimconv_T conv; } ParserInputReader; /// Highlighted region definition @@ -76,6 +80,33 @@ typedef struct { bool can_continuate; } ParserState; +static inline void viml_preader_get_line(ParserInputReader *const preader, + ParserLine *const ret_pline) + REAL_FATTR_NONNULL_ALL; + +/// Get one line from ParserInputReader +static inline void viml_preader_get_line(ParserInputReader *const preader, + ParserLine *const ret_pline) +{ + ParserLine pline; + preader->get_line(preader->cookie, &pline); + if (preader->conv.vc_type != CONV_NONE && pline.size) { + ParserLine cpline = { + .allocated = true, + .size = pline.size, + }; + cpline.data = (char *)string_convert(&preader->conv, + (char_u *)pline.data, + &cpline.size); + if (pline.allocated) { + xfree((void *)pline.data); + } + pline = cpline; + } + kvi_push(preader->lines, pline); + *ret_pline = pline; +} + static inline bool viml_parser_get_remaining_line(ParserState *const pstate, ParserLine *const ret_pline) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; @@ -90,8 +121,7 @@ static inline bool viml_parser_get_remaining_line(ParserState *const pstate, { const size_t num_lines = kv_size(pstate->reader.lines); if (pstate->pos.line == num_lines) { - pstate->reader.get_line(pstate->reader.cookie, ret_pline); - kvi_push(pstate->reader.lines, *ret_pline); + viml_preader_get_line(&pstate->reader, ret_pline); } else { *ret_pline = kv_last(pstate->reader.lines); } -- cgit From 1265da028882d9877a5ebbd3f3f52cb4b52a4b94 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 3 Sep 2017 19:53:41 +0300 Subject: viml/parser: Add helper functions for highlighting --- src/nvim/viml/parser/parser.h | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'src') diff --git a/src/nvim/viml/parser/parser.h b/src/nvim/viml/parser/parser.h index 231e43b4c7..a17edac403 100644 --- a/src/nvim/viml/parser/parser.h +++ b/src/nvim/viml/parser/parser.h @@ -156,4 +156,33 @@ static inline void viml_parser_advance(ParserState *const pstate, } } +static inline void viml_parser_highlight(ParserState *const pstate, + const ParserPosition start, + const size_t end_col, + const char *const group) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL; + +/// Record highlighting of some region of text +/// +/// @param pstate Parser state to work with. +/// @param[in] start Start position of the highlight. +/// @param[in] len Highlighting chunk length. +/// @param[in] group Highlight group. +static inline void viml_parser_highlight(ParserState *const pstate, + const ParserPosition start, + const size_t len, + const char *const group) +{ + if (pstate->colors == NULL) { + return; + } + // TODO(ZyX-I): May do some assert() sanitizing here. + // TODO(ZyX-I): May join chunks. + kvi_push(*pstate->colors, ((ParserHighlightChunk) { + .start = start, + .end_col = start.col + len, + .group = group, + })); +} + #endif // NVIM_VIML_PARSER_PARSER_H -- cgit From 430e516d3ac1235c1ee3009a8a36089bf278440e Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 3 Sep 2017 21:58:16 +0300 Subject: viml/parser/expressions: Start creating expressions parser Currently supported nodes: - Register as it is one of the simplest value nodes (even numbers are not that simple with that dot handling). - Plus, both unary and binary. - Parenthesis, both nesting and calling. Note regarding unit tests: it stores data for AST in highlighting in strings in place of tables because luassert fails to do a good job at representing big tables. Squashing a bunch of data into a single string simply yields more readable result. --- src/nvim/viml/parser/expressions.c | 625 +++++++++++++++++++++++++++++++++++++ src/nvim/viml/parser/expressions.h | 74 +++++ 2 files changed, 699 insertions(+) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index c29fac9cb4..b54f2eb237 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -13,10 +13,18 @@ #include "nvim/types.h" #include "nvim/charset.h" #include "nvim/ascii.h" +#include "nvim/lib/kvec.h" #include "nvim/viml/parser/expressions.h" #include "nvim/viml/parser/parser.h" +typedef kvec_withinit_t(ExprASTNode **, 16) ExprASTStack; + +typedef enum { + kELvlOperator, ///< Operators: function call, subscripts, binary operators, … + kELvlValue, ///< Actual value: literals, variables, nested expressions. +} ExprASTLevel; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "viml/parser/expressions.c.generated.h" #endif @@ -144,6 +152,7 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const bool peek) // Environment variable. case '$': { + // FIXME: Parser function can’t be thread-safe with vim_isIDc. CHARREG(kExprLexEnv, vim_isIDc); break; } @@ -183,6 +192,7 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const bool peek) ret.data.var.autoload = ( memchr(pline.data + 2, AUTOLOAD_CHAR, ret.len - 2) != NULL); + // FIXME: Resolve ambiguity with an argument to the lexer function. // Previous CHARREG stopped at autoload character in order to make it // possible to detect `is#`. Continue now with autoload characters // included. @@ -372,3 +382,618 @@ viml_pexpr_next_token_adv_return: } return ret; } + +// start = s ternary_expr s EOC +// ternary_expr = binop_expr +// ( s Question s ternary_expr s Colon s ternary_expr s )? +// binop_expr = unaryop_expr ( binop unaryop_expr )? +// unaryop_expr = ( unaryop )? subscript_expr +// subscript_expr = subscript_expr subscript +// | value_expr +// subscript = Bracket('[') s ternary_expr s Bracket(']') +// | s Parenthesis('(') call_args Parenthesis(')') +// | Dot ( PlainIdentifier | Number )+ +// # Note: `s` before Parenthesis('(') is only valid if preceding subscript_expr +// # is PlainIdentifier +// value_expr = ( float | Number +// | DoubleQuotedString | SingleQuotedString +// | paren_expr +// | list_literal +// | lambda_literal +// | dict_literal +// | Environment +// | Option +// | Register +// | var ) +// float = Number Dot Number ( PlainIdentifier('e') ( Plus | Minus )? Number )? +// # Note: `1.2.3` is concat and not float. `"abc".2.3` is also concat without +// # floats. +// paren_expr = Parenthesis('(') s ternary_expr s Parenthesis(')') +// list_literal = Bracket('[') s +// ( ternary_expr s Comma s )* +// ternary_expr? s +// Bracket(']') +// dict_literal = FigureBrace('{') s +// ( ternary_expr s Colon s ternary_expr s Comma s )* +// ( ternary_expr s Colon s ternary_expr s )? +// FigureBrace('}') +// lambda_literal = FigureBrace('{') s +// ( PlainIdentifier s Comma s )* +// PlainIdentifier s +// Arrow s +// ternary_expr s +// FigureBrace('}') +// var = varchunk+ +// varchunk = PlainIdentifier +// | Comparison("is" | "is#" | "isnot" | "isnot#") +// | FigureBrace('{') s ternary_expr s FigureBrace('}') +// call_args = ( s ternary_expr s Comma s )* s ternary_expr? s +// binop = s ( Plus | Minus | Dot +// | Comparison +// | Multiplication +// | Or +// | And ) s +// unaryop = s ( Not | Plus | Minus ) s +// s = Spacing? +// +// Binary operator precedence and associativity: +// +// Operator | Precedence | Associativity +// ---------+------------+----------------- +// || | 2 | left +// && | 3 | left +// cmp* | 4 | not associative +// + - . | 5 | left +// * / % | 6 | left +// +// * comparison operators: +// +// == ==# ==? != !=# !=? +// =~ =~# =~? !~ !~# !~? +// > ># >? <= <=# <=? +// < <# = >=# >=? +// is is# is? isnot isnot# isnot? +// +// Used highlighting groups and assumed linkage: +// +// NVimInvalid -> Error +// NVimInvalidValue -> NVimInvalid +// NVimInvalidOperator -> NVimInvalid +// NVimInvalidDelimiter -> NVimInvalid +// +// NVimOperator -> Operator +// NVimUnaryOperator -> NVimOperator +// NVimBinaryOperator -> NVimOperator +// NVimComparisonOperator -> NVimOperator +// NVimTernaryOperator -> NVimOperator +// +// NVimParenthesis -> Delimiter +// +// NVimInvalidSpacing -> NVimInvalid +// NVimInvalidTernaryOperator -> NVimInvalidOperator +// NVimInvalidRegister -> NVimInvalidValue +// NVimInvalidClosingBracket -> NVimInvalidDelimiter +// NVimInvalidSpacing -> NVimInvalid +// +// NVimUnaryPlus -> NVimUnaryOperator +// NVimBinaryPlus -> NVimBinaryOperator +// NVimRegister -> SpecialChar +// NVimNestingParenthesis -> NVimParenthesis +// NVimCallingParenthesis -> NVimParenthesis + +/// Allocate a new node and set some of the values +/// +/// @param[in] type Node type to allocate. +/// @param[in] level Node level to allocate +static inline ExprASTNode *viml_pexpr_new_node(const ExprASTNodeType type) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC +{ + ExprASTNode *ret = xmalloc(sizeof(*ret)); + ret->type = type; + ret->children = NULL; + ret->next = NULL; + return ret; +} + +typedef enum { + kEOpLvlInvalid = 0, + kEOpLvlParens, + kEOpLvlTernary, + kEOpLvlOr, + kEOpLvlAnd, + kEOpLvlComparison, + kEOpLvlAddition, ///< Addition, subtraction and concatenation. + kEOpLvlMultiplication, ///< Multiplication, division and modulo. + kEOpLvlUnary, ///< Unary operations: not, minus, plus. + kEOpLvlSubscript, ///< Subscripts. + kEOpLvlValue, ///< Values: literals, variables, nested expressions, … +} ExprOpLvl; + +typedef enum { + kEOpAssNo= 'n', ///< Not associative / not applicable. + kEOpAssLeft = 'l', ///< Left associativity. + kEOpAssRight = 'r', ///< Right associativity. +} ExprOpAssociativity; + +static const ExprOpLvl node_type_to_op_lvl[] = { + [kExprNodeMissing] = kEOpLvlInvalid, + [kExprNodeOpMissing] = kEOpLvlMultiplication, + + [kExprNodeNested] = kEOpLvlParens, + [kExprNodeComplexIdentifier] = kEOpLvlParens, + + [kExprNodeTernary] = kEOpLvlTernary, + + [kExprNodeBinaryPlus] = kEOpLvlAddition, + + [kExprNodeUnaryPlus] = kEOpLvlUnary, + + [kExprNodeSubscript] = kEOpLvlSubscript, + [kExprNodeCall] = kEOpLvlSubscript, + + [kExprNodeRegister] = kEOpLvlValue, + [kExprNodeListLiteral] = kEOpLvlValue, + [kExprNodePlainIdentifier] = kEOpLvlValue, +}; + +static const ExprOpAssociativity node_type_to_op_ass[] = { + [kExprNodeMissing] = kEOpAssNo, + [kExprNodeOpMissing] = kEOpAssNo, + + [kExprNodeNested] = kEOpAssNo, + [kExprNodeComplexIdentifier] = kEOpAssLeft, + + [kExprNodeTernary] = kEOpAssNo, + + [kExprNodeBinaryPlus] = kEOpAssLeft, + + [kExprNodeUnaryPlus] = kEOpAssNo, + + [kExprNodeSubscript] = kEOpAssLeft, + [kExprNodeCall] = kEOpAssLeft, + + [kExprNodeRegister] = kEOpAssNo, + [kExprNodeListLiteral] = kEOpAssNo, + [kExprNodePlainIdentifier] = kEOpAssNo, +}; + +#ifdef UNIT_TESTING +#include +REAL_FATTR_UNUSED +static inline void viml_pexpr_debug_print_ast_stack( + const ExprASTStack *const ast_stack, + const char *const msg) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE +{ + fprintf(stderr, "\n%sstack: %zu:\n", msg, kv_size(*ast_stack)); + for (size_t i = 0; i < kv_size(*ast_stack); i++) { + const ExprASTNode *const *const eastnode_p = ( + (const ExprASTNode *const *)kv_A(*ast_stack, i)); + if (*eastnode_p == NULL) { + fprintf(stderr, "- %p : NULL\n", (void *)eastnode_p); + } else { + fprintf(stderr, "- %p : %p : %c : %zu:%zu:%zu\n", + (void *)eastnode_p, (void *)(*eastnode_p), (*eastnode_p)->type, + (*eastnode_p)->start.line, (*eastnode_p)->start.col, + (*eastnode_p)->len); + } + } +} +#define PSTACK(msg) \ + viml_pexpr_debug_print_ast_stack(&ast_stack, #msg) +#define PSTACK_P(msg) \ + viml_pexpr_debug_print_ast_stack(ast_stack, #msg) +#endif + +/// Handle binary operator +/// +/// This function is responsible for handling priority levels as well. +static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, + ExprASTNode *const bop_node, + ExprASTLevel *const want_level_p) + FUNC_ATTR_NONNULL_ALL +{ + ExprASTNode **top_node_p = NULL; + ExprASTNode *top_node; + ExprOpLvl top_node_lvl; + ExprOpAssociativity top_node_ass; + assert(kv_size(*ast_stack)); + const ExprOpLvl bop_node_lvl = node_type_to_op_lvl[bop_node->type]; + do { + ExprASTNode **new_top_node_p = kv_last(*ast_stack); + ExprASTNode *new_top_node = *new_top_node_p; + assert(new_top_node != NULL); + const ExprOpLvl new_top_node_lvl = node_type_to_op_lvl[new_top_node->type]; + const ExprOpAssociativity new_top_node_ass = ( + node_type_to_op_ass[new_top_node->type]); + if (top_node_p != NULL + && ((bop_node_lvl > new_top_node_lvl + || (bop_node_lvl == new_top_node_lvl + && new_top_node_ass == kEOpAssNo)))) { + break; + } + kv_drop(*ast_stack, 1); + top_node_p = new_top_node_p; + top_node = new_top_node; + top_node_lvl = new_top_node_lvl; + top_node_ass = new_top_node_ass; + } while (kv_size(*ast_stack)); + // FIXME Handle right and no associativity correctly + *top_node_p = bop_node; + bop_node->children = top_node; + assert(bop_node->children->next == NULL); + kvi_push(*ast_stack, top_node_p); + kvi_push(*ast_stack, &bop_node->children->next); + *want_level_p = kELvlValue; +} + +/// Get highlight group name +#define HL(g) (is_invalid ? "NVimInvalid" #g : "NVim" #g) + +/// Highlight current token with the given group +#define HL_CUR_TOKEN(g) \ + viml_parser_highlight(pstate, cur_token.start, cur_token.len, \ + HL(g)) + +/// Allocate new node, saving some values +#define NEW_NODE(type) \ + viml_pexpr_new_node(type) + +/// Set position of the given node to position from the given token +/// +/// @param cur_node Node to modify. +/// @param cur_token Token to set position from. +#define POS_FROM_TOKEN(cur_node, cur_token) \ + do { \ + cur_node->start = cur_token.start; \ + cur_node->len = cur_token.len; \ + } while (0) + +/// Allocate new node and set its position from the current token +/// +/// If previous token happened to contain spacing then it will be included. +/// +/// @param cur_node Variable to save allocated node to. +/// @param typ Node type. +#define NEW_NODE_WITH_CUR_POS(cur_node, typ) \ + do { \ + cur_node = NEW_NODE(typ); \ + POS_FROM_TOKEN(cur_node, cur_token); \ + if (prev_token.type == kExprLexSpacing) { \ + cur_node->start = prev_token.start; \ + cur_node->len += prev_token.len; \ + } \ + } while (0) + +// TODO(ZyX-I): actual condition +/// Check whether it is possible to have next expression after current +/// +/// For :echo: `:echo @a @a` is a valid expression. `:echo (@a @a)` is not. +#define MAY_HAVE_NEXT_EXPR \ + (kv_size(ast_stack) == 1) + +/// Record missing operator: for things like +/// +/// :echo @a @a +/// +/// (allowed) or +/// +/// :echo (@a @a) +/// +/// (parsed as OpMissing(@a, @a)). +#define OP_MISSING \ + do { \ + if (flags & kExprFlagsMulti && MAY_HAVE_NEXT_EXPR) { \ + /* Multiple expressions allowed, return without calling */ \ + /* viml_parser_advance(). */ \ + goto viml_pexpr_parse_end; \ + } else { \ + assert(*top_node_p != NULL); \ + ERROR_FROM_TOKEN_AND_MSG(cur_token, _("E15: Missing operator: %.*s")); \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeOpMissing); \ + cur_node->len = 0; \ + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_level); \ + is_invalid = true; \ + goto viml_pexpr_parse_process_token; \ + } \ + } while (0) + +/// Set AST error, unless AST already is not correct +/// +/// @param[out] ret_ast AST to set error in. +/// @param[in] pstate Parser state, used to get error message argument. +/// @param[in] msg Error message, assumed to be already translated and +/// containing a single %token "%.*s". +/// @param[in] start Position at which error occurred. +static inline void east_set_error(ExprAST *const ret_ast, + const ParserState *const pstate, + const char *const msg, + const ParserPosition start) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE +{ + if (!ret_ast->correct) { + return; + } + const ParserLine pline = pstate->reader.lines.items[start.line]; + ret_ast->correct = false; + ret_ast->err.msg = msg; + ret_ast->err.arg_len = (int)(pline.size - start.col); + ret_ast->err.arg = pline.data + start.col; +} + +/// Set error from the given kExprLexInvalid token and given message +#define ERROR_FROM_TOKEN_AND_MSG(cur_token, msg) \ + east_set_error(&ast, pstate, msg, cur_token.start) + +/// Set error from the given kExprLexInvalid token +#define ERROR_FROM_TOKEN(cur_token) \ + ERROR_FROM_TOKEN_AND_MSG(cur_token, cur_token.data.err.msg) + +/// Parse one VimL expression +/// +/// @param pstate Parser state. +/// @param[in] flags Additional flags, see ExprParserFlags +/// +/// @return Parsed AST. +ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + ExprAST ast = { + .correct = true, + .err = { + .msg = NULL, + .arg_len = 0, + .arg = NULL, + }, + .root = NULL, + }; + ExprASTStack ast_stack; + kvi_init(ast_stack); + kvi_push(ast_stack, &ast.root); + // Expressions stack: + // 1. *last is NULL if want_level is kExprLexValue. Indicates where expression + // is to be put. + // 2. *last is not NULL otherwise, indicates current expression to be used as + // an operator argument. + ExprASTLevel want_level = kELvlValue; + LexExprToken prev_token = { .type = kExprLexMissing }; + bool highlighted_prev_spacing = false; + do { + LexExprToken cur_token = viml_pexpr_next_token(pstate, true); + if (cur_token.type == kExprLexEOC) { + if (flags & kExprFlagsDisallowEOC) { + if (cur_token.len == 0) { + // It is end of string, break. + break; + } else { + // It is NL, NUL or bar. + // + // Note: `=1 | 2` actually yields 1 in Vim without any + // errors. This will be changed here. + cur_token.type = kExprLexInvalid; + cur_token.data.err.msg = _("E15: Unexpected EOC character: %.*s"); + const ParserLine pline = ( + pstate->reader.lines.items[cur_token.start.line]); + const char eoc_char = pline.data[cur_token.start.col]; + cur_token.data.err.type = ((eoc_char == NUL || eoc_char == NL) + ? kExprLexSpacing + : kExprLexOr); + } + } else { + break; + } + } + LexExprTokenType tok_type = cur_token.type; + const bool token_invalid = (tok_type == kExprLexInvalid); + bool is_invalid = token_invalid; +viml_pexpr_parse_process_token: + if (tok_type == kExprLexSpacing) { + if (is_invalid) { + viml_parser_highlight(pstate, cur_token.start, cur_token.len, + HL(Spacing)); + } else { + // Do not do anything: let regular spacing be highlighted as normal. + // This also allows later to highlight spacing as invalid. + } + goto viml_pexpr_parse_cycle_end; + } else if (is_invalid && prev_token.type == kExprLexSpacing + && !highlighted_prev_spacing) { + viml_parser_highlight(pstate, prev_token.start, prev_token.len, + HL(Spacing)); + is_invalid = false; + highlighted_prev_spacing = true; + } + ExprASTNode **const top_node_p = kv_last(ast_stack); + ExprASTNode *cur_node = NULL; + // Keep these two asserts separate for debugging purposes. + assert(want_level == kELvlValue || *top_node_p != NULL); + assert(want_level != kELvlValue || *top_node_p == NULL); + switch (tok_type) { + case kExprLexEOC: { + assert(false); + } + case kExprLexInvalid: { + ERROR_FROM_TOKEN(cur_token); + tok_type = cur_token.data.err.type; + goto viml_pexpr_parse_process_token; + } + case kExprLexRegister: { + if (want_level == kELvlValue) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeRegister); + cur_node->data.reg.name = cur_token.data.reg.name; + *top_node_p = cur_node; + want_level = kELvlOperator; + viml_parser_highlight(pstate, cur_token.start, cur_token.len, + HL(Register)); + } else { + // Register in operator position: e.g. @a @a + OP_MISSING; + } + break; + } + case kExprLexPlus: { + if (want_level == kELvlValue) { + // Value level: assume unary plus + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnaryPlus); + *top_node_p = cur_node; + kvi_push(ast_stack, &cur_node->children); + HL_CUR_TOKEN(UnaryPlus); + } else if (want_level < kELvlValue) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeBinaryPlus); + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_level); + HL_CUR_TOKEN(BinaryPlus); + } + want_level = kELvlValue; + break; + } + case kExprLexParenthesis: { + if (cur_token.data.brc.closing) { + if (want_level == kELvlValue) { + if (kv_size(ast_stack) > 1) { + const ExprASTNode *const prev_top_node = *kv_Z(ast_stack, 1); + if (prev_top_node->type == kExprNodeCall) { + // Function call without arguments, this is not an error. + // But further code does not expect NULL nodes. + kv_drop(ast_stack, 1); + goto viml_pexpr_parse_no_paren_closing_error; + } + } + is_invalid = true; + ERROR_FROM_TOKEN_AND_MSG(cur_token, _("E15: Expected value: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeMissing); + cur_node->len = 0; + *top_node_p = cur_node; + } else { + // Always drop the topmost value: when want_level != kELvlValue + // topmost item on stack is a *finished* left operand, which may as + // well be "(@a)" which needs not be finished. + kv_drop(ast_stack, 1); + } +viml_pexpr_parse_no_paren_closing_error: {} + ExprASTNode **new_top_node_p = NULL; + while (kv_size(ast_stack) + && (new_top_node_p == NULL + || ((*new_top_node_p)->type != kExprNodeNested + && (*new_top_node_p)->type != kExprNodeCall))) { + new_top_node_p = kv_pop(ast_stack); + } + if (new_top_node_p != NULL + && ((*new_top_node_p)->type == kExprNodeNested + || (*new_top_node_p)->type == kExprNodeCall)) { + if ((*new_top_node_p)->type == kExprNodeNested) { + HL_CUR_TOKEN(NestingParenthesis); + } else { + HL_CUR_TOKEN(CallingParenthesis); + } + } else { + // “Always drop the topmost value” branch has got rid of the single + // value stack had, so there is nothing known to enclose. Correct + // this. + if (new_top_node_p == NULL) { + new_top_node_p = top_node_p; + } + is_invalid = true; + HL_CUR_TOKEN(NestingParenthesis); + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Unexpected closing parenthesis: %.*s")); + cur_node = NEW_NODE(kExprNodeNested); + cur_node->start = cur_token.start; + cur_node->len = 0; + // Unexpected closing parenthesis, assume that it was wanted to + // enclose everything in (). + cur_node->children = *new_top_node_p; + *new_top_node_p = cur_node; + assert(cur_node->next == NULL); + } + kvi_push(ast_stack, new_top_node_p); + want_level = kELvlOperator; + } else { + if (want_level == kELvlValue) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeNested); + *top_node_p = cur_node; + kvi_push(ast_stack, &cur_node->children); + HL_CUR_TOKEN(NestingParenthesis); + } else if (want_level == kELvlOperator) { + if (prev_token.type == kExprLexSpacing) { + // For some reason "function (args)" is a function call, but + // "(funcref) (args)" is not. AFAIR this somehow involves + // compatibility and Bram was commenting that this is + // intentionally inconsistent and he is not very happy with the + // situation himself. + if ((*top_node_p)->type != kExprNodePlainIdentifier + && (*top_node_p)->type != kExprNodeComplexIdentifier) { + OP_MISSING; + } + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeCall); + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_level); + HL_CUR_TOKEN(CallingParenthesis); + } else { + // Currently it is impossible to reach this. + assert(false); + } + want_level = kELvlValue; + } + break; + } + } +viml_pexpr_parse_cycle_end: + prev_token = cur_token; + highlighted_prev_spacing = false; + viml_parser_advance(pstate, cur_token.len); + } while (true); +viml_pexpr_parse_end: + if (want_level == kELvlValue) { + east_set_error(&ast, pstate, _("E15: Expected value: %.*s"), pstate->pos); + } else if (kv_size(ast_stack) != 1) { + // Something may be wrong, check whether it really is. + + // Pointer to ast.root must never be dropped, so “!= 1” is expected to be + // the same as “> 1”. + assert(kv_size(ast_stack)); + // Topmost stack item must be a *finished* value, so it must not be + // analyzed. E.g. it may contain an already finished nested expression. + kv_drop(ast_stack, 1); + while (ast.correct && kv_size(ast_stack)) { + const ExprASTNode *const cur_node = (*kv_pop(ast_stack)); + // This should only happen when want_level == kELvlValue. + assert(cur_node != NULL); + switch (cur_node->type) { + case kExprNodeOpMissing: + case kExprNodeMissing: { + // Error should’ve been already reported. + break; + } + case kExprNodeCall: { + // TODO(ZyX-I): Rehighlight as invalid? + east_set_error( + &ast, pstate, + _("E116: Missing closing parenthesis for function call: %.*s"), + cur_node->start); + break; + } + case kExprNodeNested: { + // TODO(ZyX-I): Rehighlight as invalid? + east_set_error( + &ast, pstate, + _("E110: Missing closing parenthesis for nested expression" + ": %.*s"), + cur_node->start); + break; + } + case kExprNodeBinaryPlus: + case kExprNodeUnaryPlus: + case kExprNodeRegister: { + // It is OK to see these in the stack. + break; + } + // TODO(ZyX-I): handle other values + } + } + } + kvi_destroy(ast_stack); + return ast; +} + +#undef NEW_NODE +#undef HL diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 52354760a5..13888562df 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -111,6 +111,80 @@ typedef struct { } data; ///< Additional data, if needed. } LexExprToken; +/// Expression AST node type +typedef enum { + kExprNodeMissing = 'X', + kExprNodeOpMissing = '_', + kExprNodeTernary = '?', ///< Ternary operator, valid one has three children. + kExprNodeRegister = '@', ///< Register, no children. + kExprNodeSubscript = 's', ///< Subscript, should have two or three children. + kExprNodeListLiteral = 'l', ///< List literal, any number of children. + kExprNodeUnaryPlus = 'p', + kExprNodeBinaryPlus = '+', + kExprNodeNested = 'e', ///< Nested parenthesised expression. + kExprNodeCall = 'c', ///< Function call. + /// Plain identifier: simple variable/function name + /// + /// Looks like "string", "g:Foo", etc: consists from a single + /// kExprLexPlainIdentifier token. + kExprNodePlainIdentifier = 'i', + /// Complex identifier: variable/function name with curly braces + kExprNodeComplexIdentifier = 'I', +} ExprASTNodeType; + +typedef struct expr_ast_node ExprASTNode; + +/// Structure representing one AST node +struct expr_ast_node { + ExprASTNodeType type; ///< Node type. + /// Node children: e.g. for 1 + 2 nodes 1 and 2 will be children of +. + ExprASTNode *children; + /// Next node: e.g. for 1 + 2 child nodes 1 and 2 are put into a single-linked + /// list: `(+)->children` references only node 1, node 2 is in + /// `(+)->children->next`. + ExprASTNode *next; + ParserPosition start; + size_t len; + union { + struct { + int name; ///< Register name, may be -1 if name not present. + } reg; ///< For kExprNodeRegister. + } data; +}; + +enum { + /// Allow multiple expressions in a row: e.g. for :echo + /// + /// Parser will still parse only one of them though. + kExprFlagsMulti = (1 << 0), + /// Allow NL, NUL and bar to be EOC + /// + /// When parsing expressions input by user bar is assumed to be a binary + /// operator and other two are spacings. + kExprFlagsDisallowEOC = (1 << 1), + /// Print errors when encountered + /// + /// Without the flag they are only taken into account when parsing. + kExprFlagsPrintError = (1 << 2), +} ExprParserFlags; + +/// Structure representing complety AST for one expression +typedef struct { + /// True if represented AST is correct and can be executed. Incorrect ones may + /// still be used for completion, or in linters. + bool correct; + /// When AST is not correct this message will be printed. + /// + /// Uses `emsgf(msg, arg_len, arg);`, `msg` is assumed to contain only `%.*s`. + struct { + const char *msg; + int arg_len; + const char *arg; + } err; + /// Root node of the AST. + ExprASTNode *root; +} ExprAST; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "viml/parser/expressions.h.generated.h" #endif -- cgit From 7c97f783935ec122fbf0d7d070c00804738abd6a Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 11 Sep 2017 01:27:46 +0300 Subject: klee: Start preparing for klee tests First stage: something compiling without klee, but with a buch of dirty hacks - done. Second stage: something running under klee, able to emit useful results, but still using dirty hacks - done. Third stage: make CMake care about clang argumnets - not done, may be omitted if proves to be too hard. Not that klee can be run on CI in any case. --- src/nvim/lib/ringbuf.h | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'src') diff --git a/src/nvim/lib/ringbuf.h b/src/nvim/lib/ringbuf.h index 12b75ec65a..e63eae70b0 100644 --- a/src/nvim/lib/ringbuf.h +++ b/src/nvim/lib/ringbuf.h @@ -15,6 +15,7 @@ #ifndef NVIM_LIB_RINGBUF_H #define NVIM_LIB_RINGBUF_H +#include #include #include #include @@ -73,6 +74,32 @@ typedef struct { \ RBType *buf_end; \ } TypeName##RingBuffer; +/// Dummy item free macros, for use in RINGBUF_INIT +/// +/// This macros actually does nothing. +/// +/// @param[in] item Item to be freed. +#define RINGBUF_DUMMY_FREE(item) + +/// Static ring buffer +/// +/// @warning Ring buffers created with this macros must neither be freed nor +/// deallocated. +/// +/// @param scope Ring buffer scope. +/// @param TypeName Ring buffer type name. +/// @param RBType Type of the single ring buffer element. +/// @param varname Variable name. +/// @param rbsize Ring buffer size. +#define RINGBUF_STATIC(scope, TypeName, RBType, varname, rbsize) \ +static RBType _##varname##_buf[rbsize]; \ +scope TypeName##RingBuffer varname = { \ + .buf = _##varname##_buf, \ + .next = _##varname##_buf, \ + .first = NULL, \ + .buf_end = _##varname##_buf + rbsize - 1, \ +}; + /// Initialize a new ring buffer /// /// @param TypeName Ring buffer type name. Actual type name will be -- cgit From 7980614650f0aedb39bf88466e5bd3ce90429cc1 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 17 Sep 2017 17:33:03 +0300 Subject: viml/parser/expressions: Add support for figure braces (three kinds) --- src/nvim/viml/parser/expressions.c | 644 +++++++++++++++++++++++++++++++++---- src/nvim/viml/parser/expressions.h | 35 ++ 2 files changed, 620 insertions(+), 59 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index b54f2eb237..f4cfed3113 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -20,10 +20,22 @@ typedef kvec_withinit_t(ExprASTNode **, 16) ExprASTStack; +/// Which nodes may be wanted typedef enum { - kELvlOperator, ///< Operators: function call, subscripts, binary operators, … - kELvlValue, ///< Actual value: literals, variables, nested expressions. -} ExprASTLevel; + /// Operators: function call, subscripts, binary operators, … + /// + /// For unrestricted expressions. + kENodeOperator, + /// Values: literals, variables, nested expressions, unary operators. + /// + /// For unrestricted expressions as well, implies that top item in AST stack + /// points to NULL. + kENodeValue, + /// Argument: only allows simple argument names. + kENodeArgument, + /// Argument separator: only allows commas. + kENodeArgumentSeparator, +} ExprASTWantedNode; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "viml/parser/expressions.c.generated.h" @@ -456,6 +468,8 @@ viml_pexpr_next_token_adv_return: // // Used highlighting groups and assumed linkage: // +// NVimInternalError -> highlight as fg:red/bg:red +// // NVimInvalid -> Error // NVimInvalidValue -> NVimInvalid // NVimInvalidOperator -> NVimInvalid @@ -469,11 +483,33 @@ viml_pexpr_next_token_adv_return: // // NVimParenthesis -> Delimiter // +// NVimComma -> Delimiter +// NVimArrow -> Delimiter +// +// NVimLambda -> Delimiter +// NVimDict -> Delimiter +// NVimCurly -> Delimiter +// +// NVimIdentifier -> Identifier +// NVimIdentifierScope -> NVimIdentifier +// NVimIdentifierScopeDelimiter -> NVimIdentifier +// +// NVimFigureBrace -> NVimInternalError +// +// NVimInvalidComma -> NVimInvalidDelimiter // NVimInvalidSpacing -> NVimInvalid // NVimInvalidTernaryOperator -> NVimInvalidOperator // NVimInvalidRegister -> NVimInvalidValue // NVimInvalidClosingBracket -> NVimInvalidDelimiter // NVimInvalidSpacing -> NVimInvalid +// NVimInvalidArrow -> NVimInvalidDelimiter +// NVimInvalidLambda -> NVimInvalidDelimiter +// NVimInvalidDict -> NVimInvalidDelimiter +// NVimInvalidCurly -> NVimInvalidDelimiter +// NVimInvalidFigureBrace -> NVimInvalidDelimiter +// NVimInvalidIdentifier -> NVimInvalidValue +// NVimInvalidIdentifierScope -> NVimInvalidValue +// NVimInvalidIdentifierScopeDelimiter -> NVimInvalidValue // // NVimUnaryPlus -> NVimUnaryOperator // NVimBinaryPlus -> NVimBinaryOperator @@ -498,6 +534,9 @@ static inline ExprASTNode *viml_pexpr_new_node(const ExprASTNodeType type) typedef enum { kEOpLvlInvalid = 0, kEOpLvlParens, + kEOpLvlArrow, + kEOpLvlComma, + kEOpLvlColon, kEOpLvlTernary, kEOpLvlOr, kEOpLvlAnd, @@ -506,6 +545,7 @@ typedef enum { kEOpLvlMultiplication, ///< Multiplication, division and modulo. kEOpLvlUnary, ///< Unary operations: not, minus, plus. kEOpLvlSubscript, ///< Subscripts. + kEOpLvlComplexIdentifier, ///< Plain identifier, curly braces name. kEOpLvlValue, ///< Values: literals, variables, nested expressions, … } ExprOpLvl; @@ -520,7 +560,16 @@ static const ExprOpLvl node_type_to_op_lvl[] = { [kExprNodeOpMissing] = kEOpLvlMultiplication, [kExprNodeNested] = kEOpLvlParens, - [kExprNodeComplexIdentifier] = kEOpLvlParens, + + [kExprNodeUnknownFigure] = kEOpLvlParens, + [kExprNodeLambda] = kEOpLvlParens, + [kExprNodeDictLiteral] = kEOpLvlParens, + + [kExprNodeArrow] = kEOpLvlArrow, + + [kExprNodeComma] = kEOpLvlComma, + + [kExprNodeColon] = kEOpLvlColon, [kExprNodeTernary] = kEOpLvlTernary, @@ -531,9 +580,12 @@ static const ExprOpLvl node_type_to_op_lvl[] = { [kExprNodeSubscript] = kEOpLvlSubscript, [kExprNodeCall] = kEOpLvlSubscript, + [kExprNodeComplexIdentifier] = kEOpLvlComplexIdentifier, + [kExprNodePlainIdentifier] = kEOpLvlComplexIdentifier, + [kExprNodeCurlyBracesIdentifier] = kEOpLvlComplexIdentifier, + [kExprNodeRegister] = kEOpLvlValue, [kExprNodeListLiteral] = kEOpLvlValue, - [kExprNodePlainIdentifier] = kEOpLvlValue, }; static const ExprOpAssociativity node_type_to_op_ass[] = { @@ -541,7 +593,24 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { [kExprNodeOpMissing] = kEOpAssNo, [kExprNodeNested] = kEOpAssNo, - [kExprNodeComplexIdentifier] = kEOpAssLeft, + + [kExprNodeUnknownFigure] = kEOpAssLeft, + [kExprNodeLambda] = kEOpAssNo, + [kExprNodeDictLiteral] = kEOpAssNo, + + // Does not really matter. + [kExprNodeArrow] = kEOpAssNo, + + [kExprNodeColon] = kEOpAssNo, + + // Right associativity for comma because this means easier access to arguments + // list, etc: for "[a, b, c, d]" you can access "a" in one step if it is + // represented as "list(comma(a, comma(b, comma(c, d))))" then if it is + // "list(comma(comma(comma(a, b), c), d))" in which case you will need to + // traverse all three comma() structures. And with comma operator (including + // actual comma operator from C which is not present in VimL) nobody cares + // about associativity, only about order of execution. + [kExprNodeComma] = kEOpAssRight, [kExprNodeTernary] = kEOpAssNo, @@ -552,14 +621,31 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { [kExprNodeSubscript] = kEOpAssLeft, [kExprNodeCall] = kEOpAssLeft, + [kExprNodePlainIdentifier] = kEOpAssLeft, + [kExprNodeComplexIdentifier] = kEOpAssLeft, + [kExprNodeCurlyBracesIdentifier] = kEOpAssLeft, + [kExprNodeRegister] = kEOpAssNo, [kExprNodeListLiteral] = kEOpAssNo, - [kExprNodePlainIdentifier] = kEOpAssNo, }; #ifdef UNIT_TESTING #include REAL_FATTR_UNUSED +static inline void viml_pexpr_debug_print_ast_node( + const ExprASTNode *const *const eastnode_p, + const char *const prefix) +{ + if (*eastnode_p == NULL) { + fprintf(stderr, "%s %p : NULL\n", prefix, (void *)eastnode_p); + } else { + fprintf(stderr, "%s %p : %p : %c : %zu:%zu:%zu\n", + prefix, (void *)eastnode_p, (void *)(*eastnode_p), + (*eastnode_p)->type, (*eastnode_p)->start.line, + (*eastnode_p)->start.col, (*eastnode_p)->len); + } +} +REAL_FATTR_UNUSED static inline void viml_pexpr_debug_print_ast_stack( const ExprASTStack *const ast_stack, const char *const msg) @@ -567,22 +653,17 @@ static inline void viml_pexpr_debug_print_ast_stack( { fprintf(stderr, "\n%sstack: %zu:\n", msg, kv_size(*ast_stack)); for (size_t i = 0; i < kv_size(*ast_stack); i++) { - const ExprASTNode *const *const eastnode_p = ( - (const ExprASTNode *const *)kv_A(*ast_stack, i)); - if (*eastnode_p == NULL) { - fprintf(stderr, "- %p : NULL\n", (void *)eastnode_p); - } else { - fprintf(stderr, "- %p : %p : %c : %zu:%zu:%zu\n", - (void *)eastnode_p, (void *)(*eastnode_p), (*eastnode_p)->type, - (*eastnode_p)->start.line, (*eastnode_p)->start.col, - (*eastnode_p)->len); - } + viml_pexpr_debug_print_ast_node( + (const ExprASTNode *const *)kv_A(*ast_stack, i), + "-"); } } #define PSTACK(msg) \ viml_pexpr_debug_print_ast_stack(&ast_stack, #msg) #define PSTACK_P(msg) \ viml_pexpr_debug_print_ast_stack(ast_stack, #msg) +#define PNODE_P(eastnode_p, msg) \ + viml_pexpr_debug_print_ast_node((const ExprASTNode *const *)ast_stack, #msg) #endif /// Handle binary operator @@ -590,7 +671,7 @@ static inline void viml_pexpr_debug_print_ast_stack( /// This function is responsible for handling priority levels as well. static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, ExprASTNode *const bop_node, - ExprASTLevel *const want_level_p) + ExprASTWantedNode *const want_node_p) FUNC_ATTR_NONNULL_ALL { ExprASTNode **top_node_p = NULL; @@ -599,6 +680,9 @@ static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, ExprOpAssociativity top_node_ass; assert(kv_size(*ast_stack)); const ExprOpLvl bop_node_lvl = node_type_to_op_lvl[bop_node->type]; +#ifndef NDEBUG + const ExprOpAssociativity bop_node_ass = node_type_to_op_ass[bop_node->type]; +#endif do { ExprASTNode **new_top_node_p = kv_last(*ast_stack); ExprASTNode *new_top_node = *new_top_node_p; @@ -606,6 +690,8 @@ static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, const ExprOpLvl new_top_node_lvl = node_type_to_op_lvl[new_top_node->type]; const ExprOpAssociativity new_top_node_ass = ( node_type_to_op_ass[new_top_node->type]); + assert(bop_node_lvl != new_top_node_lvl + || bop_node_ass == new_top_node_ass); if (top_node_p != NULL && ((bop_node_lvl > new_top_node_lvl || (bop_node_lvl == new_top_node_lvl @@ -617,14 +703,60 @@ static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, top_node = new_top_node; top_node_lvl = new_top_node_lvl; top_node_ass = new_top_node_ass; + if (bop_node_lvl == top_node_lvl && top_node_ass == kEOpAssRight) { + break; + } } while (kv_size(*ast_stack)); - // FIXME Handle right and no associativity correctly - *top_node_p = bop_node; - bop_node->children = top_node; - assert(bop_node->children->next == NULL); - kvi_push(*ast_stack, top_node_p); - kvi_push(*ast_stack, &bop_node->children->next); - *want_level_p = kELvlValue; + // FIXME: Handle no associativity + if (top_node_ass == kEOpAssLeft || top_node_lvl != bop_node_lvl) { + // outer(op(x,y)) -> outer(new_op(op(x,y),*)) + // + // Before: top_node_p = outer(*), points to op(x,y) + // Other stack elements unknown + // + // After: top_node_p = outer(*), points to new_op(op(x,y)) + // &bop_node->children->next = new_op(op(x,y),*), points to NULL + *top_node_p = bop_node; + bop_node->children = top_node; + assert(bop_node->children->next == NULL); + kvi_push(*ast_stack, top_node_p); + kvi_push(*ast_stack, &bop_node->children->next); + } else { + assert(top_node_lvl == bop_node_lvl && top_node_ass == kEOpAssRight); + assert(top_node->children != NULL && top_node->children->next != NULL); + // outer(op(x,y)) -> outer(op(x,new_op(y,*))) + // + // Before: top_node_p = outer(*), points to op(x,y) + // Other stack elements unknown + // + // After: top_node_p = outer(*), points to op(x,new_op(y)) + // &top_node->children->next = op(x,*), points to new_op(y) + // &bop_node->children->next = new_op(y,*), points to NULL + bop_node->children = top_node->children->next; + top_node->children->next = bop_node; + assert(bop_node->children->next == NULL); + kvi_push(*ast_stack, top_node_p); + kvi_push(*ast_stack, &top_node->children->next); + kvi_push(*ast_stack, &bop_node->children->next); + } + *want_node_p = (*want_node_p == kENodeArgumentSeparator + ? kENodeArgument + : kENodeValue); +} + +/// ParserPosition literal based on ParserPosition pos with columns shifted +/// +/// Function does not check whether remaining position is valid. +/// +/// @param[in] pos Position to shift. +/// @param[in] shift Number of bytes to shift. +/// +/// @return Shifted position. +static inline ParserPosition shifted_pos(const ParserPosition pos, + const size_t shift) + FUNC_ATTR_CONST FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return (ParserPosition) { .line = pos.line, .col = pos.col + shift }; } /// Get highlight group name @@ -692,12 +824,25 @@ static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, ERROR_FROM_TOKEN_AND_MSG(cur_token, _("E15: Missing operator: %.*s")); \ NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeOpMissing); \ cur_node->len = 0; \ - viml_pexpr_handle_bop(&ast_stack, cur_node, &want_level); \ - is_invalid = true; \ + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); \ goto viml_pexpr_parse_process_token; \ } \ } while (0) +/// Record missing value: for things like "* 5" +/// +/// @param[in] msg Error message. +#define ADD_VALUE_IF_MISSING(msg) \ + do { \ + if (want_node == kENodeValue) { \ + ERROR_FROM_TOKEN_AND_MSG(cur_token, (msg)); \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeMissing); \ + cur_node->len = 0; \ + *top_node_p = cur_node; \ + want_node = kENodeOperator; \ + } \ + } while (0) + /// Set AST error, unless AST already is not correct /// /// @param[out] ret_ast AST to set error in. @@ -721,14 +866,42 @@ static inline void east_set_error(ExprAST *const ret_ast, ret_ast->err.arg = pline.data + start.col; } -/// Set error from the given kExprLexInvalid token and given message +/// Set error from the given token and given message #define ERROR_FROM_TOKEN_AND_MSG(cur_token, msg) \ - east_set_error(&ast, pstate, msg, cur_token.start) + do { \ + is_invalid = true; \ + east_set_error(&ast, pstate, msg, cur_token.start); \ + } while (0) + +/// Like #ERROR_FROM_TOKEN_AND_MSG, but gets position from a node +#define ERROR_FROM_NODE_AND_MSG(node, msg) \ + do { \ + is_invalid = true; \ + east_set_error(&ast, pstate, msg, node->start); \ + } while (0) /// Set error from the given kExprLexInvalid token #define ERROR_FROM_TOKEN(cur_token) \ ERROR_FROM_TOKEN_AND_MSG(cur_token, cur_token.data.err.msg) +/// Select figure brace type, altering highlighting as well if needed +/// +/// @param[out] node Node to modify type. +/// @param[in] new_type New type, one of ExprASTNodeType values without +/// kExprNode prefix. +/// @param[in] hl Corresponding highlighting, passed as an argument to #HL. +#define SELECT_FIGURE_BRACE_TYPE(node, new_type, hl) \ + do { \ + ExprASTNode *const node_ = (node); \ + assert(node_->type == kExprNodeUnknownFigure \ + || node_->type == kExprNode##new_type); \ + node_->type = kExprNode##new_type; \ + if (pstate->colors) { \ + kv_A(*pstate->colors, node_->data.fig.opening_hl_idx).group = \ + HL(hl); \ + } \ + } while (0) + /// Parse one VimL expression /// /// @param pstate Parser state. @@ -751,13 +924,15 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) kvi_init(ast_stack); kvi_push(ast_stack, &ast.root); // Expressions stack: - // 1. *last is NULL if want_level is kExprLexValue. Indicates where expression + // 1. *last is NULL if want_node is kExprLexValue. Indicates where expression // is to be put. // 2. *last is not NULL otherwise, indicates current expression to be used as // an operator argument. - ExprASTLevel want_level = kELvlValue; + ExprASTWantedNode want_node = kENodeValue; LexExprToken prev_token = { .type = kExprLexMissing }; bool highlighted_prev_spacing = false; + // Lambda node, valid when parsing lambda arguments only. + ExprASTNode *lambda_node = NULL; do { LexExprToken cur_token = viml_pexpr_next_token(pstate, true); if (cur_token.type == kExprLexEOC) { @@ -789,8 +964,7 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) viml_pexpr_parse_process_token: if (tok_type == kExprLexSpacing) { if (is_invalid) { - viml_parser_highlight(pstate, cur_token.start, cur_token.len, - HL(Spacing)); + HL_CUR_TOKEN(Spacing); } else { // Do not do anything: let regular spacing be highlighted as normal. // This also allows later to highlight spacing as invalid. @@ -803,11 +977,44 @@ viml_pexpr_parse_process_token: is_invalid = false; highlighted_prev_spacing = true; } + const ParserLine pline = pstate->reader.lines.items[cur_token.start.line]; ExprASTNode **const top_node_p = kv_last(ast_stack); ExprASTNode *cur_node = NULL; - // Keep these two asserts separate for debugging purposes. - assert(want_level == kELvlValue || *top_node_p != NULL); - assert(want_level != kELvlValue || *top_node_p == NULL); + assert((want_node == kENodeValue || want_node == kENodeArgument) + == (*top_node_p == NULL)); + if ((want_node == kENodeArgumentSeparator + && tok_type != kExprLexComma + && tok_type != kExprLexArrow) + || (want_node == kENodeArgument + && !(tok_type == kExprLexPlainIdentifier + && cur_token.data.var.scope == 0 + && !cur_token.data.var.autoload) + && tok_type != kExprLexArrow)) { + lambda_node->data.fig.type_guesses.allow_lambda = false; + if (lambda_node->children != NULL + && lambda_node->children->type == kExprNodeComma) { + // If lambda has comma child this means that parser has already seen at + // least "{arg1,", so node cannot possibly be anything, but lambda. + + // Vim may give E121 or E720 in this case, but it does not look right to + // have either because both are results of reevaluation possibly-lambda + // node as a dictionary and here this is not going to happen. + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Expected lambda arguments list or arrow: %.*s")); + } else { + // Else it may appear that possibly-lambda node is actually a dictionary + // or curly-braces-name identifier. + lambda_node = NULL; + if (want_node == kENodeArgumentSeparator) { + want_node = kENodeOperator; + } else { + want_node = kENodeValue; + } + } + } + assert(lambda_node == NULL + || want_node == kENodeArgumentSeparator + || want_node == kENodeArgument); switch (tok_type) { case kExprLexEOC: { assert(false); @@ -818,13 +1025,12 @@ viml_pexpr_parse_process_token: goto viml_pexpr_parse_process_token; } case kExprLexRegister: { - if (want_level == kELvlValue) { + if (want_node == kENodeValue) { NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeRegister); cur_node->data.reg.name = cur_token.data.reg.name; *top_node_p = cur_node; - want_level = kELvlOperator; - viml_parser_highlight(pstate, cur_token.start, cur_token.len, - HL(Register)); + want_node = kENodeOperator; + HL_CUR_TOKEN(Register); } else { // Register in operator position: e.g. @a @a OP_MISSING; @@ -832,23 +1038,343 @@ viml_pexpr_parse_process_token: break; } case kExprLexPlus: { - if (want_level == kELvlValue) { + if (want_node == kENodeValue) { // Value level: assume unary plus NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnaryPlus); *top_node_p = cur_node; kvi_push(ast_stack, &cur_node->children); HL_CUR_TOKEN(UnaryPlus); - } else if (want_level < kELvlValue) { + } else { NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeBinaryPlus); - viml_pexpr_handle_bop(&ast_stack, cur_node, &want_level); + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); HL_CUR_TOKEN(BinaryPlus); } - want_level = kELvlValue; + want_node = kENodeValue; + break; + } + case kExprLexComma: { + assert(want_node != kENodeArgument); + if (want_node == kENodeValue) { + // Value level: comma appearing here is not valid. + // Note: in Vim string(,x) will give E116, this is not the case here. + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Expected value, got comma: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeMissing); + cur_node->len = 0; + *top_node_p = cur_node; + want_node = (want_node == kENodeArgument + ? kENodeArgumentSeparator + : kENodeOperator); + } + if (want_node == kENodeArgumentSeparator) { + assert(lambda_node->data.fig.type_guesses.allow_lambda); + assert(lambda_node != NULL); + SELECT_FIGURE_BRACE_TYPE(lambda_node, Lambda, Lambda); + } + if (kv_size(ast_stack) < 2) { + goto viml_pexpr_parse_invalid_comma; + } + for (size_t i = 1; i < kv_size(ast_stack); i++) { + const ExprASTNode *const *const eastnode_p = + (const ExprASTNode *const *)kv_Z(ast_stack, i); + if (!((*eastnode_p)->type == kExprNodeComma + || ((*eastnode_p)->type == kExprNodeColon + && i == 1)) + || i == kv_size(ast_stack) - 1) { + switch ((*eastnode_p)->type) { + case kExprNodeLambda: { + assert(want_node == kENodeArgumentSeparator); + break; + } + case kExprNodeDictLiteral: + case kExprNodeListLiteral: + case kExprNodeCall: { + break; + } + default: { +viml_pexpr_parse_invalid_comma: + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Comma outside of call, lambda or literal: %.*s")); + break; + } + } + break; + } + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComma); + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); + HL_CUR_TOKEN(Comma); + break; + } + case kExprLexColon: { + ADD_VALUE_IF_MISSING(_("E15: Expected value, got colon: %.*s")); + if (kv_size(ast_stack) < 2) { + goto viml_pexpr_parse_invalid_colon; + } + for (size_t i = 1; i < kv_size(ast_stack); i++) { + ExprASTNode *const *const eastnode_p = + (ExprASTNode *const *)kv_Z(ast_stack, i); + if ((*eastnode_p)->type != kExprNodeColon + || i == kv_size(ast_stack) - 1) { + switch ((*eastnode_p)->type) { + case kExprNodeUnknownFigure: { + SELECT_FIGURE_BRACE_TYPE((*eastnode_p), DictLiteral, Dict); + break; + } + case kExprNodeComma: + case kExprNodeDictLiteral: + case kExprNodeTernary: { + break; + } + default: { +viml_pexpr_parse_invalid_colon: + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Colon outside of dictionary or ternary operator: " + "%.*s")); + break; + } + } + break; + } + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); + // FIXME: Handle ternary operator. + HL_CUR_TOKEN(Colon); + want_node = kENodeValue; + break; + } + case kExprLexFigureBrace: { + if (cur_token.data.brc.closing) { + ExprASTNode **new_top_node_p = NULL; + // Always drop the topmost value: + // + // 1. When want_node != kENodeValue topmost item on stack is + // a *finished* left operand, which may as well be "{@a}" which + // needs not be finished again. + // 2. Otherwise it is pointing to NULL what nobody wants. + kv_drop(ast_stack, 1); + if (!kv_size(ast_stack)) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnknownFigure); + cur_node->data.fig.type_guesses.allow_lambda = false; + cur_node->data.fig.type_guesses.allow_dict = false; + cur_node->data.fig.type_guesses.allow_ident = false; + cur_node->len = 0; + if (want_node != kENodeValue) { + cur_node->children = *top_node_p; + } + *top_node_p = cur_node; + goto viml_pexpr_parse_figure_brace_closing_error; + } + if (want_node == kENodeValue) { + if ((*kv_last(ast_stack))->type != kExprNodeUnknownFigure + && (*kv_last(ast_stack))->type != kExprNodeComma) { + // kv_last being UnknownFigure may occur for empty dictionary + // literal, while Comma is expected in case of non-empty one. + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Expected value, got closing figure brace: %.*s")); + } + } else { + if (!kv_size(ast_stack)) { + new_top_node_p = top_node_p; + goto viml_pexpr_parse_figure_brace_closing_error; + } + } + do { + new_top_node_p = kv_pop(ast_stack); + } while (kv_size(ast_stack) + && (new_top_node_p == NULL + || ((*new_top_node_p)->type != kExprNodeUnknownFigure + && (*new_top_node_p)->type != kExprNodeDictLiteral + && ((*new_top_node_p)->type + != kExprNodeCurlyBracesIdentifier) + && (*new_top_node_p)->type != kExprNodeLambda))); + ExprASTNode *new_top_node = *new_top_node_p; + switch (new_top_node->type) { + case kExprNodeUnknownFigure: { + if (new_top_node->children == NULL) { + // No children of curly braces node indicates empty dictionary. + + // Should actually be kENodeArgument, but that was changed + // earlier. + assert(want_node == kENodeValue); + assert(new_top_node->data.fig.type_guesses.allow_dict); + SELECT_FIGURE_BRACE_TYPE(new_top_node, DictLiteral, Dict); + HL_CUR_TOKEN(Dict); + } else if (new_top_node->data.fig.type_guesses.allow_ident) { + SELECT_FIGURE_BRACE_TYPE(new_top_node, CurlyBracesIdentifier, + Curly); + HL_CUR_TOKEN(Curly); + } else { + // If by this time type of the node has not already been + // guessed, but it definitely is not a curly braces name then + // it is invalid for sure. + ERROR_FROM_NODE_AND_MSG( + new_top_node, + _("E15: Don't know what figure brace means: %.*s")); + if (pstate->colors) { + // Will reset to NVimInvalidFigureBrace. + kv_A(*pstate->colors, + new_top_node->data.fig.opening_hl_idx).group = ( + HL(FigureBrace)); + } + HL_CUR_TOKEN(FigureBrace); + } + break; + } + case kExprNodeDictLiteral: { + HL_CUR_TOKEN(Dict); + break; + } + case kExprNodeCurlyBracesIdentifier: { + HL_CUR_TOKEN(Curly); + break; + } + case kExprNodeLambda: { + HL_CUR_TOKEN(Lambda); + break; + } + default: { +viml_pexpr_parse_figure_brace_closing_error: + assert(!kv_size(ast_stack)); + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Unexpected closing figure brace: %.*s")); + HL_CUR_TOKEN(FigureBrace); + break; + } + } + kvi_push(ast_stack, new_top_node_p); + want_node = kENodeOperator; + } else { + if (want_node == kENodeValue) { + HL_CUR_TOKEN(FigureBrace); + // Value: may be any of lambda, dictionary literal and curly braces + // name. + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnknownFigure); + cur_node->data.fig.type_guesses.allow_lambda = true; + cur_node->data.fig.type_guesses.allow_dict = true; + cur_node->data.fig.type_guesses.allow_ident = true; + if (pstate->colors) { + cur_node->data.fig.opening_hl_idx = kv_size(*pstate->colors) - 1; + } + *top_node_p = cur_node; + kvi_push(ast_stack, &cur_node->children); + want_node = kENodeArgument; + lambda_node = cur_node; + } else { + // Operator: may only be curly braces name, but only under certain + // conditions. + + // First condition is that there is no space before {. + if (prev_token.type == kExprLexSpacing) { + OP_MISSING; + } + switch ((*top_node_p)->type) { + // Second is that previous node is one of the identifiers: + // complex, plain, curly braces. + + // TODO(ZyX-I): Extend syntax to allow ${expr}. This is needed to + // handle environment variables like those bash uses for + // `export -f`: their names consist not only of alphanumeric + // characetrs. + case kExprNodeComplexIdentifier: + case kExprNodePlainIdentifier: + case kExprNodeCurlyBracesIdentifier: { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComplexIdentifier); + cur_node->len = 0; + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); + ExprASTNode *const new_top_node = *kv_last(ast_stack); + assert(new_top_node->next == NULL); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeCurlyBracesIdentifier); + new_top_node->next = cur_node; + kvi_push(ast_stack, &cur_node->children); + HL_CUR_TOKEN(Curly); + break; + } + default: { + OP_MISSING; + break; + } + } + } + } + break; + } + case kExprLexArrow: { + if (want_node == kENodeArgumentSeparator + || want_node == kENodeArgument) { + if (want_node == kENodeArgument) { + kv_drop(ast_stack, 1); + } + assert(kv_size(ast_stack) >= 1); + while ((*kv_last(ast_stack))->type != kExprNodeLambda + && (*kv_last(ast_stack))->type != kExprNodeUnknownFigure) { + kv_drop(ast_stack, 1); + } + assert((*kv_last(ast_stack)) == lambda_node); + SELECT_FIGURE_BRACE_TYPE(lambda_node, Lambda, Lambda); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeArrow); + if (lambda_node->children == NULL) { + assert(want_node == kENodeArgument); + lambda_node->children = cur_node; + kvi_push(ast_stack, &lambda_node->children); + } else { + assert(lambda_node->children->next == NULL); + lambda_node->children->next = cur_node; + kvi_push(ast_stack, &lambda_node->children->next); + } + kvi_push(ast_stack, &cur_node->children); + lambda_node = NULL; + } else { + // Only first branch is valid. + is_invalid = true; + ADD_VALUE_IF_MISSING(_("E15: Unexpected arrow: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeArrow); + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); + } + want_node = kENodeValue; + HL_CUR_TOKEN(Arrow); + break; + } + case kExprLexPlainIdentifier: { + if (want_node == kENodeValue || want_node == kENodeArgument) { + want_node = (want_node == kENodeArgument + ? kENodeArgumentSeparator + : kENodeOperator); + // FIXME: It is not valid to have scope inside complex identifier, + // check that. + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodePlainIdentifier); + cur_node->data.var.scope = cur_token.data.var.scope; + const size_t scope_shift = (cur_token.data.var.scope == 0 + ? 0 + : 2); + cur_node->data.var.ident = (pline.data + cur_token.start.col + + scope_shift); + cur_node->data.var.ident_len = cur_token.len - scope_shift; + *top_node_p = cur_node; + if (scope_shift) { + viml_parser_highlight(pstate, cur_token.start, 1, + HL(IdentifierScope)); + viml_parser_highlight(pstate, shifted_pos(cur_token.start, 1), 1, + HL(IdentifierScopeDelimiter)); + } + if (scope_shift < cur_token.len) { + viml_parser_highlight(pstate, shifted_pos(cur_token.start, + scope_shift), + cur_token.len - scope_shift, + HL(Identifier)); + } + } else { + OP_MISSING; + } break; } case kExprLexParenthesis: { if (cur_token.data.brc.closing) { - if (want_level == kELvlValue) { + if (want_node == kENodeValue) { if (kv_size(ast_stack) > 1) { const ExprASTNode *const prev_top_node = *kv_Z(ast_stack, 1); if (prev_top_node->type == kExprNodeCall) { @@ -858,15 +1384,15 @@ viml_pexpr_parse_process_token: goto viml_pexpr_parse_no_paren_closing_error; } } - is_invalid = true; - ERROR_FROM_TOKEN_AND_MSG(cur_token, _("E15: Expected value: %.*s")); + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Expected value, got parenthesis: %.*s")); NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeMissing); cur_node->len = 0; *top_node_p = cur_node; } else { - // Always drop the topmost value: when want_level != kELvlValue + // Always drop the topmost value: when want_node != kENodeValue // topmost item on stack is a *finished* left operand, which may as - // well be "(@a)" which needs not be finished. + // well be "(@a)" which needs not be finished again. kv_drop(ast_stack, 1); } viml_pexpr_parse_no_paren_closing_error: {} @@ -892,10 +1418,9 @@ viml_pexpr_parse_no_paren_closing_error: {} if (new_top_node_p == NULL) { new_top_node_p = top_node_p; } - is_invalid = true; - HL_CUR_TOKEN(NestingParenthesis); ERROR_FROM_TOKEN_AND_MSG( cur_token, _("E15: Unexpected closing parenthesis: %.*s")); + HL_CUR_TOKEN(NestingParenthesis); cur_node = NEW_NODE(kExprNodeNested); cur_node->start = cur_token.start; cur_node->len = 0; @@ -906,14 +1431,14 @@ viml_pexpr_parse_no_paren_closing_error: {} assert(cur_node->next == NULL); } kvi_push(ast_stack, new_top_node_p); - want_level = kELvlOperator; + want_node = kENodeOperator; } else { - if (want_level == kELvlValue) { + if (want_node == kENodeValue) { NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeNested); *top_node_p = cur_node; kvi_push(ast_stack, &cur_node->children); HL_CUR_TOKEN(NestingParenthesis); - } else if (want_level == kELvlOperator) { + } else if (want_node == kENodeOperator) { if (prev_token.type == kExprLexSpacing) { // For some reason "function (args)" is a function call, but // "(funcref) (args)" is not. AFAIR this somehow involves @@ -926,13 +1451,13 @@ viml_pexpr_parse_no_paren_closing_error: {} } } NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeCall); - viml_pexpr_handle_bop(&ast_stack, cur_node, &want_level); + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); HL_CUR_TOKEN(CallingParenthesis); } else { // Currently it is impossible to reach this. assert(false); } - want_level = kELvlValue; + want_node = kENodeValue; } break; } @@ -943,8 +1468,9 @@ viml_pexpr_parse_cycle_end: viml_parser_advance(pstate, cur_token.len); } while (true); viml_pexpr_parse_end: - if (want_level == kELvlValue) { - east_set_error(&ast, pstate, _("E15: Expected value: %.*s"), pstate->pos); + if (want_node == kENodeValue) { + east_set_error(&ast, pstate, _("E15: Expected value, got EOC: %.*s"), + pstate->pos); } else if (kv_size(ast_stack) != 1) { // Something may be wrong, check whether it really is. @@ -956,7 +1482,7 @@ viml_pexpr_parse_end: kv_drop(ast_stack, 1); while (ast.correct && kv_size(ast_stack)) { const ExprASTNode *const cur_node = (*kv_pop(ast_stack)); - // This should only happen when want_level == kELvlValue. + // This should only happen when want_node == kENodeValue. assert(cur_node != NULL); switch (cur_node->type) { case kExprNodeOpMissing: diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 13888562df..13640ec137 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -2,6 +2,7 @@ #define NVIM_VIML_PARSER_EXPRESSIONS_H #include +#include #include #include "nvim/types.h" @@ -130,6 +131,17 @@ typedef enum { kExprNodePlainIdentifier = 'i', /// Complex identifier: variable/function name with curly braces kExprNodeComplexIdentifier = 'I', + /// Figure brace expression which is not yet known + /// + /// May resolve to any of kExprNodeDictLiteral, kExprNodeLambda or + /// kExprNodeCurlyBracesIdentifier. + kExprNodeUnknownFigure = '{', + kExprNodeLambda = '\\', ///< Lambda. + kExprNodeDictLiteral = 'd', ///< Dictionary literal. + kExprNodeCurlyBracesIdentifier= '}', ///< Part of the curly braces name. + kExprNodeComma = ',', ///< Comma “operator”. + kExprNodeColon = ':', ///< Colon “operator”. + kExprNodeArrow = '>', ///< Arrow “operator”. } ExprASTNodeType; typedef struct expr_ast_node ExprASTNode; @@ -149,6 +161,27 @@ struct expr_ast_node { struct { int name; ///< Register name, may be -1 if name not present. } reg; ///< For kExprNodeRegister. + struct { + /// Which nodes UnknownFigure can’t possibly represent. + struct { + /// True if UnknownFigure may actually represent dictionary literal. + bool allow_dict; + /// True if UnknownFigure may actually represent lambda. + bool allow_lambda; + /// True if UnknownFigure may actually be part of curly braces name. + bool allow_ident; + } type_guesses; + /// Highlight chunk index, used for rehighlighting if needed + size_t opening_hl_idx; + } fig; ///< For kExprNodeUnknownFigure. + struct { + int scope; ///< Scope character or 0 if not present. + /// Actual identifier without scope. + /// + /// Points to inside parser reader state. + const char *ident; + size_t ident_len; ///< Actual identifier length. + } var; } data; }; @@ -166,6 +199,8 @@ enum { /// /// Without the flag they are only taken into account when parsing. kExprFlagsPrintError = (1 << 2), + // WARNING: whenever you add a new flag, alter klee_assume() statement in + // viml_expressions_parser.c. } ExprParserFlags; /// Structure representing complety AST for one expression -- cgit From d4782fb1ca05e76095086bdcbc8dcea47f532d00 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 26 Sep 2017 00:28:34 +0300 Subject: viml/parser/expressions: Make commas actually work when calling --- src/nvim/viml/parser/expressions.c | 99 +++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 44 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index f4cfed3113..b9abf4a067 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -395,6 +395,43 @@ viml_pexpr_next_token_adv_return: return ret; } +#ifdef UNIT_TESTING +#include +REAL_FATTR_UNUSED +static inline void viml_pexpr_debug_print_ast_node( + const ExprASTNode *const *const eastnode_p, + const char *const prefix) +{ + if (*eastnode_p == NULL) { + fprintf(stderr, "%s %p : NULL\n", prefix, (void *)eastnode_p); + } else { + fprintf(stderr, "%s %p : %p : %c : %zu:%zu:%zu\n", + prefix, (void *)eastnode_p, (void *)(*eastnode_p), + (*eastnode_p)->type, (*eastnode_p)->start.line, + (*eastnode_p)->start.col, (*eastnode_p)->len); + } +} +REAL_FATTR_UNUSED +static inline void viml_pexpr_debug_print_ast_stack( + const ExprASTStack *const ast_stack, + const char *const msg) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE +{ + fprintf(stderr, "\n%sstack: %zu:\n", msg, kv_size(*ast_stack)); + for (size_t i = 0; i < kv_size(*ast_stack); i++) { + viml_pexpr_debug_print_ast_node( + (const ExprASTNode *const *)kv_A(*ast_stack, i), + "-"); + } +} +#define PSTACK(msg) \ + viml_pexpr_debug_print_ast_stack(&ast_stack, #msg) +#define PSTACK_P(msg) \ + viml_pexpr_debug_print_ast_stack(ast_stack, #msg) +#define PNODE_P(eastnode_p, msg) \ + viml_pexpr_debug_print_ast_node((const ExprASTNode *const *)ast_stack, #msg) +#endif + // start = s ternary_expr s EOC // ternary_expr = binop_expr // ( s Question s ternary_expr s Colon s ternary_expr s )? @@ -560,6 +597,9 @@ static const ExprOpLvl node_type_to_op_lvl[] = { [kExprNodeOpMissing] = kEOpLvlMultiplication, [kExprNodeNested] = kEOpLvlParens, + // Note: it is kEOpLvlSubscript for “binary operator” itself, but + // kEOpLvlParens when it comes to inside the parenthesis. + [kExprNodeCall] = kEOpLvlParens, [kExprNodeUnknownFigure] = kEOpLvlParens, [kExprNodeLambda] = kEOpLvlParens, @@ -578,7 +618,6 @@ static const ExprOpLvl node_type_to_op_lvl[] = { [kExprNodeUnaryPlus] = kEOpLvlUnary, [kExprNodeSubscript] = kEOpLvlSubscript, - [kExprNodeCall] = kEOpLvlSubscript, [kExprNodeComplexIdentifier] = kEOpLvlComplexIdentifier, [kExprNodePlainIdentifier] = kEOpLvlComplexIdentifier, @@ -593,6 +632,7 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { [kExprNodeOpMissing] = kEOpAssNo, [kExprNodeNested] = kEOpAssNo, + [kExprNodeCall] = kEOpAssNo, [kExprNodeUnknownFigure] = kEOpAssLeft, [kExprNodeLambda] = kEOpAssNo, @@ -619,7 +659,6 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { [kExprNodeUnaryPlus] = kEOpAssNo, [kExprNodeSubscript] = kEOpAssLeft, - [kExprNodeCall] = kEOpAssLeft, [kExprNodePlainIdentifier] = kEOpAssLeft, [kExprNodeComplexIdentifier] = kEOpAssLeft, @@ -629,43 +668,6 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { [kExprNodeListLiteral] = kEOpAssNo, }; -#ifdef UNIT_TESTING -#include -REAL_FATTR_UNUSED -static inline void viml_pexpr_debug_print_ast_node( - const ExprASTNode *const *const eastnode_p, - const char *const prefix) -{ - if (*eastnode_p == NULL) { - fprintf(stderr, "%s %p : NULL\n", prefix, (void *)eastnode_p); - } else { - fprintf(stderr, "%s %p : %p : %c : %zu:%zu:%zu\n", - prefix, (void *)eastnode_p, (void *)(*eastnode_p), - (*eastnode_p)->type, (*eastnode_p)->start.line, - (*eastnode_p)->start.col, (*eastnode_p)->len); - } -} -REAL_FATTR_UNUSED -static inline void viml_pexpr_debug_print_ast_stack( - const ExprASTStack *const ast_stack, - const char *const msg) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE -{ - fprintf(stderr, "\n%sstack: %zu:\n", msg, kv_size(*ast_stack)); - for (size_t i = 0; i < kv_size(*ast_stack); i++) { - viml_pexpr_debug_print_ast_node( - (const ExprASTNode *const *)kv_A(*ast_stack, i), - "-"); - } -} -#define PSTACK(msg) \ - viml_pexpr_debug_print_ast_stack(&ast_stack, #msg) -#define PSTACK_P(msg) \ - viml_pexpr_debug_print_ast_stack(ast_stack, #msg) -#define PNODE_P(eastnode_p, msg) \ - viml_pexpr_debug_print_ast_node((const ExprASTNode *const *)ast_stack, #msg) -#endif - /// Handle binary operator /// /// This function is responsible for handling priority levels as well. @@ -679,17 +681,24 @@ static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, ExprOpLvl top_node_lvl; ExprOpAssociativity top_node_ass; assert(kv_size(*ast_stack)); - const ExprOpLvl bop_node_lvl = node_type_to_op_lvl[bop_node->type]; +#define NODE_LVL(typ) \ + (bop_node->type == kExprNodeCall && typ == kExprNodeCall \ + ? kEOpLvlSubscript \ + : node_type_to_op_lvl[typ]) +#define NODE_ASS(typ) \ + (bop_node->type == kExprNodeCall && typ == kExprNodeCall \ + ? kEOpAssLeft \ + : node_type_to_op_ass[typ]) + const ExprOpLvl bop_node_lvl = NODE_LVL(bop_node->type); #ifndef NDEBUG - const ExprOpAssociativity bop_node_ass = node_type_to_op_ass[bop_node->type]; + const ExprOpAssociativity bop_node_ass = NODE_ASS(bop_node->type); #endif do { ExprASTNode **new_top_node_p = kv_last(*ast_stack); ExprASTNode *new_top_node = *new_top_node_p; assert(new_top_node != NULL); - const ExprOpLvl new_top_node_lvl = node_type_to_op_lvl[new_top_node->type]; - const ExprOpAssociativity new_top_node_ass = ( - node_type_to_op_ass[new_top_node->type]); + const ExprOpLvl new_top_node_lvl = NODE_LVL(new_top_node->type); + const ExprOpAssociativity new_top_node_ass = NODE_ASS(new_top_node->type); assert(bop_node_lvl != new_top_node_lvl || bop_node_ass == new_top_node_ass); if (top_node_p != NULL @@ -742,6 +751,8 @@ static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, *want_node_p = (*want_node_p == kENodeArgumentSeparator ? kENodeArgument : kENodeValue); +#undef NODE_ASS +#undef NODE_LVL } /// ParserPosition literal based on ParserPosition pos with columns shifted -- cgit From 3cc65ac054976ef7520f0247b430ebef2f9537b7 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 26 Sep 2017 00:52:40 +0300 Subject: viml/parser/expressions: Make commas actually work when calling --- src/nvim/viml/parser/expressions.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index b9abf4a067..7bee779c49 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -681,24 +681,22 @@ static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, ExprOpLvl top_node_lvl; ExprOpAssociativity top_node_ass; assert(kv_size(*ast_stack)); -#define NODE_LVL(typ) \ - (bop_node->type == kExprNodeCall && typ == kExprNodeCall \ - ? kEOpLvlSubscript \ - : node_type_to_op_lvl[typ]) -#define NODE_ASS(typ) \ - (bop_node->type == kExprNodeCall && typ == kExprNodeCall \ - ? kEOpAssLeft \ - : node_type_to_op_ass[typ]) - const ExprOpLvl bop_node_lvl = NODE_LVL(bop_node->type); + const ExprOpLvl bop_node_lvl = (bop_node->type == kExprNodeCall + ? kEOpLvlSubscript + : node_type_to_op_lvl[bop_node->type]); #ifndef NDEBUG - const ExprOpAssociativity bop_node_ass = NODE_ASS(bop_node->type); + const ExprOpAssociativity bop_node_ass = ( + bop_node->type == kExprNodeCall + ? kEOpAssLeft + : node_type_to_op_ass[bop_node->type]); #endif do { ExprASTNode **new_top_node_p = kv_last(*ast_stack); ExprASTNode *new_top_node = *new_top_node_p; assert(new_top_node != NULL); - const ExprOpLvl new_top_node_lvl = NODE_LVL(new_top_node->type); - const ExprOpAssociativity new_top_node_ass = NODE_ASS(new_top_node->type); + const ExprOpLvl new_top_node_lvl = node_type_to_op_lvl[new_top_node->type]; + const ExprOpAssociativity new_top_node_ass = ( + node_type_to_op_ass[new_top_node->type]); assert(bop_node_lvl != new_top_node_lvl || bop_node_ass == new_top_node_ass); if (top_node_p != NULL @@ -751,8 +749,6 @@ static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, *want_node_p = (*want_node_p == kENodeArgumentSeparator ? kENodeArgument : kENodeValue); -#undef NODE_ASS -#undef NODE_LVL } /// ParserPosition literal based on ParserPosition pos with columns shifted -- cgit From 0987d3b10f36202e9f0289b50298e69aaf2fa4d2 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 26 Sep 2017 01:22:13 +0300 Subject: viml/parser/expressions: Make curly braces name actually work --- src/nvim/viml/parser/expressions.c | 116 +++++++++++++++++++++++++------------ 1 file changed, 78 insertions(+), 38 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 7bee779c49..cabf2dac58 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -909,6 +909,55 @@ static inline void east_set_error(ExprAST *const ret_ast, } \ } while (0) +/// Add identifier which should constitute complex identifier node +/// +/// This one is to be called only in case want_node is kENodeOperator. +/// +/// @param new_ident_node_code Code used to create a new identifier node and +/// update want_node and ast_stack, without +/// a trailing semicolon. +/// @param hl Highlighting name to use, passed as an argument to #HL. +#define ADD_IDENT(new_ident_node_code, hl) \ + do { \ + assert(want_node == kENodeOperator); \ + /* Operator: may only be curly braces name, but only under certain */ \ + /* conditions. */ \ +\ + /* First condition is that there is no space before a part of complex */ \ + /* identifier. */ \ + if (prev_token.type == kExprLexSpacing) { \ + OP_MISSING; \ + } \ + switch ((*top_node_p)->type) { \ + /* Second is that previous node is one of the identifiers: */ \ + /* complex, plain, curly braces. */ \ +\ + /* TODO(ZyX-I): Extend syntax to allow ${expr}. This is needed to */ \ + /* handle environment variables like those bash uses for */ \ + /* `export -f`: their names consist not only of alphanumeric */ \ + /* characetrs. */ \ + case kExprNodeComplexIdentifier: \ + case kExprNodePlainIdentifier: \ + case kExprNodeCurlyBracesIdentifier: { \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComplexIdentifier); \ + cur_node->len = 0; \ + cur_node->children = *top_node_p; \ + *top_node_p = cur_node; \ + kvi_push(ast_stack, &cur_node->children->next); \ + ExprASTNode **const new_top_node_p = kv_last(ast_stack); \ + assert(*new_top_node_p == NULL); \ + new_ident_node_code; \ + *new_top_node_p = cur_node; \ + HL_CUR_TOKEN(hl); \ + break; \ + } \ + default: { \ + OP_MISSING; \ + break; \ + } \ + } \ + } while (0) + /// Parse one VimL expression /// /// @param pstate Parser state. @@ -1272,40 +1321,18 @@ viml_pexpr_parse_figure_brace_closing_error: want_node = kENodeArgument; lambda_node = cur_node; } else { - // Operator: may only be curly braces name, but only under certain - // conditions. - - // First condition is that there is no space before {. - if (prev_token.type == kExprLexSpacing) { - OP_MISSING; - } - switch ((*top_node_p)->type) { - // Second is that previous node is one of the identifiers: - // complex, plain, curly braces. - - // TODO(ZyX-I): Extend syntax to allow ${expr}. This is needed to - // handle environment variables like those bash uses for - // `export -f`: their names consist not only of alphanumeric - // characetrs. - case kExprNodeComplexIdentifier: - case kExprNodePlainIdentifier: - case kExprNodeCurlyBracesIdentifier: { - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComplexIdentifier); - cur_node->len = 0; - viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); - ExprASTNode *const new_top_node = *kv_last(ast_stack); - assert(new_top_node->next == NULL); - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeCurlyBracesIdentifier); - new_top_node->next = cur_node; - kvi_push(ast_stack, &cur_node->children); - HL_CUR_TOKEN(Curly); - break; - } - default: { - OP_MISSING; - break; - } - } + ADD_IDENT( + do { + NEW_NODE_WITH_CUR_POS(cur_node, + kExprNodeCurlyBracesIdentifier); + cur_node->data.fig.opening_hl_idx = kv_size(*pstate->colors); + cur_node->data.fig.type_guesses.allow_lambda = false; + cur_node->data.fig.type_guesses.allow_dict = false; + cur_node->data.fig.type_guesses.allow_ident = true; + kvi_push(ast_stack, &cur_node->children); + want_node = kENodeValue; + } while (0), + Curly); } } break; @@ -1351,8 +1378,6 @@ viml_pexpr_parse_figure_brace_closing_error: want_node = (want_node == kENodeArgument ? kENodeArgumentSeparator : kENodeOperator); - // FIXME: It is not valid to have scope inside complex identifier, - // check that. NEW_NODE_WITH_CUR_POS(cur_node, kExprNodePlainIdentifier); cur_node->data.var.scope = cur_token.data.var.scope; const size_t scope_shift = (cur_token.data.var.scope == 0 @@ -1374,8 +1399,22 @@ viml_pexpr_parse_figure_brace_closing_error: cur_token.len - scope_shift, HL(Identifier)); } + // FIXME: Actually, g{foo}g:foo is valid: "1?g{foo}g:foo" is like + // "g{foo}g" and not an error. } else { - OP_MISSING; + if (cur_token.data.var.scope == 0) { + ADD_IDENT( + do { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodePlainIdentifier); + cur_node->data.var.scope = cur_token.data.var.scope; + cur_node->data.var.ident = pline.data + cur_token.start.col; + cur_node->data.var.ident_len = cur_token.len; + want_node = kENodeOperator; + } while (0), + Identifier); + } else { + OP_MISSING; + } } break; } @@ -1453,7 +1492,8 @@ viml_pexpr_parse_no_paren_closing_error: {} // intentionally inconsistent and he is not very happy with the // situation himself. if ((*top_node_p)->type != kExprNodePlainIdentifier - && (*top_node_p)->type != kExprNodeComplexIdentifier) { + && (*top_node_p)->type != kExprNodeComplexIdentifier + && (*top_node_p)->type != kExprNodeCurlyBracesIdentifier) { OP_MISSING; } } -- cgit From 9fa8f7fc0a24371f7956450d840bdae8a2fc9a51 Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 28 Sep 2017 00:40:25 +0300 Subject: viml/parser/expressions: Add a way to adjust lexer It also adds support for kExprLexOr which for some reason was forgotten. It was only made sure that KLEE test compiles in non-KLEE mode, not that something works or that KLEE is able to run tests. --- src/nvim/viml/parser/expressions.c | 105 +++++++++++++++++++++++++------------ src/nvim/viml/parser/expressions.h | 28 ++++++++++ 2 files changed, 100 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index cabf2dac58..3027c0046b 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -47,10 +47,10 @@ typedef enum { /// Get next token for the VimL expression input /// /// @param pstate Parser state. -/// @param[in] peek If true, do not advance pstate cursor. +/// @param[in] flags Flags, @see LexExprFlags. /// /// @return Next token. -LexExprToken viml_pexpr_next_token(ParserState *const pstate, const bool peek) +LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { LexExprToken ret = { @@ -153,12 +153,33 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const bool peek) } // Number. - // Note: determining whether dot is (not) a part of a float needs more - // context, so lexer does not do this. - // FIXME: Resolve ambiguity by additional argument. case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { + ret.data.num.is_float = false; CHARREG(kExprLexNumber, ascii_isdigit); + if (flags & kELFlagAllowFloat) { + if (pline.size > ret.len + 1 + && pline.data[ret.len] == '.' + && ascii_isdigit(pline.data[ret.len + 1])) { + ret.len++; + ret.data.num.is_float = true; + CHARREG(kExprLexNumber, ascii_isdigit); + if (pline.size > ret.len + 1 + && (pline.data[ret.len] == 'e' + || pline.data[ret.len] == 'E') + && ((pline.size > ret.len + 2 + && (pline.data[ret.len + 1] == '+' + || pline.data[ret.len + 1] == '-') + && ascii_isdigit(pline.data[ret.len + 2])) + || ascii_isdigit(pline.data[ret.len + 1]))) { + ret.len++; + if (pline.data[ret.len] == '+' || pline.data[ret.len] == '-') { + ret.len++; + } + CHARREG(kExprLexNumber, ascii_isdigit); + } + } + } break; } @@ -187,8 +208,9 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const bool peek) ret.data.var.autoload = false; CHARREG(kExprLexPlainIdentifier, ISWORD); // "is" and "isnot" operators. - if ((ret.len == 2 && memcmp(pline.data, "is", 2) == 0) - || (ret.len == 5 && memcmp(pline.data, "isnot", 5) == 0)) { + if (!(flags & kELFlagIsNotCmp) + && ((ret.len == 2 && memcmp(pline.data, "is", 2) == 0) + || (ret.len == 5 && memcmp(pline.data, "isnot", 5) == 0))) { ret.type = kExprLexComparison; ret.data.cmp.type = kExprLexCmpIdentical; ret.data.cmp.inv = (ret.len == 5); @@ -197,14 +219,14 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const bool peek) } else if (ret.len == 1 && pline.size > 1 && strchr("sgvbwtla", schar) != NULL - && pline.data[ret.len] == ':') { + && pline.data[ret.len] == ':' + && !(flags & kELFlagForbidScope)) { ret.len++; ret.data.var.scope = schar; CHARREG(kExprLexPlainIdentifier, ISWORD_OR_AUTOLOAD); ret.data.var.autoload = ( memchr(pline.data + 2, AUTOLOAD_CHAR, ret.len - 2) != NULL); - // FIXME: Resolve ambiguity with an argument to the lexer function. // Previous CHARREG stopped at autoload character in order to make it // possible to detect `is#`. Continue now with autoload characters // included. @@ -373,7 +395,30 @@ viml_pexpr_next_token_invalid_comparison: // Expression end because Ex command ended. case NUL: case NL: { - ret.type = kExprLexEOC; + if (flags & kELFlagForbidEOC) { + ret.type = kExprLexInvalid; + ret.data.err.msg = _("E15: Unexpected EOC character: %.*s"); + ret.data.err.type = kExprLexSpacing; + } else { + ret.type = kExprLexEOC; + } + break; + } + + case '|': { + if (pline.size >= 2 && pline.data[ret.len] == '|') { + // "||" is or. + ret.len++; + ret.type = kExprLexOr; + } else if (flags & kELFlagForbidEOC) { + // Note: `=1 | 2` actually yields 1 in Vim without any + // errors. This will be changed here. + ret.type = kExprLexInvalid; + ret.data.err.msg = _("E15: Unexpected EOC character: %.*s"); + ret.data.err.type = kExprLexOr; + } else { + ret.type = kExprLexEOC; + } break; } @@ -389,7 +434,7 @@ viml_pexpr_next_token_invalid_comparison: } #undef GET_CCS viml_pexpr_next_token_adv_return: - if (!peek) { + if (!(flags & kELFlagPeek)) { viml_parser_advance(pstate, ret.len); } return ret; @@ -990,34 +1035,28 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) // Lambda node, valid when parsing lambda arguments only. ExprASTNode *lambda_node = NULL; do { - LexExprToken cur_token = viml_pexpr_next_token(pstate, true); + const int want_node_to_lexer_flags[] = { + [kENodeValue] = kELFlagIsNotCmp, + [kENodeOperator] = kELFlagForbidScope, + [kENodeArgument] = kELFlagIsNotCmp, + [kENodeArgumentSeparator] = kELFlagForbidScope, + }; + // FIXME Determine when (not) to allow floating-point numbers. + const int lexer_additional_flags = ( + kELFlagPeek + | ((flags & kExprFlagsDisallowEOC) ? kELFlagForbidEOC : 0)); + LexExprToken cur_token = viml_pexpr_next_token( + pstate, want_node_to_lexer_flags[want_node] | lexer_additional_flags); if (cur_token.type == kExprLexEOC) { - if (flags & kExprFlagsDisallowEOC) { - if (cur_token.len == 0) { - // It is end of string, break. - break; - } else { - // It is NL, NUL or bar. - // - // Note: `=1 | 2` actually yields 1 in Vim without any - // errors. This will be changed here. - cur_token.type = kExprLexInvalid; - cur_token.data.err.msg = _("E15: Unexpected EOC character: %.*s"); - const ParserLine pline = ( - pstate->reader.lines.items[cur_token.start.line]); - const char eoc_char = pline.data[cur_token.start.col]; - cur_token.data.err.type = ((eoc_char == NUL || eoc_char == NL) - ? kExprLexSpacing - : kExprLexOr); - } - } else { - break; - } + break; } LexExprTokenType tok_type = cur_token.type; const bool token_invalid = (tok_type == kExprLexInvalid); bool is_invalid = token_invalid; viml_pexpr_parse_process_token: + // May use different flags this time. + cur_token = viml_pexpr_next_token( + pstate, want_node_to_lexer_flags[want_node] | lexer_additional_flags); if (tok_type == kExprLexSpacing) { if (is_invalid) { HL_CUR_TOKEN(Spacing); diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 13640ec137..64abab9e41 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -109,9 +109,37 @@ typedef struct { LexExprTokenType type; ///< Suggested type for parsing incorrect code. const char *msg; ///< Error message. } err; ///< For kExprLexInvalid + + struct { + bool is_float; ///< True if number is a floating-point. + } num; ///< For kExprLexNumber } data; ///< Additional data, if needed. } LexExprToken; +typedef enum { + /// If set, “pointer” to the current byte in pstate will not be shifted + kELFlagPeek = (1 << 0), + /// Determines whether scope is allowed to come before the identifier + kELFlagForbidScope = (1 << 1), + /// Determines whether floating-point numbers are allowed + /// + /// I.e. whether dot is a decimal point separator or is not a part of + /// a number at all. + kELFlagAllowFloat = (1 << 2), + /// Determines whether `is` and `isnot` are seen as comparison operators + /// + /// If set they are supposed to be just regular identifiers. + kELFlagIsNotCmp = (1 << 3), + /// Determines whether EOC tokens are allowed + /// + /// If set then it will yield Invalid token with E15 in place of EOC one if + /// “EOC” is something like "|". It is fine with emitting EOC at the end of + /// string still, with or without this flag set. + kELFlagForbidEOC = (1 << 4), + // WARNING: whenever you add a new flag, alter klee_assume() statement in + // viml_expressions_lexer.c. +} LexExprFlags; + /// Expression AST node type typedef enum { kExprNodeMissing = 'X', -- cgit From f33543377e39fc62ab063ca57c716984fb07aea1 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 30 Sep 2017 21:34:34 +0300 Subject: viml/parser/expressions: Add a way to represent tokens from C code --- src/nvim/viml/parser/expressions.c | 179 +++++++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 3027c0046b..fc64fee140 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -440,6 +440,176 @@ viml_pexpr_next_token_adv_return: return ret; } +#ifdef UNIT_TESTING +static const char *const eltkn_type_tab[] = { + [kExprLexInvalid] = "Invalid", + [kExprLexMissing] = "Missing", + [kExprLexSpacing] = "Spacing", + [kExprLexEOC] = "EOC", + + [kExprLexQuestion] = "Question", + [kExprLexColon] = "Colon", + [kExprLexOr] = "Or", + [kExprLexAnd] = "And", + [kExprLexComparison] = "Comparison", + [kExprLexPlus] = "Plus", + [kExprLexMinus] = "Minus", + [kExprLexDot] = "Dot", + [kExprLexMultiplication] = "Multiplication", + + [kExprLexNot] = "Not", + + [kExprLexNumber] = "Number", + [kExprLexSingleQuotedString] = "SingleQuotedString", + [kExprLexDoubleQuotedString] = "DoubleQuotedString", + [kExprLexOption] = "Option", + [kExprLexRegister] = "Register", + [kExprLexEnv] = "Env", + [kExprLexPlainIdentifier] = "PlainIdentifier", + + [kExprLexBracket] = "Bracket", + [kExprLexFigureBrace] = "FigureBrace", + [kExprLexParenthesis] = "Parenthesis", + [kExprLexComma] = "Comma", + [kExprLexArrow] = "Arrow", +}; + +static const char *const eltkn_cmp_type_tab[] = { + [kExprLexCmpEqual] = "Equal", + [kExprLexCmpMatches] = "Matches", + [kExprLexCmpGreater] = "Greater", + [kExprLexCmpGreaterOrEqual] = "GreaterOrEqual", + [kExprLexCmpIdentical] = "Identical", +}; + +static const char *const ccs_tab[] = { + [kCCStrategyUseOption] = "UseOption", + [kCCStrategyMatchCase] = "MatchCase", + [kCCStrategyIgnoreCase] = "IgnoreCase", +}; + +static const char *const eltkn_mul_type_tab[] = { + [kExprLexMulMul] = "Mul", + [kExprLexMulDiv] = "Div", + [kExprLexMulMod] = "Mod", +}; + +static const char *const eltkn_opt_scope_tab[] = { + [kExprLexOptUnspecified] = "Unspecified", + [kExprLexOptGlobal] = "Global", + [kExprLexOptLocal] = "Local", +}; + +/// Represent `int` character as a string +/// +/// Converts +/// - ASCII digits into '{digit}' +/// - ASCII printable characters into a single-character strings +/// - everything else to numbers. +/// +/// @param[in] ch Character to convert. +/// +/// @return Converted string, stored in a static buffer (overriden after each +/// call). +static const char *intchar2str(const int ch) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + static char buf[sizeof(int) * 3 + 1]; + if (' ' <= ch && ch < 0x7f) { + if (ascii_isdigit(ch)) { + buf[0] = '\''; + buf[1] = (char)ch; + buf[2] = '\''; + buf[3] = NUL; + } else { + buf[0] = (char)ch; + buf[1] = NUL; + } + } else { + snprintf(buf, sizeof(buf), "%i", ch); + } + return buf; +} + +/// Represent token as a string +/// +/// Intended for testing and debugging purposes. +/// +/// @param[in] pstate Parser state, needed to get token string from it. May be +/// NULL, in which case in place of obtaining part of the +/// string represented by token only token length is +/// returned. +/// @param[in] token Token to represent. +/// @param[out] ret_size Return string size, for cases like NULs inside +/// a string. May be NULL. +/// +/// @return Token represented in a string form, in a static buffer (overwritten +/// on each call). +const char *viml_pexpr_repr_token(const ParserState *const pstate, + const LexExprToken token, + size_t *const ret_size) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + static char ret[1024]; + char *p = ret; + const char *const e = &ret[1024] - 1; +#define ADDSTR(...) \ + do { \ + p += snprintf(p, (size_t)(sizeof(ret) - (size_t)(p - ret)), __VA_ARGS__); \ + if (p >= e) { \ + goto viml_pexpr_repr_token_end; \ + } \ + } while (0) + ADDSTR("%zu:%zu:%s", token.start.line, token.start.col, + eltkn_type_tab[token.type]); + switch (token.type) { +#define TKNARGS(tkn_type, ...) \ + case tkn_type: { \ + ADDSTR(__VA_ARGS__); \ + break; \ + } + TKNARGS(kExprLexComparison, "(type=%s,ccs=%s,inv=%i)", + eltkn_cmp_type_tab[token.data.cmp.type], + ccs_tab[token.data.cmp.ccs], + (int)token.data.cmp.inv) + TKNARGS(kExprLexMultiplication, "(type=%s)", + eltkn_mul_type_tab[token.data.mul.type]) + TKNARGS(kExprLexRegister, "(name=%s)", intchar2str(token.data.reg.name)) + case kExprLexDoubleQuotedString: + TKNARGS(kExprLexSingleQuotedString, "(closed=%i)", + (int)token.data.str.closed) + TKNARGS(kExprLexOption, "(scope=%s,name=%.*s)", + eltkn_opt_scope_tab[token.data.opt.scope], + (int)token.data.opt.len, token.data.opt.name) + TKNARGS(kExprLexPlainIdentifier, "(scope=%s,autoload=%i)", + intchar2str(token.data.var.scope), (int)token.data.var.autoload) + TKNARGS(kExprLexNumber, "(is_float=%i)", (int)token.data.num.is_float) + TKNARGS(kExprLexInvalid, "(msg=%s)", token.data.err.msg) + default: { + // No additional arguments. + break; + } +#undef TKNARGS + } + if (pstate == NULL) { + ADDSTR("::%zu", token.len); + } else { + *p++ = ':'; + memmove( + p, &pstate->reader.lines.items[token.start.line].data[token.start.col], + token.len); + p += token.len; + *p = NUL; + } +#undef ADDSTR +viml_pexpr_repr_token_end: + if (ret_size != NULL) { + *ret_size = (size_t)(p - ret); + } + return ret; +} +#endif + #ifdef UNIT_TESTING #include REAL_FATTR_UNUSED @@ -469,12 +639,21 @@ static inline void viml_pexpr_debug_print_ast_stack( "-"); } } + +static inline void viml_pexpr_debug_print_token( + const ParserState *const pstate, const LexExprToken token) + FUNC_ATTR_ALWAYS_INLINE +{ + fprintf(stderr, "\ntkn: %s\n", viml_pexpr_repr_token(pstate, token, NULL)); +} #define PSTACK(msg) \ viml_pexpr_debug_print_ast_stack(&ast_stack, #msg) #define PSTACK_P(msg) \ viml_pexpr_debug_print_ast_stack(ast_stack, #msg) #define PNODE_P(eastnode_p, msg) \ viml_pexpr_debug_print_ast_node((const ExprASTNode *const *)ast_stack, #msg) +#define PTOKEN(tkn) \ + viml_pexpr_debug_print_token(pstate, tkn) #endif // start = s ternary_expr s EOC -- cgit From 3735537a508c5690c4622ebe450e6f3f15706670 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 1 Oct 2017 15:54:46 +0300 Subject: viml/parser/expressions: Fix call inside nested parenthesis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It may have incorrectly tried to call everything because of essentially “value” nodes being treated as not such. --- src/nvim/viml/parser/expressions.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index fc64fee140..1713d0c89f 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -612,6 +612,7 @@ viml_pexpr_repr_token_end: #ifdef UNIT_TESTING #include + REAL_FATTR_UNUSED static inline void viml_pexpr_debug_print_ast_node( const ExprASTNode *const *const eastnode_p, @@ -626,6 +627,7 @@ static inline void viml_pexpr_debug_print_ast_node( (*eastnode_p)->start.col, (*eastnode_p)->len); } } + REAL_FATTR_UNUSED static inline void viml_pexpr_debug_print_ast_stack( const ExprASTStack *const ast_stack, @@ -640,6 +642,7 @@ static inline void viml_pexpr_debug_print_ast_stack( } } +REAL_FATTR_UNUSED static inline void viml_pexpr_debug_print_token( const ParserState *const pstate, const LexExprToken token) FUNC_ATTR_ALWAYS_INLINE @@ -794,6 +797,7 @@ static inline ExprASTNode *viml_pexpr_new_node(const ExprASTNodeType type) typedef enum { kEOpLvlInvalid = 0, + kEOpLvlComplexIdentifier, kEOpLvlParens, kEOpLvlArrow, kEOpLvlComma, @@ -806,7 +810,6 @@ typedef enum { kEOpLvlMultiplication, ///< Multiplication, division and modulo. kEOpLvlUnary, ///< Unary operations: not, minus, plus. kEOpLvlSubscript, ///< Subscripts. - kEOpLvlComplexIdentifier, ///< Plain identifier, curly braces name. kEOpLvlValue, ///< Values: literals, variables, nested expressions, … } ExprOpLvl; @@ -843,10 +846,10 @@ static const ExprOpLvl node_type_to_op_lvl[] = { [kExprNodeSubscript] = kEOpLvlSubscript, - [kExprNodeComplexIdentifier] = kEOpLvlComplexIdentifier, - [kExprNodePlainIdentifier] = kEOpLvlComplexIdentifier, [kExprNodeCurlyBracesIdentifier] = kEOpLvlComplexIdentifier, + [kExprNodeComplexIdentifier] = kEOpLvlValue, + [kExprNodePlainIdentifier] = kEOpLvlValue, [kExprNodeRegister] = kEOpLvlValue, [kExprNodeListLiteral] = kEOpLvlValue, }; @@ -884,10 +887,10 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { [kExprNodeSubscript] = kEOpAssLeft, - [kExprNodePlainIdentifier] = kEOpAssLeft, - [kExprNodeComplexIdentifier] = kEOpAssLeft, [kExprNodeCurlyBracesIdentifier] = kEOpAssLeft, + [kExprNodeComplexIdentifier] = kEOpAssLeft, + [kExprNodePlainIdentifier] = kEOpAssNo, [kExprNodeRegister] = kEOpAssNo, [kExprNodeListLiteral] = kEOpAssNo, }; -- cgit From 9e721031d597bfa435da03597939191970f7a918 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 1 Oct 2017 16:50:46 +0300 Subject: viml/parser/expressions: Fix determining invalid commas/colons --- src/nvim/viml/parser/expressions.c | 163 +++++++++++++++++++++++++------------ src/nvim/viml/parser/expressions.h | 8 +- 2 files changed, 117 insertions(+), 54 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 1713d0c89f..c283241cb4 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -13,6 +13,7 @@ #include "nvim/types.h" #include "nvim/charset.h" #include "nvim/ascii.h" +#include "nvim/assert.h" #include "nvim/lib/kvec.h" #include "nvim/viml/parser/expressions.h" @@ -37,6 +38,32 @@ typedef enum { kENodeArgumentSeparator, } ExprASTWantedNode; +/// Operator priority level +typedef enum { + kEOpLvlInvalid = 0, + kEOpLvlComplexIdentifier, + kEOpLvlParens, + kEOpLvlArrow, + kEOpLvlComma, + kEOpLvlColon, + kEOpLvlTernary, + kEOpLvlOr, + kEOpLvlAnd, + kEOpLvlComparison, + kEOpLvlAddition, ///< Addition, subtraction and concatenation. + kEOpLvlMultiplication, ///< Multiplication, division and modulo. + kEOpLvlUnary, ///< Unary operations: not, minus, plus. + kEOpLvlSubscript, ///< Subscripts. + kEOpLvlValue, ///< Values: literals, variables, nested expressions, … +} ExprOpLvl; + +/// Operator associativity +typedef enum { + kEOpAssNo= 'n', ///< Not associative / not applicable. + kEOpAssLeft = 'l', ///< Left associativity. + kEOpAssRight = 'r', ///< Right associativity. +} ExprOpAssociativity; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "viml/parser/expressions.c.generated.h" #endif @@ -747,6 +774,7 @@ static inline void viml_pexpr_debug_print_token( // // NVimParenthesis -> Delimiter // +// NVimColon -> Delimiter // NVimComma -> Delimiter // NVimArrow -> Delimiter // @@ -895,6 +923,32 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { [kExprNodeListLiteral] = kEOpAssNo, }; +/// Get AST node priority level +/// +/// Used primary to reduce line length, so keep the name short. +/// +/// @param[in] node Node to get priority for. +/// +/// @return Node priority level. +static inline ExprOpLvl node_lvl(const ExprASTNode node) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT +{ + return node_type_to_op_lvl[node.type]; +} + +/// Get AST node associativity, to be used for operator nodes primary +/// +/// Used primary to reduce line length, so keep the name short. +/// +/// @param[in] node Node to get priority for. +/// +/// @return Node associativity. +static inline ExprOpAssociativity node_ass(const ExprASTNode node) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT +{ + return node_type_to_op_ass[node.type]; +} + /// Handle binary operator /// /// This function is responsible for handling priority levels as well. @@ -910,20 +964,19 @@ static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, assert(kv_size(*ast_stack)); const ExprOpLvl bop_node_lvl = (bop_node->type == kExprNodeCall ? kEOpLvlSubscript - : node_type_to_op_lvl[bop_node->type]); + : node_lvl(*bop_node)); #ifndef NDEBUG const ExprOpAssociativity bop_node_ass = ( bop_node->type == kExprNodeCall ? kEOpAssLeft - : node_type_to_op_ass[bop_node->type]); + : node_ass(*bop_node)); #endif do { ExprASTNode **new_top_node_p = kv_last(*ast_stack); ExprASTNode *new_top_node = *new_top_node_p; assert(new_top_node != NULL); - const ExprOpLvl new_top_node_lvl = node_type_to_op_lvl[new_top_node->type]; - const ExprOpAssociativity new_top_node_ass = ( - node_type_to_op_ass[new_top_node->type]); + const ExprOpLvl new_top_node_lvl = node_lvl(*new_top_node); + const ExprOpAssociativity new_top_node_ass = node_ass(*new_top_node); assert(bop_node_lvl != new_top_node_lvl || bop_node_ass == new_top_node_ass); if (top_node_p != NULL @@ -1352,32 +1405,31 @@ viml_pexpr_parse_process_token: goto viml_pexpr_parse_invalid_comma; } for (size_t i = 1; i < kv_size(ast_stack); i++) { - const ExprASTNode *const *const eastnode_p = - (const ExprASTNode *const *)kv_Z(ast_stack, i); - if (!((*eastnode_p)->type == kExprNodeComma - || ((*eastnode_p)->type == kExprNodeColon - && i == 1)) - || i == kv_size(ast_stack) - 1) { - switch ((*eastnode_p)->type) { - case kExprNodeLambda: { - assert(want_node == kENodeArgumentSeparator); - break; - } - case kExprNodeDictLiteral: - case kExprNodeListLiteral: - case kExprNodeCall: { - break; - } - default: { + ExprASTNode *const *const eastnode_p = + (ExprASTNode *const *)kv_Z(ast_stack, i); + const ExprASTNodeType eastnode_type = (*eastnode_p)->type; + const ExprOpLvl eastnode_lvl = node_lvl(**eastnode_p); + if (eastnode_type == kExprNodeLambda) { + assert(want_node == kENodeArgumentSeparator); + break; + } else if (eastnode_type == kExprNodeDictLiteral + || eastnode_type == kExprNodeListLiteral + || eastnode_type == kExprNodeCall) { + break; + } else if (eastnode_type == kExprNodeComma + || eastnode_type == kExprNodeColon + || eastnode_lvl > kEOpLvlComma) { + // Do nothing + } else { viml_pexpr_parse_invalid_comma: - ERROR_FROM_TOKEN_AND_MSG( - cur_token, - _("E15: Comma outside of call, lambda or literal: %.*s")); - break; - } - } + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Comma outside of call, lambda or literal: %.*s")); break; } + if (i == kv_size(ast_stack) - 1) { + goto viml_pexpr_parse_invalid_comma; + } } NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComma); viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); @@ -1389,37 +1441,48 @@ viml_pexpr_parse_invalid_comma: if (kv_size(ast_stack) < 2) { goto viml_pexpr_parse_invalid_colon; } + bool is_ternary = false; + bool can_be_ternary = true; for (size_t i = 1; i < kv_size(ast_stack); i++) { ExprASTNode *const *const eastnode_p = (ExprASTNode *const *)kv_Z(ast_stack, i); - if ((*eastnode_p)->type != kExprNodeColon - || i == kv_size(ast_stack) - 1) { - switch ((*eastnode_p)->type) { - case kExprNodeUnknownFigure: { - SELECT_FIGURE_BRACE_TYPE((*eastnode_p), DictLiteral, Dict); - break; - } - case kExprNodeComma: - case kExprNodeDictLiteral: - case kExprNodeTernary: { - break; - } - default: { + const ExprASTNodeType eastnode_type = (*eastnode_p)->type; + const ExprOpLvl eastnode_lvl = node_lvl(**eastnode_p); + STATIC_ASSERT(kEOpLvlTernary > kEOpLvlComma, + "Unexpected operator priorities"); + if (can_be_ternary && eastnode_lvl == kEOpLvlTernary) { + assert(eastnode_type == kExprNodeTernary); + is_ternary = true; + break; + } else if (eastnode_type == kExprNodeUnknownFigure) { + SELECT_FIGURE_BRACE_TYPE(*eastnode_p, DictLiteral, Dict); + break; + } else if (eastnode_type == kExprNodeDictLiteral + || eastnode_type == kExprNodeComma) { + break; + } else if (eastnode_lvl > kEOpLvlTernary) { + // Do nothing + } else if (eastnode_lvl > kEOpLvlComma) { + can_be_ternary = false; + } else { viml_pexpr_parse_invalid_colon: - ERROR_FROM_TOKEN_AND_MSG( - cur_token, - _("E15: Colon outside of dictionary or ternary operator: " - "%.*s")); - break; - } - } + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Colon outside of dictionary or ternary operator: " + "%.*s")); break; } + if (i == kv_size(ast_stack) - 1) { + goto viml_pexpr_parse_invalid_colon; + } } NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); - // FIXME: Handle ternary operator. - HL_CUR_TOKEN(Colon); + if (is_ternary) { + HL_CUR_TOKEN(TernaryColon); + } else { + HL_CUR_TOKEN(Colon); + } want_node = kENodeValue; break; } diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 64abab9e41..01a51e4eda 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -144,10 +144,10 @@ typedef enum { typedef enum { kExprNodeMissing = 'X', kExprNodeOpMissing = '_', - kExprNodeTernary = '?', ///< Ternary operator, valid one has three children. - kExprNodeRegister = '@', ///< Register, no children. - kExprNodeSubscript = 's', ///< Subscript, should have two or three children. - kExprNodeListLiteral = 'l', ///< List literal, any number of children. + kExprNodeTernary = '?', ///< Ternary operator. + kExprNodeRegister = '@', ///< Register. + kExprNodeSubscript = 's', ///< Subscript. + kExprNodeListLiteral = 'l', ///< List literal. kExprNodeUnaryPlus = 'p', kExprNodeBinaryPlus = '+', kExprNodeNested = 'e', ///< Nested parenthesised expression. -- cgit From 6144e26eb920a90b0db22bd7afcac0b9e0734ed6 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 1 Oct 2017 22:35:41 +0300 Subject: viml/parser/expressions: Add support for ternary operator --- src/nvim/viml/parser/expressions.c | 87 ++++++++++++++++++++++---------------- src/nvim/viml/parser/expressions.h | 6 ++- 2 files changed, 56 insertions(+), 37 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index c283241cb4..41c77c5c88 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -46,6 +46,7 @@ typedef enum { kEOpLvlArrow, kEOpLvlComma, kEOpLvlColon, + kEOpLvlTernaryValue, kEOpLvlTernary, kEOpLvlOr, kEOpLvlAnd, @@ -770,7 +771,8 @@ static inline void viml_pexpr_debug_print_token( // NVimUnaryOperator -> NVimOperator // NVimBinaryOperator -> NVimOperator // NVimComparisonOperator -> NVimOperator -// NVimTernaryOperator -> NVimOperator +// NVimTernary -> NVimOperator +// NVimTernaryColon -> NVimTernary // // NVimParenthesis -> Delimiter // @@ -790,7 +792,8 @@ static inline void viml_pexpr_debug_print_token( // // NVimInvalidComma -> NVimInvalidDelimiter // NVimInvalidSpacing -> NVimInvalid -// NVimInvalidTernaryOperator -> NVimInvalidOperator +// NVimInvalidTernary -> NVimInvalidOperator +// NVimInvalidTernaryColon -> NVimInvalidTernary // NVimInvalidRegister -> NVimInvalidValue // NVimInvalidClosingBracket -> NVimInvalidDelimiter // NVimInvalidSpacing -> NVimInvalid @@ -823,30 +826,6 @@ static inline ExprASTNode *viml_pexpr_new_node(const ExprASTNodeType type) return ret; } -typedef enum { - kEOpLvlInvalid = 0, - kEOpLvlComplexIdentifier, - kEOpLvlParens, - kEOpLvlArrow, - kEOpLvlComma, - kEOpLvlColon, - kEOpLvlTernary, - kEOpLvlOr, - kEOpLvlAnd, - kEOpLvlComparison, - kEOpLvlAddition, ///< Addition, subtraction and concatenation. - kEOpLvlMultiplication, ///< Multiplication, division and modulo. - kEOpLvlUnary, ///< Unary operations: not, minus, plus. - kEOpLvlSubscript, ///< Subscripts. - kEOpLvlValue, ///< Values: literals, variables, nested expressions, … -} ExprOpLvl; - -typedef enum { - kEOpAssNo= 'n', ///< Not associative / not applicable. - kEOpAssLeft = 'l', ///< Left associativity. - kEOpAssRight = 'r', ///< Right associativity. -} ExprOpAssociativity; - static const ExprOpLvl node_type_to_op_lvl[] = { [kExprNodeMissing] = kEOpLvlInvalid, [kExprNodeOpMissing] = kEOpLvlMultiplication, @@ -868,6 +847,8 @@ static const ExprOpLvl node_type_to_op_lvl[] = { [kExprNodeTernary] = kEOpLvlTernary, + [kExprNodeTernaryValue] = kEOpLvlTernaryValue, + [kExprNodeBinaryPlus] = kEOpLvlAddition, [kExprNodeUnaryPlus] = kEOpLvlUnary, @@ -907,7 +888,9 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { // about associativity, only about order of execution. [kExprNodeComma] = kEOpAssRight, - [kExprNodeTernary] = kEOpAssNo, + [kExprNodeTernary] = kEOpAssRight, + + [kExprNodeTernaryValue] = kEOpAssRight, [kExprNodeBinaryPlus] = kEOpAssLeft, @@ -1450,9 +1433,20 @@ viml_pexpr_parse_invalid_comma: const ExprOpLvl eastnode_lvl = node_lvl(**eastnode_p); STATIC_ASSERT(kEOpLvlTernary > kEOpLvlComma, "Unexpected operator priorities"); - if (can_be_ternary && eastnode_lvl == kEOpLvlTernary) { - assert(eastnode_type == kExprNodeTernary); + if (can_be_ternary && eastnode_type == kExprNodeTernaryValue + && !(*eastnode_p)->data.ter.got_colon) { + kv_drop(ast_stack, i); + (*eastnode_p)->start = cur_token.start; + (*eastnode_p)->len = cur_token.len; + if (prev_token.type == kExprLexSpacing) { + (*eastnode_p)->start = prev_token.start; + (*eastnode_p)->len += prev_token.len; + } is_ternary = true; + (*eastnode_p)->data.ter.got_colon = true; + assert((*eastnode_p)->children != NULL); + assert((*eastnode_p)->children->next == NULL); + kvi_push(ast_stack, &(*eastnode_p)->children->next); break; } else if (eastnode_type == kExprNodeUnknownFigure) { SELECT_FIGURE_BRACE_TYPE(*eastnode_p, DictLiteral, Dict); @@ -1460,7 +1454,7 @@ viml_pexpr_parse_invalid_comma: } else if (eastnode_type == kExprNodeDictLiteral || eastnode_type == kExprNodeComma) { break; - } else if (eastnode_lvl > kEOpLvlTernary) { + } else if (eastnode_lvl >= kEOpLvlTernaryValue) { // Do nothing } else if (eastnode_lvl > kEOpLvlComma) { can_be_ternary = false; @@ -1476,11 +1470,11 @@ viml_pexpr_parse_invalid_colon: goto viml_pexpr_parse_invalid_colon; } } - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); - viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); if (is_ternary) { HL_CUR_TOKEN(TernaryColon); } else { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); HL_CUR_TOKEN(Colon); } want_node = kENodeValue; @@ -1683,8 +1677,6 @@ viml_pexpr_parse_figure_brace_closing_error: cur_token.len - scope_shift, HL(Identifier)); } - // FIXME: Actually, g{foo}g:foo is valid: "1?g{foo}g:foo" is like - // "g{foo}g" and not an error. } else { if (cur_token.data.var.scope == 0) { ADD_IDENT( @@ -1792,6 +1784,21 @@ viml_pexpr_parse_no_paren_closing_error: {} } break; } + case kExprLexQuestion: { + ADD_VALUE_IF_MISSING(_("E15: Expected value, got question mark: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeTernary); + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); + HL_CUR_TOKEN(Ternary); + ExprASTNode *ter_val_node; + NEW_NODE_WITH_CUR_POS(ter_val_node, kExprNodeTernaryValue); + ter_val_node->data.ter.got_colon = false; + assert(cur_node->children != NULL); + assert(cur_node->children->next == NULL); + assert(kv_last(ast_stack) == &cur_node->children->next); + *kv_last(ast_stack) = ter_val_node; + kvi_push(ast_stack, &ter_val_node->children); + break; + } } viml_pexpr_parse_cycle_end: prev_token = cur_token; @@ -1815,6 +1822,7 @@ viml_pexpr_parse_end: const ExprASTNode *const cur_node = (*kv_pop(ast_stack)); // This should only happen when want_node == kENodeValue. assert(cur_node != NULL); + // TODO(ZyX-I): Rehighlight as invalid? switch (cur_node->type) { case kExprNodeOpMissing: case kExprNodeMissing: { @@ -1822,7 +1830,6 @@ viml_pexpr_parse_end: break; } case kExprNodeCall: { - // TODO(ZyX-I): Rehighlight as invalid? east_set_error( &ast, pstate, _("E116: Missing closing parenthesis for function call: %.*s"), @@ -1830,7 +1837,6 @@ viml_pexpr_parse_end: break; } case kExprNodeNested: { - // TODO(ZyX-I): Rehighlight as invalid? east_set_error( &ast, pstate, _("E110: Missing closing parenthesis for nested expression" @@ -1844,6 +1850,15 @@ viml_pexpr_parse_end: // It is OK to see these in the stack. break; } + case kExprNodeTernaryValue: { + if (!cur_node->data.ter.got_colon) { + // Actually Vim throws E109 in more cases. + east_set_error( + &ast, pstate, _("E109: Missing ':' after '?': %.*s"), + cur_node->start); + } + break; + } // TODO(ZyX-I): handle other values } } diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 01a51e4eda..cf0907850a 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -145,6 +145,7 @@ typedef enum { kExprNodeMissing = 'X', kExprNodeOpMissing = '_', kExprNodeTernary = '?', ///< Ternary operator. + kExprNodeTernaryValue = 'C', ///< Ternary operator, colon. kExprNodeRegister = '@', ///< Register. kExprNodeSubscript = 's', ///< Subscript. kExprNodeListLiteral = 'l', ///< List literal. @@ -209,7 +210,10 @@ struct expr_ast_node { /// Points to inside parser reader state. const char *ident; size_t ident_len; ///< Actual identifier length. - } var; + } var; ///< For kExprNodePlainIdentifier. + struct { + bool got_colon; ///< True if colon was seen. + } ter; ///< For kExprNodeTernaryValue. } data; }; -- cgit From 6791c574209c83570746c139d93f8e6a6b9cd135 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 2 Oct 2017 01:22:35 +0300 Subject: viml/parser/expressions: Make sure that arrows outside lambda throw --- src/nvim/viml/parser/expressions.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 41c77c5c88..982465055e 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -1642,8 +1642,9 @@ viml_pexpr_parse_figure_brace_closing_error: lambda_node = NULL; } else { // Only first branch is valid. - is_invalid = true; ADD_VALUE_IF_MISSING(_("E15: Unexpected arrow: %.*s")); + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Arrow outside of lambda: %.*s")); NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeArrow); viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); } -- cgit From 6168e1127c1c80a3810854649b0776146545043b Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 2 Oct 2017 02:41:55 +0300 Subject: viml/parser/expressions: Add support for comparison operators --- src/nvim/viml/parser/expressions.c | 131 ++++++++++++++++++++++++++----------- src/nvim/viml/parser/expressions.h | 45 ++++++++----- 2 files changed, 122 insertions(+), 54 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 982465055e..8e6f991e03 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -102,7 +102,7 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) if (ret.len < pline.size \ && strchr("?#", pline.data[ret.len]) != NULL) { \ ret.data.cmp.ccs = \ - (CaseCompareStrategy)pline.data[ret.len]; \ + (ExprCaseCompareStrategy)pline.data[ret.len]; \ ret.len++; \ } else { \ ret.data.cmp.ccs = kCCStrategyUseOption; \ @@ -240,7 +240,7 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) && ((ret.len == 2 && memcmp(pline.data, "is", 2) == 0) || (ret.len == 5 && memcmp(pline.data, "isnot", 5) == 0))) { ret.type = kExprLexComparison; - ret.data.cmp.type = kExprLexCmpIdentical; + ret.data.cmp.type = kExprCmpIdentical; ret.data.cmp.inv = (ret.len == 5); GET_CCS(ret, pline); // Scope: `s:`, etc. @@ -381,10 +381,10 @@ viml_pexpr_next_token_invalid_comparison: ret.type = kExprLexComparison; ret.data.cmp.inv = (schar == '!'); if (pline.data[1] == '=') { - ret.data.cmp.type = kExprLexCmpEqual; + ret.data.cmp.type = kExprCmpEqual; ret.len++; } else if (pline.data[1] == '~') { - ret.data.cmp.type = kExprLexCmpMatches; + ret.data.cmp.type = kExprCmpMatches; ret.len++; } else { goto viml_pexpr_next_token_invalid_comparison; @@ -404,8 +404,8 @@ viml_pexpr_next_token_invalid_comparison: GET_CCS(ret, pline); ret.data.cmp.inv = (schar == '<'); ret.data.cmp.type = ((ret.data.cmp.inv ^ haseqsign) - ? kExprLexCmpGreaterOrEqual - : kExprLexCmpGreater); + ? kExprCmpGreaterOrEqual + : kExprCmpGreater); break; } @@ -503,11 +503,11 @@ static const char *const eltkn_type_tab[] = { }; static const char *const eltkn_cmp_type_tab[] = { - [kExprLexCmpEqual] = "Equal", - [kExprLexCmpMatches] = "Matches", - [kExprLexCmpGreater] = "Greater", - [kExprLexCmpGreaterOrEqual] = "GreaterOrEqual", - [kExprLexCmpIdentical] = "Identical", + [kExprCmpEqual] = "Equal", + [kExprCmpMatches] = "Matches", + [kExprCmpGreater] = "Greater", + [kExprCmpGreaterOrEqual] = "GreaterOrEqual", + [kExprCmpIdentical] = "Identical", }; static const char *const ccs_tab[] = { @@ -770,7 +770,8 @@ static inline void viml_pexpr_debug_print_token( // NVimOperator -> Operator // NVimUnaryOperator -> NVimOperator // NVimBinaryOperator -> NVimOperator -// NVimComparisonOperator -> NVimOperator +// NVimComparisonOperator -> NVimBinaryOperator +// NVimComparisonOperatorModifier -> NVimComparisonOperator // NVimTernary -> NVimOperator // NVimTernaryColon -> NVimTernary // @@ -805,6 +806,8 @@ static inline void viml_pexpr_debug_print_token( // NVimInvalidIdentifier -> NVimInvalidValue // NVimInvalidIdentifierScope -> NVimInvalidValue // NVimInvalidIdentifierScopeDelimiter -> NVimInvalidValue +// NVimInvalidComparisonOperator -> NVimInvalidOperator +// NVimInvalidComparisonOperatorModifier -> NVimInvalidComparisonOperator // // NVimUnaryPlus -> NVimUnaryOperator // NVimBinaryPlus -> NVimBinaryOperator @@ -849,6 +852,8 @@ static const ExprOpLvl node_type_to_op_lvl[] = { [kExprNodeTernaryValue] = kEOpLvlTernaryValue, + [kExprNodeComparison] = kEOpLvlComparison, + [kExprNodeBinaryPlus] = kEOpLvlAddition, [kExprNodeUnaryPlus] = kEOpLvlUnary, @@ -892,6 +897,8 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { [kExprNodeTernaryValue] = kEOpAssRight, + [kExprNodeComparison] = kEOpAssRight, + [kExprNodeBinaryPlus] = kEOpAssLeft, [kExprNodeUnaryPlus] = kEOpAssNo, @@ -935,11 +942,23 @@ static inline ExprOpAssociativity node_ass(const ExprASTNode node) /// Handle binary operator /// /// This function is responsible for handling priority levels as well. -static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, +/// +/// @param[in] pstate Parser state, used for error reporting. +/// @param ast_stack AST stack. May be popped of some values and will +/// definitely receive new ones. +/// @param bop_node New node to handle. +/// @param[out] want_node_p New value of want_node. +/// @param[out] ast_err Location where error is saved, if any. +/// +/// @return True if no errors occurred, false otherwise. +static bool viml_pexpr_handle_bop(const ParserState *const pstate, + ExprASTStack *const ast_stack, ExprASTNode *const bop_node, - ExprASTWantedNode *const want_node_p) + ExprASTWantedNode *const want_node_p, + ExprASTError *const ast_err) FUNC_ATTR_NONNULL_ALL { + bool ret = true; ExprASTNode **top_node_p = NULL; ExprASTNode *top_node; ExprOpLvl top_node_lvl; @@ -977,7 +996,6 @@ static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, break; } } while (kv_size(*ast_stack)); - // FIXME: Handle no associativity if (top_node_ass == kEOpAssLeft || top_node_lvl != bop_node_lvl) { // outer(op(x,y)) -> outer(new_op(op(x,y),*)) // @@ -1008,10 +1026,18 @@ static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, kvi_push(*ast_stack, top_node_p); kvi_push(*ast_stack, &top_node->children->next); kvi_push(*ast_stack, &bop_node->children->next); + // TODO(ZyX-I): Make this not error, but treat like Python does + if (bop_node->type == kExprNodeComparison) { + east_set_error(pstate, ast_err, + _("E15: Operator is not associative: %.*s"), + bop_node->start); + ret = false; + } } *want_node_p = (*want_node_p == kENodeArgumentSeparator ? kENodeArgument : kENodeValue); + return ret; } /// ParserPosition literal based on ParserPosition pos with columns shifted @@ -1074,6 +1100,13 @@ static inline ParserPosition shifted_pos(const ParserPosition pos, #define MAY_HAVE_NEXT_EXPR \ (kv_size(ast_stack) == 1) +/// Add operator node +/// +/// @param[in] cur_node Node to add. +#define ADD_OP_NODE(cur_node) \ + is_invalid |= !viml_pexpr_handle_bop(pstate, &ast_stack, cur_node, \ + &want_node, &ast.err) + /// Record missing operator: for things like /// /// :echo @a @a @@ -1094,7 +1127,7 @@ static inline ParserPosition shifted_pos(const ParserPosition pos, ERROR_FROM_TOKEN_AND_MSG(cur_token, _("E15: Missing operator: %.*s")); \ NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeOpMissing); \ cur_node->len = 0; \ - viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); \ + ADD_OP_NODE(cur_node); \ goto viml_pexpr_parse_process_token; \ } \ } while (0) @@ -1120,34 +1153,33 @@ static inline ParserPosition shifted_pos(const ParserPosition pos, /// @param[in] msg Error message, assumed to be already translated and /// containing a single %token "%.*s". /// @param[in] start Position at which error occurred. -static inline void east_set_error(ExprAST *const ret_ast, - const ParserState *const pstate, +static inline void east_set_error(const ParserState *const pstate, + ExprASTError *const ret_ast_err, const char *const msg, const ParserPosition start) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE { - if (!ret_ast->correct) { + if (ret_ast_err->msg != NULL) { return; } const ParserLine pline = pstate->reader.lines.items[start.line]; - ret_ast->correct = false; - ret_ast->err.msg = msg; - ret_ast->err.arg_len = (int)(pline.size - start.col); - ret_ast->err.arg = pline.data + start.col; + ret_ast_err->msg = msg; + ret_ast_err->arg_len = (int)(pline.size - start.col); + ret_ast_err->arg = pline.data + start.col; } /// Set error from the given token and given message #define ERROR_FROM_TOKEN_AND_MSG(cur_token, msg) \ do { \ is_invalid = true; \ - east_set_error(&ast, pstate, msg, cur_token.start); \ + east_set_error(pstate, &ast.err, msg, cur_token.start); \ } while (0) /// Like #ERROR_FROM_TOKEN_AND_MSG, but gets position from a node #define ERROR_FROM_NODE_AND_MSG(node, msg) \ do { \ is_invalid = true; \ - east_set_error(&ast, pstate, msg, node->start); \ + east_set_error(pstate, &ast.err, msg, node->start); \ } while (0) /// Set error from the given kExprLexInvalid token @@ -1231,7 +1263,6 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { ExprAST ast = { - .correct = true, .err = { .msg = NULL, .arg_len = 0, @@ -1359,12 +1390,38 @@ viml_pexpr_parse_process_token: HL_CUR_TOKEN(UnaryPlus); } else { NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeBinaryPlus); - viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); + ADD_OP_NODE(cur_node); HL_CUR_TOKEN(BinaryPlus); } want_node = kENodeValue; break; } + case kExprLexComparison: { + ADD_VALUE_IF_MISSING( + _("E15: Expected value, got comparison operator: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComparison); + if (cur_token.type == kExprLexInvalid) { + cur_node->data.cmp.ccs = kCCStrategyUseOption; + cur_node->data.cmp.type = kExprCmpEqual; + cur_node->data.cmp.inv = false; + } else { + cur_node->data.cmp.ccs = cur_token.data.cmp.ccs; + cur_node->data.cmp.type = cur_token.data.cmp.type; + cur_node->data.cmp.inv = cur_token.data.cmp.inv; + } + ADD_OP_NODE(cur_node); + if (cur_token.data.cmp.ccs != kCCStrategyUseOption) { + viml_parser_highlight(pstate, cur_token.start, cur_token.len - 1, + HL(ComparisonOperator)); + viml_parser_highlight( + pstate, shifted_pos(cur_token.start, cur_token.len - 1), 1, + HL(ComparisonOperatorModifier)); + } else { + HL_CUR_TOKEN(ComparisonOperator); + } + want_node = kENodeValue; + break; + } case kExprLexComma: { assert(want_node != kENodeArgument); if (want_node == kENodeValue) { @@ -1415,7 +1472,7 @@ viml_pexpr_parse_invalid_comma: } } NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComma); - viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); + ADD_OP_NODE(cur_node); HL_CUR_TOKEN(Comma); break; } @@ -1474,7 +1531,7 @@ viml_pexpr_parse_invalid_colon: HL_CUR_TOKEN(TernaryColon); } else { NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); - viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); + ADD_OP_NODE(cur_node); HL_CUR_TOKEN(Colon); } want_node = kENodeValue; @@ -1646,7 +1703,7 @@ viml_pexpr_parse_figure_brace_closing_error: ERROR_FROM_TOKEN_AND_MSG( cur_token, _("E15: Arrow outside of lambda: %.*s")); NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeArrow); - viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); + ADD_OP_NODE(cur_node); } want_node = kENodeValue; HL_CUR_TOKEN(Arrow); @@ -1775,7 +1832,7 @@ viml_pexpr_parse_no_paren_closing_error: {} } } NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeCall); - viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); + ADD_OP_NODE(cur_node); HL_CUR_TOKEN(CallingParenthesis); } else { // Currently it is impossible to reach this. @@ -1788,7 +1845,7 @@ viml_pexpr_parse_no_paren_closing_error: {} case kExprLexQuestion: { ADD_VALUE_IF_MISSING(_("E15: Expected value, got question mark: %.*s")); NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeTernary); - viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); + ADD_OP_NODE(cur_node); HL_CUR_TOKEN(Ternary); ExprASTNode *ter_val_node; NEW_NODE_WITH_CUR_POS(ter_val_node, kExprNodeTernaryValue); @@ -1808,7 +1865,7 @@ viml_pexpr_parse_cycle_end: } while (true); viml_pexpr_parse_end: if (want_node == kENodeValue) { - east_set_error(&ast, pstate, _("E15: Expected value, got EOC: %.*s"), + east_set_error(pstate, &ast.err, _("E15: Expected value, got EOC: %.*s"), pstate->pos); } else if (kv_size(ast_stack) != 1) { // Something may be wrong, check whether it really is. @@ -1819,7 +1876,7 @@ viml_pexpr_parse_end: // Topmost stack item must be a *finished* value, so it must not be // analyzed. E.g. it may contain an already finished nested expression. kv_drop(ast_stack, 1); - while (ast.correct && kv_size(ast_stack)) { + while (ast.err.msg == NULL && kv_size(ast_stack)) { const ExprASTNode *const cur_node = (*kv_pop(ast_stack)); // This should only happen when want_node == kENodeValue. assert(cur_node != NULL); @@ -1832,14 +1889,14 @@ viml_pexpr_parse_end: } case kExprNodeCall: { east_set_error( - &ast, pstate, + pstate, &ast.err, _("E116: Missing closing parenthesis for function call: %.*s"), cur_node->start); break; } case kExprNodeNested: { east_set_error( - &ast, pstate, + pstate, &ast.err, _("E110: Missing closing parenthesis for nested expression" ": %.*s"), cur_node->start); @@ -1855,7 +1912,7 @@ viml_pexpr_parse_end: if (!cur_node->data.ter.got_colon) { // Actually Vim throws E109 in more cases. east_set_error( - &ast, pstate, _("E109: Missing ':' after '?': %.*s"), + pstate, &ast.err, _("E109: Missing ':' after '?': %.*s"), cur_node->start); } break; diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index cf0907850a..8ca3ceacb9 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -16,7 +16,7 @@ typedef enum { kCCStrategyUseOption = 0, // 0 for xcalloc kCCStrategyMatchCase = '#', kCCStrategyIgnoreCase = '?', -} CaseCompareStrategy; +} ExprCaseCompareStrategy; /// Lexer token type typedef enum { @@ -52,6 +52,14 @@ typedef enum { kExprLexArrow, ///< Arrow, like from lambda expressions. } LexExprTokenType; +typedef enum { + kExprCmpEqual, ///< Equality, unequality. + kExprCmpMatches, ///< Matches regex, not matches regex. + kExprCmpGreater, ///< `>` or `<=` + kExprCmpGreaterOrEqual, ///< `>=` or `<`. + kExprCmpIdentical, ///< `is` or `isnot` +} ExprComparisonType; + /// Lexer token typedef struct { ParserPosition start; @@ -59,14 +67,8 @@ typedef struct { LexExprTokenType type; union { struct { - enum { - kExprLexCmpEqual, ///< Equality, unequality. - kExprLexCmpMatches, ///< Matches regex, not matches regex. - kExprLexCmpGreater, ///< `>` or `<=` - kExprLexCmpGreaterOrEqual, ///< `>=` or `<`. - kExprLexCmpIdentical, ///< `is` or `isnot` - } type; ///< Comparison type. - CaseCompareStrategy ccs; ///< Case comparison strategy. + ExprComparisonType type; ///< Comparison type. + ExprCaseCompareStrategy ccs; ///< Case comparison strategy. bool inv; ///< True if comparison is to be inverted. } cmp; ///< For kExprLexComparison. @@ -171,6 +173,7 @@ typedef enum { kExprNodeComma = ',', ///< Comma “operator”. kExprNodeColon = ':', ///< Colon “operator”. kExprNodeArrow = '>', ///< Arrow “operator”. + kExprNodeComparison = '=', ///< Various comparison operators. } ExprASTNodeType; typedef struct expr_ast_node ExprASTNode; @@ -214,6 +217,11 @@ struct expr_ast_node { struct { bool got_colon; ///< True if colon was seen. } ter; ///< For kExprNodeTernaryValue. + struct { + ExprComparisonType type; ///< Comparison type. + ExprCaseCompareStrategy ccs; ///< Case comparison strategy. + bool inv; ///< True if comparison is to be inverted. + } cmp; ///< For kExprNodeComparison. } data; }; @@ -235,19 +243,22 @@ enum { // viml_expressions_parser.c. } ExprParserFlags; +/// AST error definition +typedef struct { + /// Error message. Must contain a single printf format atom: %.*s. + const char *msg; + /// Error message argument: points to the location of the error. + const char *arg; + /// Message argument length: length till the end of string. + int arg_len; +} ExprASTError; + /// Structure representing complety AST for one expression typedef struct { - /// True if represented AST is correct and can be executed. Incorrect ones may - /// still be used for completion, or in linters. - bool correct; /// When AST is not correct this message will be printed. /// /// Uses `emsgf(msg, arg_len, arg);`, `msg` is assumed to contain only `%.*s`. - struct { - const char *msg; - int arg_len; - const char *arg; - } err; + ExprASTError err; /// Root node of the AST. ExprASTNode *root; } ExprAST; -- cgit From 0bc4e2237960712426da3774c1430f5874c49aea Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 3 Oct 2017 00:39:40 +0300 Subject: viml/parser/expressions: Forbid dot or alpha characters after a float This is basically what Vim already does, in addition to forbidding floats should there be a concat immediately before it. --- src/nvim/viml/parser/expressions.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 8e6f991e03..8c95d1db14 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -186,6 +186,7 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) ret.data.num.is_float = false; CHARREG(kExprLexNumber, ascii_isdigit); if (flags & kELFlagAllowFloat) { + const LexExprToken non_float_ret = ret; if (pline.size > ret.len + 1 && pline.data[ret.len] == '.' && ascii_isdigit(pline.data[ret.len + 1])) { @@ -207,6 +208,11 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) CHARREG(kExprLexNumber, ascii_isdigit); } } + if (pline.size > ret.len + && (pline.data[ret.len] == '.' + || ASCII_ISALPHA(pline.data[ret.len]))) { + ret = non_float_ret; + } } break; } -- cgit From 163792e9b9854fe046ada3233dec0fd0f6c55737 Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 6 Oct 2017 01:19:43 +0300 Subject: viml/parser/expressions: Make lexer parse numbers, support non-decimal --- src/nvim/viml/parser/expressions.c | 146 ++++++++++++++++++++++++++++++++++--- src/nvim/viml/parser/expressions.h | 6 ++ 2 files changed, 141 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 8c95d1db14..5d892fb8f8 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -15,10 +15,13 @@ #include "nvim/ascii.h" #include "nvim/assert.h" #include "nvim/lib/kvec.h" +#include "nvim/eval/typval.h" #include "nvim/viml/parser/expressions.h" #include "nvim/viml/parser/parser.h" +#define vim_str2nr(s, ...) vim_str2nr((const char_u *)(s), __VA_ARGS__) + typedef kvec_withinit_t(ExprASTNode **, 16) ExprASTStack; /// Which nodes may be wanted @@ -72,6 +75,43 @@ typedef enum { /// Character used as a separator in autoload function/variable names. #define AUTOLOAD_CHAR '#' +/// Scale number by a given factor +/// +/// Used to apply exponent to a number. Idea taken from uClibc. +/// +/// @param[in] num Number to scale. Does not bother doing anything if it is +/// zero. +/// @param[in] base Base, should be 10 since non-decimal floating-point +/// numbers are not supported. +/// @param[in] exponent Exponent to scale by. +/// @param[in] exponent_negative True if exponent is negative. +static inline float_T scale_number(const float_T num, + const uint8_t base, + const uvarnumber_T exponent, + const bool exponent_negative) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_CONST +{ + if (num == 0 || exponent == 0) { + return num; + } + assert(base); + uvarnumber_T exp = exponent; + float_T p_base = (float_T)base; + float_T ret = num; + while (exp) { + if (exp & 1) { + if (exponent_negative) { + ret /= p_base; + } else { + ret *= p_base; + } + } + exp >>= 1; + p_base *= p_base; + } + return ret; +} + /// Get next token for the VimL expression input /// /// @param pstate Parser state. @@ -184,6 +224,11 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { ret.data.num.is_float = false; + ret.data.num.base = 10; + size_t frac_start = 0; + size_t exp_start = 0; + size_t frac_end = 0; + bool exp_negative = false; CHARREG(kExprLexNumber, ascii_isdigit); if (flags & kELFlagAllowFloat) { const LexExprToken non_float_ret = ret; @@ -191,8 +236,18 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) && pline.data[ret.len] == '.' && ascii_isdigit(pline.data[ret.len + 1])) { ret.len++; + frac_start = ret.len; + frac_end = ret.len; ret.data.num.is_float = true; - CHARREG(kExprLexNumber, ascii_isdigit); + for (; ret.len < pline.size && ascii_isdigit(pline.data[ret.len]) + ; ret.len++) { + // A small optimization: trailing zeroes in fractional part do not + // add anything to significand, so it is useless to include them in + // frac_end. + if (pline.data[ret.len] != '0') { + frac_end = ret.len + 1; + } + } if (pline.size > ret.len + 1 && (pline.data[ret.len] == 'e' || pline.data[ret.len] == 'E') @@ -202,9 +257,11 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) && ascii_isdigit(pline.data[ret.len + 2])) || ascii_isdigit(pline.data[ret.len + 1]))) { ret.len++; - if (pline.data[ret.len] == '+' || pline.data[ret.len] == '-') { + if (pline.data[ret.len] == '+' + || (exp_negative = (pline.data[ret.len] == '-'))) { ret.len++; } + exp_start = ret.len; CHARREG(kExprLexNumber, ascii_isdigit); } } @@ -214,6 +271,58 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) ret = non_float_ret; } } + // TODO(ZyX-I): detect overflows + if (ret.data.num.is_float) { + // Vim used to use string2float here which in turn uses strtod(). There + // are two problems with this approach: + // 1. strtod() is locale-dependent. Not sure how it is worked around so + // that I do not see relevant bugs, but it still does not look like + // a good idea. + // 2. strtod() does not accept length argument. + // + // The below variant of parsing floats was recognized as acceptable + // because it is basically how uClibc does the thing: it generates + // a number ignoring decimal point (but recording its position), then + // uses recorded position to scale number down when processing exponent. + float_T significand_part = 0; + uvarnumber_T exp_part = 0; + const size_t frac_size = (size_t)(frac_end - frac_start); + for (size_t i = 0; i < frac_end; i++) { + if (i == frac_start - 1) { + continue; + } + significand_part = significand_part * 10 + (pline.data[i] - '0'); + } + if (exp_start) { + vim_str2nr(pline.data + exp_start, NULL, NULL, 0, NULL, &exp_part, + (int)(ret.len - exp_start)); + } + if (exp_negative) { + exp_part += frac_size; + } else { + if (exp_part < frac_size) { + exp_negative = true; + exp_part = frac_size - exp_part; + } else { + exp_part -= frac_size; + } + } + ret.data.num.val.floating = scale_number(significand_part, 10, exp_part, + exp_negative); + } else { + int len; + int prep; + vim_str2nr(pline.data, &prep, &len, STR2NR_ALL, NULL, + &ret.data.num.val.integer, (int)pline.size); + ret.len = (size_t)len; + const uint8_t bases[] = { + [0] = 10, + ['0'] = 8, + ['x'] = 16, ['X'] = 16, + ['b'] = 2, ['B'] = 2, + }; + ret.data.num.base = bases[prep]; + } break; } @@ -474,7 +583,6 @@ viml_pexpr_next_token_adv_return: return ret; } -#ifdef UNIT_TESTING static const char *const eltkn_type_tab[] = { [kExprLexInvalid] = "Invalid", [kExprLexMissing] = "Missing", @@ -617,7 +725,12 @@ const char *viml_pexpr_repr_token(const ParserState *const pstate, (int)token.data.opt.len, token.data.opt.name) TKNARGS(kExprLexPlainIdentifier, "(scope=%s,autoload=%i)", intchar2str(token.data.var.scope), (int)token.data.var.autoload) - TKNARGS(kExprLexNumber, "(is_float=%i)", (int)token.data.num.is_float) + TKNARGS(kExprLexNumber, "(is_float=%i,base=%i,val=%lg)", + (int)token.data.num.is_float, + (int)token.data.num.base, + (double)(token.data.num.is_float + ? token.data.num.val.floating + : token.data.num.val.integer)) TKNARGS(kExprLexInvalid, "(msg=%s)", token.data.err.msg) default: { // No additional arguments. @@ -642,7 +755,6 @@ viml_pexpr_repr_token_end: } return ret; } -#endif #ifdef UNIT_TESTING #include @@ -776,8 +888,10 @@ static inline void viml_pexpr_debug_print_token( // NVimOperator -> Operator // NVimUnaryOperator -> NVimOperator // NVimBinaryOperator -> NVimOperator +// // NVimComparisonOperator -> NVimBinaryOperator // NVimComparisonOperatorModifier -> NVimComparisonOperator +// // NVimTernary -> NVimOperator // NVimTernaryColon -> NVimTernary // @@ -795,8 +909,21 @@ static inline void viml_pexpr_debug_print_token( // NVimIdentifierScope -> NVimIdentifier // NVimIdentifierScopeDelimiter -> NVimIdentifier // +// NVimIdentifierKey -> Identifier +// // NVimFigureBrace -> NVimInternalError // +// NVimUnaryPlus -> NVimUnaryOperator +// NVimBinaryPlus -> NVimBinaryOperator +// NVimConcatOrSubscript -> NVimBinaryOperator +// +// NVimRegister -> SpecialChar +// NVimNumber -> Number +// NVimFloat -> NVimNumber +// +// NVimNestingParenthesis -> NVimParenthesis +// NVimCallingParenthesis -> NVimParenthesis +// // NVimInvalidComma -> NVimInvalidDelimiter // NVimInvalidSpacing -> NVimInvalid // NVimInvalidTernary -> NVimInvalidOperator @@ -814,12 +941,9 @@ static inline void viml_pexpr_debug_print_token( // NVimInvalidIdentifierScopeDelimiter -> NVimInvalidValue // NVimInvalidComparisonOperator -> NVimInvalidOperator // NVimInvalidComparisonOperatorModifier -> NVimInvalidComparisonOperator -// -// NVimUnaryPlus -> NVimUnaryOperator -// NVimBinaryPlus -> NVimBinaryOperator -// NVimRegister -> SpecialChar -// NVimNestingParenthesis -> NVimParenthesis -// NVimCallingParenthesis -> NVimParenthesis +// NVimInvalidNumber -> NVimInvalidValue +// NVimInvalidFloat -> NVimInvalidValue +// NVimInvalidIdentifierKey -> NVimInvalidIdentifier /// Allocate a new node and set some of the values /// diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 8ca3ceacb9..29903490bb 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -7,6 +7,7 @@ #include "nvim/types.h" #include "nvim/viml/parser/parser.h" +#include "nvim/eval/typval.h" // Defines whether to ignore case: // == kCCStrategyUseOption @@ -113,6 +114,11 @@ typedef struct { } err; ///< For kExprLexInvalid struct { + union { + float_T floating; + uvarnumber_T integer; + } val; ///< Number value. + uint8_t base; ///< Base: 2, 8, 10 or 16. bool is_float; ///< True if number is a floating-point. } num; ///< For kExprLexNumber } data; ///< Additional data, if needed. -- cgit From 21a5ce033c5a853bed3204ea9f0f7a3cfc1d164f Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 3 Oct 2017 01:30:02 +0300 Subject: viml/parser/expressions: Add support for the dot operator and numbers --- src/nvim/viml/parser/expressions.c | 108 ++++++++++++++++++++++++++++++++++--- src/nvim/viml/parser/expressions.h | 23 +++++++- 2 files changed, 122 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 5d892fb8f8..4babf4312c 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -915,7 +915,8 @@ static inline void viml_pexpr_debug_print_token( // // NVimUnaryPlus -> NVimUnaryOperator // NVimBinaryPlus -> NVimBinaryOperator -// NVimConcatOrSubscript -> NVimBinaryOperator +// NVimConcat -> NVimBinaryOperator +// NVimConcatOrSubscript -> NVimConcat // // NVimRegister -> SpecialChar // NVimNumber -> Number @@ -971,6 +972,7 @@ static const ExprOpLvl node_type_to_op_lvl[] = { [kExprNodeUnknownFigure] = kEOpLvlParens, [kExprNodeLambda] = kEOpLvlParens, [kExprNodeDictLiteral] = kEOpLvlParens, + [kExprNodeListLiteral] = kEOpLvlParens, [kExprNodeArrow] = kEOpLvlArrow, @@ -985,17 +987,21 @@ static const ExprOpLvl node_type_to_op_lvl[] = { [kExprNodeComparison] = kEOpLvlComparison, [kExprNodeBinaryPlus] = kEOpLvlAddition, + [kExprNodeConcat] = kEOpLvlAddition, [kExprNodeUnaryPlus] = kEOpLvlUnary, + [kExprNodeConcatOrSubscript] = kEOpLvlSubscript, [kExprNodeSubscript] = kEOpLvlSubscript, [kExprNodeCurlyBracesIdentifier] = kEOpLvlComplexIdentifier, [kExprNodeComplexIdentifier] = kEOpLvlValue, [kExprNodePlainIdentifier] = kEOpLvlValue, + [kExprNodePlainKey] = kEOpLvlValue, [kExprNodeRegister] = kEOpLvlValue, - [kExprNodeListLiteral] = kEOpLvlValue, + [kExprNodeInteger] = kEOpLvlValue, + [kExprNodeFloat] = kEOpLvlValue, }; static const ExprOpAssociativity node_type_to_op_ass[] = { @@ -1008,6 +1014,7 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { [kExprNodeUnknownFigure] = kEOpAssLeft, [kExprNodeLambda] = kEOpAssNo, [kExprNodeDictLiteral] = kEOpAssNo, + [kExprNodeListLiteral] = kEOpAssNo, // Does not really matter. [kExprNodeArrow] = kEOpAssNo, @@ -1030,17 +1037,21 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { [kExprNodeComparison] = kEOpAssRight, [kExprNodeBinaryPlus] = kEOpAssLeft, + [kExprNodeConcat] = kEOpAssLeft, [kExprNodeUnaryPlus] = kEOpAssNo, + [kExprNodeConcatOrSubscript] = kEOpAssLeft, [kExprNodeSubscript] = kEOpAssLeft, [kExprNodeCurlyBracesIdentifier] = kEOpAssLeft, [kExprNodeComplexIdentifier] = kEOpAssLeft, [kExprNodePlainIdentifier] = kEOpAssNo, + [kExprNodePlainKey] = kEOpAssNo, [kExprNodeRegister] = kEOpAssNo, - [kExprNodeListLiteral] = kEOpAssNo, + [kExprNodeInteger] = kEOpAssNo, + [kExprNodeFloat] = kEOpAssNo, }; /// Get AST node priority level @@ -1420,10 +1431,20 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) [kENodeArgument] = kELFlagIsNotCmp, [kENodeArgumentSeparator] = kELFlagForbidScope, }; - // FIXME Determine when (not) to allow floating-point numbers. + const bool is_concat_or_subscript = ( + want_node == kENodeValue + && kv_size(ast_stack) > 1 + && (*kv_Z(ast_stack, 1))->type == kExprNodeConcatOrSubscript); const int lexer_additional_flags = ( kELFlagPeek - | ((flags & kExprFlagsDisallowEOC) ? kELFlagForbidEOC : 0)); + | ((flags & kExprFlagsDisallowEOC) ? kELFlagForbidEOC : 0) + | ((want_node == kENodeValue + && (kv_size(ast_stack) == 1 + || ((*kv_Z(ast_stack, 1))->type != kExprNodeConcat + && ((*kv_Z(ast_stack, 1))->type + != kExprNodeConcatOrSubscript)))) + ? kELFlagAllowFloat + : 0)); LexExprToken cur_token = viml_pexpr_next_token( pstate, want_node_to_lexer_flags[want_node] | lexer_additional_flags); if (cur_token.type == kExprLexEOC) { @@ -1456,11 +1477,42 @@ viml_pexpr_parse_process_token: ExprASTNode *cur_node = NULL; assert((want_node == kENodeValue || want_node == kENodeArgument) == (*top_node_p == NULL)); + // Note: in Vim whether expression "cond?d.a:2" is valid depends both on + // "cond" and whether "d" is a dictionary: expression is valid if condition + // is true and "d" is a dictionary (with "a" key or it will complain about + // missing one, but this is not relevant); if any of the requirements is + // broken then this thing is parsed as "d . a:2" yielding missing colon + // error. This parser does not allow such ambiguity, especially because it + // simply can’t: whether "d" is a dictionary is not known at the parsing + // time. + // + // Here example will always contain a concat with "a:2" sucking colon, + // making expression invalid both because there is no longer a spare colon + // for ternary and because concatenating dictionary with anything is not + // valid. There are more cases when this will make a difference though. + const bool node_is_key = ( + is_concat_or_subscript + && (cur_token.type == kExprLexPlainIdentifier + ? (!cur_token.data.var.autoload + && cur_token.data.var.scope == 0) + : (cur_token.type == kExprLexNumber)) + && prev_token.type != kExprLexSpacing); + if (is_concat_or_subscript && !node_is_key) { + // Note: in Vim "d. a" (this is the reason behind `prev_token.type != + // kExprLexSpacing` part of the condition) as well as any other "d.{expr}" + // where "{expr}" does not look like a key is invalid whenever "d" happens + // to be a dictionary. Since parser has no idea whether preceding + // expression is actually a dictionary it can’t outright reject anything, + // so it turns kExprNodeConcatOrSubscript into kExprNodeConcat instead, + // which will yield different errors then Vim does in a number of + // circumstances, and in any case runtime and not parse time errors. + (*kv_Z(ast_stack, 1))->type = kExprNodeConcat; + } if ((want_node == kENodeArgumentSeparator && tok_type != kExprLexComma && tok_type != kExprLexArrow) || (want_node == kENodeArgument - && !(tok_type == kExprLexPlainIdentifier + && !(cur_token.type == kExprLexPlainIdentifier && cur_token.data.var.scope == 0 && !cur_token.data.var.autoload) && tok_type != kExprLexArrow)) { @@ -1844,7 +1896,10 @@ viml_pexpr_parse_figure_brace_closing_error: want_node = (want_node == kENodeArgument ? kENodeArgumentSeparator : kENodeOperator); - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodePlainIdentifier); + NEW_NODE_WITH_CUR_POS(cur_node, + (node_is_key + ? kExprNodePlainKey + : kExprNodePlainIdentifier)); cur_node->data.var.scope = cur_token.data.var.scope; const size_t scope_shift = (cur_token.data.var.scope == 0 ? 0 @@ -1854,6 +1909,7 @@ viml_pexpr_parse_figure_brace_closing_error: cur_node->data.var.ident_len = cur_token.len - scope_shift; *top_node_p = cur_node; if (scope_shift) { + assert(!node_is_key); viml_parser_highlight(pstate, cur_token.start, 1, HL(IdentifierScope)); viml_parser_highlight(pstate, shifted_pos(cur_token.start, 1), 1, @@ -1863,7 +1919,9 @@ viml_pexpr_parse_figure_brace_closing_error: viml_parser_highlight(pstate, shifted_pos(cur_token.start, scope_shift), cur_token.len - scope_shift, - HL(Identifier)); + (node_is_key + ? HL(IdentifierKey) + : HL(Identifier))); } } else { if (cur_token.data.var.scope == 0) { @@ -1882,6 +1940,40 @@ viml_pexpr_parse_figure_brace_closing_error: } break; } + case kExprLexNumber: { + if (want_node != kENodeValue) { + OP_MISSING; + } + if (node_is_key) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodePlainKey); + cur_node->data.var.ident = pline.data + cur_token.start.col; + cur_node->data.var.ident_len = cur_token.len; + HL_CUR_TOKEN(IdentifierKey); + } else if (cur_token.data.num.is_float) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeFloat); + cur_node->data.flt.value = cur_token.data.num.val.floating; + HL_CUR_TOKEN(Float); + } else { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeInteger); + cur_node->data.num.value = cur_token.data.num.val.integer; + HL_CUR_TOKEN(Number); + } + want_node = kENodeOperator; + *top_node_p = cur_node; + break; + } + case kExprLexDot: { + ADD_VALUE_IF_MISSING(_("E15: Unexpected dot: %.*s")); + if (prev_token.type == kExprLexSpacing) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeConcat); + HL_CUR_TOKEN(Concat); + } else { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeConcatOrSubscript); + HL_CUR_TOKEN(ConcatOrSubscript); + } + ADD_OP_NODE(cur_node); + break; + } case kExprLexParenthesis: { if (cur_token.data.brc.closing) { if (want_node == kENodeValue) { diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 29903490bb..0d496c87ba 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -166,6 +166,8 @@ typedef enum { /// Looks like "string", "g:Foo", etc: consists from a single /// kExprLexPlainIdentifier token. kExprNodePlainIdentifier = 'i', + /// Plain dictionary key, for use with kExprNodeConcatOrSubscript + kExprNodePlainKey = 'k', /// Complex identifier: variable/function name with curly braces kExprNodeComplexIdentifier = 'I', /// Figure brace expression which is not yet known @@ -180,6 +182,19 @@ typedef enum { kExprNodeColon = ':', ///< Colon “operator”. kExprNodeArrow = '>', ///< Arrow “operator”. kExprNodeComparison = '=', ///< Various comparison operators. + /// Concat operator + /// + /// To be only used in cases when it is known for sure it is not a subscript. + kExprNodeConcat = '.', + /// Concat or subscript operator + /// + /// For cases when it is not obvious whether expression is a concat or + /// a subscript. May only have either number or plain identifier as the second + /// child. To make it easier to avoid curly braces in place of + /// kExprNodePlainIdentifier node kExprNodePlainKey is used. + kExprNodeConcatOrSubscript = 'S', + kExprNodeInteger = '0', ///< Integral number. + kExprNodeFloat = '1', ///< Floating-point number. } ExprASTNodeType; typedef struct expr_ast_node ExprASTNode; @@ -219,7 +234,7 @@ struct expr_ast_node { /// Points to inside parser reader state. const char *ident; size_t ident_len; ///< Actual identifier length. - } var; ///< For kExprNodePlainIdentifier. + } var; ///< For kExprNodePlainIdentifier and kExprNodePlainKey. struct { bool got_colon; ///< True if colon was seen. } ter; ///< For kExprNodeTernaryValue. @@ -228,6 +243,12 @@ struct expr_ast_node { ExprCaseCompareStrategy ccs; ///< Case comparison strategy. bool inv; ///< True if comparison is to be inverted. } cmp; ///< For kExprNodeComparison. + struct { + uvarnumber_T value; + } num; ///< For kExprNodeInteger. + struct { + float_T value; + } flt; ///< For kExprNodeFloat. } data; }; -- cgit From e45e519495832e3d1d0fde1e32723d4140c5fc65 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 8 Oct 2017 01:19:58 +0300 Subject: viml/parser/expressions: Error out on multiple colons in a row --- src/nvim/viml/parser/expressions.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 4babf4312c..fffae8833d 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -1691,8 +1691,10 @@ viml_pexpr_parse_invalid_comma: SELECT_FIGURE_BRACE_TYPE(*eastnode_p, DictLiteral, Dict); break; } else if (eastnode_type == kExprNodeDictLiteral - || eastnode_type == kExprNodeComma) { + || eastnode_type == kExprNodeSubscript) { break; + } else if (eastnode_type == kExprNodeColon) { + goto viml_pexpr_parse_invalid_colon; } else if (eastnode_lvl >= kEOpLvlTernaryValue) { // Do nothing } else if (eastnode_lvl > kEOpLvlComma) { -- cgit From bd3a4166b25a64dbe406be09b3140955cf694477 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 8 Oct 2017 02:17:05 +0300 Subject: viml/parser/expressions: Add support for subscript and list literals --- src/nvim/viml/parser/expressions.c | 164 ++++++++++++++++++++++++++++++++----- 1 file changed, 142 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index fffae8833d..0613cc66d5 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -905,6 +905,10 @@ static inline void viml_pexpr_debug_print_token( // NVimDict -> Delimiter // NVimCurly -> Delimiter // +// NVimList -> Delimiter +// NVimSubscript -> Delimiter +// NVimSubscriptColon -> NVimSubscript +// // NVimIdentifier -> Identifier // NVimIdentifierScope -> NVimIdentifier // NVimIdentifierScopeDelimiter -> NVimIdentifier @@ -945,6 +949,9 @@ static inline void viml_pexpr_debug_print_token( // NVimInvalidNumber -> NVimInvalidValue // NVimInvalidFloat -> NVimInvalidValue // NVimInvalidIdentifierKey -> NVimInvalidIdentifier +// NVimInvalidList -> NVimInvalidDelimiter +// NVimInvalidSubscript -> NVimInvalidDelimiter +// NVimInvalidSubscriptColon -> NVimInvalidSubscript /// Allocate a new node and set some of the values /// @@ -965,9 +972,10 @@ static const ExprOpLvl node_type_to_op_lvl[] = { [kExprNodeOpMissing] = kEOpLvlMultiplication, [kExprNodeNested] = kEOpLvlParens, - // Note: it is kEOpLvlSubscript for “binary operator” itself, but + // Note: below nodes are kEOpLvlSubscript for “binary operator” itself, but // kEOpLvlParens when it comes to inside the parenthesis. [kExprNodeCall] = kEOpLvlParens, + [kExprNodeSubscript] = kEOpLvlParens, [kExprNodeUnknownFigure] = kEOpLvlParens, [kExprNodeLambda] = kEOpLvlParens, @@ -992,7 +1000,6 @@ static const ExprOpLvl node_type_to_op_lvl[] = { [kExprNodeUnaryPlus] = kEOpLvlUnary, [kExprNodeConcatOrSubscript] = kEOpLvlSubscript, - [kExprNodeSubscript] = kEOpLvlSubscript, [kExprNodeCurlyBracesIdentifier] = kEOpLvlComplexIdentifier, @@ -1010,6 +1017,7 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { [kExprNodeNested] = kEOpAssNo, [kExprNodeCall] = kEOpAssNo, + [kExprNodeSubscript] = kEOpAssNo, [kExprNodeUnknownFigure] = kEOpAssLeft, [kExprNodeLambda] = kEOpAssNo, @@ -1042,7 +1050,6 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { [kExprNodeUnaryPlus] = kEOpAssNo, [kExprNodeConcatOrSubscript] = kEOpAssLeft, - [kExprNodeSubscript] = kEOpAssLeft, [kExprNodeCurlyBracesIdentifier] = kEOpAssLeft, @@ -1105,12 +1112,14 @@ static bool viml_pexpr_handle_bop(const ParserState *const pstate, ExprOpLvl top_node_lvl; ExprOpAssociativity top_node_ass; assert(kv_size(*ast_stack)); - const ExprOpLvl bop_node_lvl = (bop_node->type == kExprNodeCall + const ExprOpLvl bop_node_lvl = ((bop_node->type == kExprNodeCall + || bop_node->type == kExprNodeSubscript) ? kEOpLvlSubscript : node_lvl(*bop_node)); #ifndef NDEBUG const ExprOpAssociativity bop_node_ass = ( - bop_node->type == kExprNodeCall + (bop_node->type == kExprNodeCall + || bop_node->type == kExprNodeSubscript) ? kEOpAssLeft : node_ass(*bop_node)); #endif @@ -1214,8 +1223,8 @@ static inline ParserPosition shifted_pos(const ParserPosition pos, /// @param cur_token Token to set position from. #define POS_FROM_TOKEN(cur_node, cur_token) \ do { \ - cur_node->start = cur_token.start; \ - cur_node->len = cur_token.len; \ + (cur_node)->start = cur_token.start; \ + (cur_node)->len = cur_token.len; \ } while (0) /// Allocate new node and set its position from the current token @@ -1226,11 +1235,11 @@ static inline ParserPosition shifted_pos(const ParserPosition pos, /// @param typ Node type. #define NEW_NODE_WITH_CUR_POS(cur_node, typ) \ do { \ - cur_node = NEW_NODE(typ); \ - POS_FROM_TOKEN(cur_node, cur_token); \ + (cur_node) = NEW_NODE(typ); \ + POS_FROM_TOKEN((cur_node), cur_token); \ if (prev_token.type == kExprLexSpacing) { \ - cur_node->start = prev_token.start; \ - cur_node->len += prev_token.len; \ + (cur_node)->start = prev_token.start; \ + (cur_node)->len += prev_token.len; \ } \ } while (0) @@ -1280,9 +1289,8 @@ static inline ParserPosition shifted_pos(const ParserPosition pos, do { \ if (want_node == kENodeValue) { \ ERROR_FROM_TOKEN_AND_MSG(cur_token, (msg)); \ - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeMissing); \ - cur_node->len = 0; \ - *top_node_p = cur_node; \ + NEW_NODE_WITH_CUR_POS((*top_node_p), kExprNodeMissing); \ + (*top_node_p)->len = 0; \ want_node = kENodeOperator; \ } \ } while (0) @@ -1658,13 +1666,14 @@ viml_pexpr_parse_invalid_comma: HL_CUR_TOKEN(Comma); break; } +#define EXP_VAL_COLON "E15: Expected value, got colon: %.*s" case kExprLexColon: { - ADD_VALUE_IF_MISSING(_("E15: Expected value, got colon: %.*s")); if (kv_size(ast_stack) < 2) { goto viml_pexpr_parse_invalid_colon; } bool is_ternary = false; bool can_be_ternary = true; + bool is_subscript = false; for (size_t i = 1; i < kv_size(ast_stack); i++) { ExprASTNode *const *const eastnode_p = (ExprASTNode *const *)kv_Z(ast_stack, i); @@ -1683,6 +1692,7 @@ viml_pexpr_parse_invalid_comma: } is_ternary = true; (*eastnode_p)->data.ter.got_colon = true; + ADD_VALUE_IF_MISSING(_(EXP_VAL_COLON)); assert((*eastnode_p)->children != NULL); assert((*eastnode_p)->children->next == NULL); kvi_push(ast_stack, &(*eastnode_p)->children->next); @@ -1690,14 +1700,18 @@ viml_pexpr_parse_invalid_comma: } else if (eastnode_type == kExprNodeUnknownFigure) { SELECT_FIGURE_BRACE_TYPE(*eastnode_p, DictLiteral, Dict); break; - } else if (eastnode_type == kExprNodeDictLiteral - || eastnode_type == kExprNodeSubscript) { + } else if (eastnode_type == kExprNodeDictLiteral) { + break; + } else if (eastnode_type == kExprNodeSubscript) { + is_subscript = true; + can_be_ternary = false; + assert(!is_ternary); break; } else if (eastnode_type == kExprNodeColon) { goto viml_pexpr_parse_invalid_colon; } else if (eastnode_lvl >= kEOpLvlTernaryValue) { // Do nothing - } else if (eastnode_lvl > kEOpLvlComma) { + } else if (eastnode_lvl >= kEOpLvlComma) { can_be_ternary = false; } else { viml_pexpr_parse_invalid_colon: @@ -1711,16 +1725,122 @@ viml_pexpr_parse_invalid_colon: goto viml_pexpr_parse_invalid_colon; } } - if (is_ternary) { - HL_CUR_TOKEN(TernaryColon); - } else { + if (is_subscript) { + assert(kv_size(ast_stack) > 1); + // Colon immediately following subscript start: it is empty subscript + // part like a[:2]. + if (want_node == kENodeValue + && (*kv_Z(ast_stack, 1))->type == kExprNodeSubscript) { + NEW_NODE_WITH_CUR_POS(*top_node_p, kExprNodeMissing); + (*top_node_p)->len = 0; + want_node = kENodeOperator; + } else { + ADD_VALUE_IF_MISSING(_(EXP_VAL_COLON)); + } NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); ADD_OP_NODE(cur_node); - HL_CUR_TOKEN(Colon); + HL_CUR_TOKEN(SubscriptColon); + } else { + ADD_VALUE_IF_MISSING(_(EXP_VAL_COLON)); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); + if (is_ternary) { + HL_CUR_TOKEN(TernaryColon); + } else { + ADD_OP_NODE(cur_node); + HL_CUR_TOKEN(Colon); + } } want_node = kENodeValue; break; } +#undef EXP_VAL_COLON + case kExprLexBracket: { + if (cur_token.data.brc.closing) { + ExprASTNode **new_top_node_p = NULL; + // Always drop the topmost value: + // + // 1. When want_node != kENodeValue topmost item on stack is + // a *finished* left operand, which may as well be "{@a}" which + // needs not be finished again. + // 2. Otherwise it is pointing to NULL what nobody wants. + kv_drop(ast_stack, 1); + if (!kv_size(ast_stack)) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeListLiteral); + cur_node->len = 0; + if (want_node != kENodeValue) { + cur_node->children = *top_node_p; + } + *top_node_p = cur_node; + goto viml_pexpr_parse_bracket_closing_error; + } + if (want_node == kENodeValue) { + // It is OK to want value if + // + // 1. It is empty list literal, in which case top node will be + // ListLiteral. + // 2. It is list literal with trailing comma, in which case top node + // will be that comma. + // 3. It is subscript with colon, but without one of the values: + // e.g. "a[:]", "a[1:]", top node will be colon in this case. + if ((*kv_last(ast_stack))->type != kExprNodeListLiteral + && (*kv_last(ast_stack))->type != kExprNodeComma + && (*kv_last(ast_stack))->type != kExprNodeColon) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Expected value, got closing bracket: %.*s")); + } + } else { + if (!kv_size(ast_stack)) { + new_top_node_p = top_node_p; + goto viml_pexpr_parse_bracket_closing_error; + } + } + do { + new_top_node_p = kv_pop(ast_stack); + } while (kv_size(ast_stack) + && (new_top_node_p == NULL + || ((*new_top_node_p)->type != kExprNodeListLiteral + && (*new_top_node_p)->type != kExprNodeSubscript))); + ExprASTNode *new_top_node = *new_top_node_p; + switch (new_top_node->type) { + case kExprNodeListLiteral: { + HL_CUR_TOKEN(List); + break; + } + case kExprNodeSubscript: { + HL_CUR_TOKEN(Subscript); + break; + } + default: { +viml_pexpr_parse_bracket_closing_error: + assert(!kv_size(ast_stack)); + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Unexpected closing figure brace: %.*s")); + HL_CUR_TOKEN(List); + break; + } + } + kvi_push(ast_stack, new_top_node_p); + want_node = kENodeOperator; + } else { + if (want_node == kENodeValue) { + // Value means list literal. + HL_CUR_TOKEN(List); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeListLiteral); + *top_node_p = cur_node; + kvi_push(ast_stack, &cur_node->children); + want_node = kENodeValue; + } else { + if (prev_token.type == kExprLexSpacing) { + OP_MISSING; + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeSubscript); + ADD_OP_NODE(cur_node); + HL_CUR_TOKEN(Subscript); + } + } + break; + } case kExprLexFigureBrace: { if (cur_token.data.brc.closing) { ExprASTNode **new_top_node_p = NULL; -- cgit From 6f22b5afad23ccc0390a6e2dd61d2e166ac6696a Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 8 Oct 2017 21:19:10 +0300 Subject: mbyte: Lint some functions which are to be copied for symbolic tests --- src/nvim/globals.h | 23 ----- src/nvim/mbyte.c | 248 +++++++++++++++++++++++++++++------------------------ 2 files changed, 137 insertions(+), 134 deletions(-) (limited to 'src') diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 13ecafcbe3..86fff46737 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -725,29 +725,6 @@ EXTERN int vr_lines_changed INIT(= 0); /* #Lines changed by "gR" so far */ /// Encoding used when 'fencs' is set to "default" EXTERN char_u *fenc_default INIT(= NULL); -// To speed up BYTELEN(); keep a lookup table to quickly get the length in -// bytes of a UTF-8 character from the first byte of a UTF-8 string. Bytes -// which are illegal when used as the first byte have a 1. The NUL byte has -// length 1. -EXTERN char utf8len_tab[256] INIT(= { - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1, -}); - # if defined(USE_ICONV) && defined(DYNAMIC_ICONV) /* Pointers to functions and variables to be loaded at runtime */ EXTERN size_t (*iconv)(iconv_t cd, const char **inbuf, size_t *inbytesleft, diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index b24770a409..f65d7a6b13 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -72,19 +72,41 @@ struct interval { # include "unicode_tables.generated.h" #endif -/* - * Like utf8len_tab above, but using a zero for illegal lead bytes. - */ -const uint8_t utf8len_tab_zero[256] = -{ - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, - 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,0,0, +// To speed up BYTELEN(); keep a lookup table to quickly get the length in +// bytes of a UTF-8 character from the first byte of a UTF-8 string. Bytes +// which are illegal when used as the first byte have a 1. The NUL byte has +// length 1. +const uint8_t utf8len_tab[] = { + // ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?A ?B ?C ?D ?E ?F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B? + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C? + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // D? + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // E? + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1, // F? +}; + +// Like utf8len_tab above, but using a zero for illegal lead bytes. +const uint8_t utf8len_tab_zero[] = { + //1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 2 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 4 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 6 + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 8 + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // A + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // C + 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,0,0, // E }; /* @@ -528,45 +550,52 @@ int utf_off2cells(unsigned off, unsigned max_off) return (off + 1 < max_off && ScreenLines[off + 1] == 0) ? 2 : 1; } -/* - * Convert a UTF-8 byte sequence to a wide character. - * If the sequence is illegal or truncated by a NUL the first byte is - * returned. - * Does not include composing characters, of course. - */ -int utf_ptr2char(const char_u *p) +/// Convert a UTF-8 byte sequence to a wide character +/// +/// If the sequence is illegal or truncated by a NUL then the first byte is +/// returned. Does not include composing characters for obvious reasons. +/// +/// @param[in] p String to convert. +/// +/// @return Unicode codepoint or byte value. +int utf_ptr2char(const char_u *const p) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - uint8_t len; - - if (p[0] < 0x80) /* be quick for ASCII */ + if (p[0] < 0x80) { // Be quick for ASCII. return p[0]; + } - len = utf8len_tab_zero[p[0]]; + const uint8_t len = utf8len_tab_zero[p[0]]; if (len > 1 && (p[1] & 0xc0) == 0x80) { - if (len == 2) + if (len == 2) { return ((p[0] & 0x1f) << 6) + (p[1] & 0x3f); + } if ((p[2] & 0xc0) == 0x80) { - if (len == 3) - return ((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6) - + (p[2] & 0x3f); + if (len == 3) { + return (((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6) + + (p[2] & 0x3f)); + } if ((p[3] & 0xc0) == 0x80) { - if (len == 4) - return ((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12) - + ((p[2] & 0x3f) << 6) + (p[3] & 0x3f); + if (len == 4) { + return (((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12) + + ((p[2] & 0x3f) << 6) + (p[3] & 0x3f)); + } if ((p[4] & 0xc0) == 0x80) { - if (len == 5) - return ((p[0] & 0x03) << 24) + ((p[1] & 0x3f) << 18) - + ((p[2] & 0x3f) << 12) + ((p[3] & 0x3f) << 6) - + (p[4] & 0x3f); - if ((p[5] & 0xc0) == 0x80 && len == 6) - return ((p[0] & 0x01) << 30) + ((p[1] & 0x3f) << 24) - + ((p[2] & 0x3f) << 18) + ((p[3] & 0x3f) << 12) - + ((p[4] & 0x3f) << 6) + (p[5] & 0x3f); + if (len == 5) { + return (((p[0] & 0x03) << 24) + ((p[1] & 0x3f) << 18) + + ((p[2] & 0x3f) << 12) + ((p[3] & 0x3f) << 6) + + (p[4] & 0x3f)); + } + if ((p[5] & 0xc0) == 0x80 && len == 6) { + return (((p[0] & 0x01) << 30) + ((p[1] & 0x3f) << 24) + + ((p[2] & 0x3f) << 18) + ((p[3] & 0x3f) << 12) + + ((p[4] & 0x3f) << 6) + (p[5] & 0x3f)); + } } } } } - /* Illegal value, just return the first byte */ + // Illegal value: just return the first byte. return p[0]; } @@ -767,23 +796,24 @@ int utfc_char2bytes(int off, char_u *buf) return len; } -/* - * Get the length of a UTF-8 byte sequence, not including any following - * composing characters. - * Returns 0 for "". - * Returns 1 for an illegal byte sequence. - */ -int utf_ptr2len(const char_u *p) +/// Get the length of a UTF-8 byte sequence representing a single codepoint +/// +/// @param[in] p UTF-8 string. +/// +/// @return Sequence length, 0 for empty string and 1 for non-UTF-8 byte +/// sequence. +int utf_ptr2len(const char_u *const p) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - int len; - int i; - - if (*p == NUL) + if (*p == NUL) { return 0; - len = utf8len_tab[*p]; - for (i = 1; i < len; ++i) - if ((p[i] & 0xc0) != 0x80) + } + const int len = utf8len_tab[*p]; + for (int i = 1; i < len; i++) { + if ((p[i] & 0xc0) != 0x80) { return 1; + } + } return len; } @@ -824,38 +854,38 @@ int utf_ptr2len_len(const char_u *p, int size) return len; } -/* - * Return the number of bytes the UTF-8 encoding of the character at "p" takes. - * This includes following composing characters. - */ -int utfc_ptr2len(const char_u *p) +/// Return the number of bytes occupied by a UTF-8 character in a string +/// +/// This includes following composing characters. +int utfc_ptr2len(const char_u *const p) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - int len; - int b0 = *p; - int prevlen; + uint8_t b0 = (uint8_t)(*p); - if (b0 == NUL) + if (b0 == NUL) { return 0; - if (b0 < 0x80 && p[1] < 0x80) /* be quick for ASCII */ + } + if (b0 < 0x80 && p[1] < 0x80) { // be quick for ASCII return 1; + } - /* Skip over first UTF-8 char, stopping at a NUL byte. */ - len = utf_ptr2len(p); + // Skip over first UTF-8 char, stopping at a NUL byte. + int len = utf_ptr2len(p); - /* Check for illegal byte. */ - if (len == 1 && b0 >= 0x80) + // Check for illegal byte. + if (len == 1 && b0 >= 0x80) { return 1; + } - /* - * Check for composing characters. We can handle only the first six, but - * skip all of them (otherwise the cursor would get stuck). - */ - prevlen = 0; - for (;; ) { - if (p[len] < 0x80 || !UTF_COMPOSINGLIKE(p + prevlen, p + len)) + // Check for composing characters. We can handle only the first six, but + // skip all of them (otherwise the cursor would get stuck). + int prevlen = 0; + for (;;) { + if (p[len] < 0x80 || !UTF_COMPOSINGLIKE(p + prevlen, p + len)) { return len; + } - /* Skip over composing char */ + // Skip over composing char. prevlen = len; len += utf_ptr2len(p + len); } @@ -913,23 +943,22 @@ int utfc_ptr2len_len(const char_u *p, int size) return len; } -/* - * Return the number of bytes the UTF-8 encoding of character "c" takes. - * This does not include composing characters. - */ -int utf_char2len(int c) +/// Determine how many bytes certain unicode codepoint will occupy +int utf_char2len(const int c) { - if (c < 0x80) + if (c < 0x80) { return 1; - if (c < 0x800) + } else if (c < 0x800) { return 2; - if (c < 0x10000) + } else if (c < 0x10000) { return 3; - if (c < 0x200000) + } else if (c < 0x200000) { return 4; - if (c < 0x4000000) + } else if (c < 0x4000000) { return 5; - return 6; + } else { + return 6; + } } /// Convert Unicode character to UTF-8 string @@ -937,46 +966,42 @@ int utf_char2len(int c) /// @param c character to convert to \p buf /// @param[out] buf UTF-8 string generated from \p c, does not add \0 /// @return Number of bytes (1-6). Does not include composing characters. -int utf_char2bytes(int c, char_u *const buf) +int utf_char2bytes(const int c, char_u *const buf) { - if (c < 0x80) { /* 7 bits */ + if (c < 0x80) { // 7 bits buf[0] = c; return 1; - } - if (c < 0x800) { /* 11 bits */ + } else if (c < 0x800) { // 11 bits buf[0] = 0xc0 + ((unsigned)c >> 6); buf[1] = 0x80 + (c & 0x3f); return 2; - } - if (c < 0x10000) { /* 16 bits */ + } else if (c < 0x10000) { // 16 bits buf[0] = 0xe0 + ((unsigned)c >> 12); buf[1] = 0x80 + (((unsigned)c >> 6) & 0x3f); buf[2] = 0x80 + (c & 0x3f); return 3; - } - if (c < 0x200000) { /* 21 bits */ + } else if (c < 0x200000) { // 21 bits buf[0] = 0xf0 + ((unsigned)c >> 18); buf[1] = 0x80 + (((unsigned)c >> 12) & 0x3f); buf[2] = 0x80 + (((unsigned)c >> 6) & 0x3f); buf[3] = 0x80 + (c & 0x3f); return 4; - } - if (c < 0x4000000) { /* 26 bits */ + } else if (c < 0x4000000) { // 26 bits buf[0] = 0xf8 + ((unsigned)c >> 24); buf[1] = 0x80 + (((unsigned)c >> 18) & 0x3f); buf[2] = 0x80 + (((unsigned)c >> 12) & 0x3f); buf[3] = 0x80 + (((unsigned)c >> 6) & 0x3f); buf[4] = 0x80 + (c & 0x3f); return 5; + } else { // 31 bits + buf[0] = 0xfc + ((unsigned)c >> 30); + buf[1] = 0x80 + (((unsigned)c >> 24) & 0x3f); + buf[2] = 0x80 + (((unsigned)c >> 18) & 0x3f); + buf[3] = 0x80 + (((unsigned)c >> 12) & 0x3f); + buf[4] = 0x80 + (((unsigned)c >> 6) & 0x3f); + buf[5] = 0x80 + (c & 0x3f); + return 6; } - /* 31 bits */ - buf[0] = 0xfc + ((unsigned)c >> 30); - buf[1] = 0x80 + (((unsigned)c >> 24) & 0x3f); - buf[2] = 0x80 + (((unsigned)c >> 18) & 0x3f); - buf[3] = 0x80 + (((unsigned)c >> 12) & 0x3f); - buf[4] = 0x80 + (((unsigned)c >> 6) & 0x3f); - buf[5] = 0x80 + (c & 0x3f); - return 6; } /* @@ -1513,14 +1538,15 @@ int utf_head_off(const char_u *base, const char_u *p) return (int)(p - q); } -/* - * Copy a character from "*fp" to "*tp" and advance the pointers. - */ -void mb_copy_char(const char_u **fp, char_u **tp) +/// Copy a character, advancing the pointers +/// +/// @param[in,out] fp Source of the character to copy. +/// @param[in,out] tp Destination to copy to. +void mb_copy_char(const char_u **const fp, char_u **const tp) { - int l = (*mb_ptr2len)(*fp); + const size_t l = (size_t)utfc_ptr2len(*fp); - memmove(*tp, *fp, (size_t)l); + memmove(*tp, *fp, l); *tp += l; *fp += l; } -- cgit From e423cfe1948d75285dad2df151f53400f3b3be4e Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 8 Oct 2017 21:41:34 +0300 Subject: edit: Lint some functions which are to be copied for symbolic tests --- src/nvim/edit.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/nvim/edit.c b/src/nvim/edit.c index ca62679fab..e57598fc91 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -6074,27 +6074,30 @@ void free_last_insert(void) #endif -/* - * Add character "c" to buffer "s". Escape the special meaning of K_SPECIAL - * and CSI. Handle multi-byte characters. - * Returns a pointer to after the added bytes. - */ +/// Add character "c" to buffer "s" +/// +/// Escapes the special meaning of K_SPECIAL and CSI, handles multi-byte +/// characters. +/// +/// @param[in] c Character to add. +/// @param[out] s Buffer to add to. Must have at least MB_MAXBYTES + 1 bytes. +/// +/// @return Pointer to after the added bytes. char_u *add_char2buf(int c, char_u *s) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { char_u temp[MB_MAXBYTES + 1]; - int i; - int len; - - len = (*mb_char2bytes)(c, temp); - for (i = 0; i < len; ++i) { + const int len = utf_char2bytes(c, temp); + for (int i = 0; i < len; ++i) { c = temp[i]; - /* Need to escape K_SPECIAL and CSI like in the typeahead buffer. */ + // Need to escape K_SPECIAL and CSI like in the typeahead buffer. if (c == K_SPECIAL) { *s++ = K_SPECIAL; *s++ = KS_SPECIAL; *s++ = KE_FILLER; - } else + } else { *s++ = c; + } } return s; } -- cgit From c484613ce034cf9b10a4185621abdf8d82b570f8 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 8 Oct 2017 21:52:29 +0300 Subject: keymap: Lint some functions to be copied for symbolic tests --- src/nvim/keymap.c | 435 +++++++++++++++++++++++++++--------------------------- 1 file changed, 216 insertions(+), 219 deletions(-) (limited to 'src') diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index 3d7ebb6382..f344d65c8d 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -24,12 +24,11 @@ * Some useful tables. */ -static struct modmasktable { - short mod_mask; /* Bit-mask for particular key modifier */ - short mod_flag; /* Bit(s) for particular key modifier */ - char_u name; /* Single letter name of modifier */ -} mod_mask_table[] = -{ +static const struct modmasktable { + short mod_mask; ///< Bit-mask for particular key modifier. + short mod_flag; ///< Bit(s) for particular key modifier. + char_u name; ///< Single letter name of modifier. +} mod_mask_table[] = { {MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'M'}, {MOD_MASK_META, MOD_MASK_META, (char_u)'T'}, {MOD_MASK_CTRL, MOD_MASK_CTRL, (char_u)'C'}, @@ -139,155 +138,154 @@ static char_u modifier_keys_table[] = NUL }; -static struct key_name_entry { - int key; /* Special key code or ascii value */ - char_u *name; /* Name of key */ -} key_names_table[] = -{ - {' ', (char_u *)"Space"}, - {TAB, (char_u *)"Tab"}, - {K_TAB, (char_u *)"Tab"}, - {NL, (char_u *)"NL"}, - {NL, (char_u *)"NewLine"}, /* Alternative name */ - {NL, (char_u *)"LineFeed"}, /* Alternative name */ - {NL, (char_u *)"LF"}, /* Alternative name */ - {CAR, (char_u *)"CR"}, - {CAR, (char_u *)"Return"}, /* Alternative name */ - {CAR, (char_u *)"Enter"}, /* Alternative name */ - {K_BS, (char_u *)"BS"}, - {K_BS, (char_u *)"BackSpace"}, /* Alternative name */ - {ESC, (char_u *)"Esc"}, - {CSI, (char_u *)"CSI"}, - {K_CSI, (char_u *)"xCSI"}, - {'|', (char_u *)"Bar"}, - {'\\', (char_u *)"Bslash"}, - {K_DEL, (char_u *)"Del"}, - {K_DEL, (char_u *)"Delete"}, /* Alternative name */ - {K_KDEL, (char_u *)"kDel"}, - {K_UP, (char_u *)"Up"}, - {K_DOWN, (char_u *)"Down"}, - {K_LEFT, (char_u *)"Left"}, - {K_RIGHT, (char_u *)"Right"}, - {K_XUP, (char_u *)"xUp"}, - {K_XDOWN, (char_u *)"xDown"}, - {K_XLEFT, (char_u *)"xLeft"}, - {K_XRIGHT, (char_u *)"xRight"}, - - {K_F1, (char_u *)"F1"}, - {K_F2, (char_u *)"F2"}, - {K_F3, (char_u *)"F3"}, - {K_F4, (char_u *)"F4"}, - {K_F5, (char_u *)"F5"}, - {K_F6, (char_u *)"F6"}, - {K_F7, (char_u *)"F7"}, - {K_F8, (char_u *)"F8"}, - {K_F9, (char_u *)"F9"}, - {K_F10, (char_u *)"F10"}, - - {K_F11, (char_u *)"F11"}, - {K_F12, (char_u *)"F12"}, - {K_F13, (char_u *)"F13"}, - {K_F14, (char_u *)"F14"}, - {K_F15, (char_u *)"F15"}, - {K_F16, (char_u *)"F16"}, - {K_F17, (char_u *)"F17"}, - {K_F18, (char_u *)"F18"}, - {K_F19, (char_u *)"F19"}, - {K_F20, (char_u *)"F20"}, - - {K_F21, (char_u *)"F21"}, - {K_F22, (char_u *)"F22"}, - {K_F23, (char_u *)"F23"}, - {K_F24, (char_u *)"F24"}, - {K_F25, (char_u *)"F25"}, - {K_F26, (char_u *)"F26"}, - {K_F27, (char_u *)"F27"}, - {K_F28, (char_u *)"F28"}, - {K_F29, (char_u *)"F29"}, - {K_F30, (char_u *)"F30"}, - - {K_F31, (char_u *)"F31"}, - {K_F32, (char_u *)"F32"}, - {K_F33, (char_u *)"F33"}, - {K_F34, (char_u *)"F34"}, - {K_F35, (char_u *)"F35"}, - {K_F36, (char_u *)"F36"}, - {K_F37, (char_u *)"F37"}, - - {K_XF1, (char_u *)"xF1"}, - {K_XF2, (char_u *)"xF2"}, - {K_XF3, (char_u *)"xF3"}, - {K_XF4, (char_u *)"xF4"}, - - {K_HELP, (char_u *)"Help"}, - {K_UNDO, (char_u *)"Undo"}, - {K_INS, (char_u *)"Insert"}, - {K_INS, (char_u *)"Ins"}, /* Alternative name */ - {K_KINS, (char_u *)"kInsert"}, - {K_HOME, (char_u *)"Home"}, - {K_KHOME, (char_u *)"kHome"}, - {K_XHOME, (char_u *)"xHome"}, - {K_ZHOME, (char_u *)"zHome"}, - {K_END, (char_u *)"End"}, - {K_KEND, (char_u *)"kEnd"}, - {K_XEND, (char_u *)"xEnd"}, - {K_ZEND, (char_u *)"zEnd"}, - {K_PAGEUP, (char_u *)"PageUp"}, - {K_PAGEDOWN, (char_u *)"PageDown"}, - {K_KPAGEUP, (char_u *)"kPageUp"}, - {K_KPAGEDOWN, (char_u *)"kPageDown"}, - - {K_KPLUS, (char_u *)"kPlus"}, - {K_KMINUS, (char_u *)"kMinus"}, - {K_KDIVIDE, (char_u *)"kDivide"}, - {K_KMULTIPLY, (char_u *)"kMultiply"}, - {K_KENTER, (char_u *)"kEnter"}, - {K_KPOINT, (char_u *)"kPoint"}, - - {K_K0, (char_u *)"k0"}, - {K_K1, (char_u *)"k1"}, - {K_K2, (char_u *)"k2"}, - {K_K3, (char_u *)"k3"}, - {K_K4, (char_u *)"k4"}, - {K_K5, (char_u *)"k5"}, - {K_K6, (char_u *)"k6"}, - {K_K7, (char_u *)"k7"}, - {K_K8, (char_u *)"k8"}, - {K_K9, (char_u *)"k9"}, - - {'<', (char_u *)"lt"}, - - {K_MOUSE, (char_u *)"Mouse"}, - {K_LEFTMOUSE, (char_u *)"LeftMouse"}, - {K_LEFTMOUSE_NM, (char_u *)"LeftMouseNM"}, - {K_LEFTDRAG, (char_u *)"LeftDrag"}, - {K_LEFTRELEASE, (char_u *)"LeftRelease"}, - {K_LEFTRELEASE_NM, (char_u *)"LeftReleaseNM"}, - {K_MIDDLEMOUSE, (char_u *)"MiddleMouse"}, - {K_MIDDLEDRAG, (char_u *)"MiddleDrag"}, - {K_MIDDLERELEASE, (char_u *)"MiddleRelease"}, - {K_RIGHTMOUSE, (char_u *)"RightMouse"}, - {K_RIGHTDRAG, (char_u *)"RightDrag"}, - {K_RIGHTRELEASE, (char_u *)"RightRelease"}, - {K_MOUSEDOWN, (char_u *)"ScrollWheelUp"}, - {K_MOUSEUP, (char_u *)"ScrollWheelDown"}, - {K_MOUSELEFT, (char_u *)"ScrollWheelRight"}, - {K_MOUSERIGHT, (char_u *)"ScrollWheelLeft"}, - {K_MOUSEDOWN, (char_u *)"MouseDown"}, /* OBSOLETE: Use */ - {K_MOUSEUP, (char_u *)"MouseUp"}, /* ScrollWheelXXX instead */ - {K_X1MOUSE, (char_u *)"X1Mouse"}, - {K_X1DRAG, (char_u *)"X1Drag"}, - {K_X1RELEASE, (char_u *)"X1Release"}, - {K_X2MOUSE, (char_u *)"X2Mouse"}, - {K_X2DRAG, (char_u *)"X2Drag"}, - {K_X2RELEASE, (char_u *)"X2Release"}, - {K_DROP, (char_u *)"Drop"}, - {K_ZERO, (char_u *)"Nul"}, - {K_SNR, (char_u *)"SNR"}, - {K_PLUG, (char_u *)"Plug"}, - {K_PASTE, (char_u *)"Paste"}, - {K_FOCUSGAINED, (char_u *)"FocusGained"}, - {K_FOCUSLOST, (char_u *)"FocusLost"}, +static const struct key_name_entry { + int key; ///< Special key code or ASCII value. + const char *name; ///< Name of the key +} key_names_table[] = { + {' ', "Space"}, + {TAB, "Tab"}, + {K_TAB, "Tab"}, + {NL, "NL"}, + {NL, "NewLine"}, // Alternative name + {NL, "LineFeed"}, // Alternative name + {NL, "LF"}, // Alternative name + {CAR, "CR"}, + {CAR, "Return"}, // Alternative name + {CAR, "Enter"}, // Alternative name + {K_BS, "BS"}, + {K_BS, "BackSpace"}, // Alternative name + {ESC, "Esc"}, + {CSI, "CSI"}, + {K_CSI, "xCSI"}, + {'|', "Bar"}, + {'\\', "Bslash"}, + {K_DEL, "Del"}, + {K_DEL, "Delete"}, // Alternative name + {K_KDEL, "kDel"}, + {K_UP, "Up"}, + {K_DOWN, "Down"}, + {K_LEFT, "Left"}, + {K_RIGHT, "Right"}, + {K_XUP, "xUp"}, + {K_XDOWN, "xDown"}, + {K_XLEFT, "xLeft"}, + {K_XRIGHT, "xRight"}, + + {K_F1, "F1"}, + {K_F2, "F2"}, + {K_F3, "F3"}, + {K_F4, "F4"}, + {K_F5, "F5"}, + {K_F6, "F6"}, + {K_F7, "F7"}, + {K_F8, "F8"}, + {K_F9, "F9"}, + {K_F10, "F10"}, + + {K_F11, "F11"}, + {K_F12, "F12"}, + {K_F13, "F13"}, + {K_F14, "F14"}, + {K_F15, "F15"}, + {K_F16, "F16"}, + {K_F17, "F17"}, + {K_F18, "F18"}, + {K_F19, "F19"}, + {K_F20, "F20"}, + + {K_F21, "F21"}, + {K_F22, "F22"}, + {K_F23, "F23"}, + {K_F24, "F24"}, + {K_F25, "F25"}, + {K_F26, "F26"}, + {K_F27, "F27"}, + {K_F28, "F28"}, + {K_F29, "F29"}, + {K_F30, "F30"}, + + {K_F31, "F31"}, + {K_F32, "F32"}, + {K_F33, "F33"}, + {K_F34, "F34"}, + {K_F35, "F35"}, + {K_F36, "F36"}, + {K_F37, "F37"}, + + {K_XF1, "xF1"}, + {K_XF2, "xF2"}, + {K_XF3, "xF3"}, + {K_XF4, "xF4"}, + + {K_HELP, "Help"}, + {K_UNDO, "Undo"}, + {K_INS, "Insert"}, + {K_INS, "Ins"}, // Alternative name + {K_KINS, "kInsert"}, + {K_HOME, "Home"}, + {K_KHOME, "kHome"}, + {K_XHOME, "xHome"}, + {K_ZHOME, "zHome"}, + {K_END, "End"}, + {K_KEND, "kEnd"}, + {K_XEND, "xEnd"}, + {K_ZEND, "zEnd"}, + {K_PAGEUP, "PageUp"}, + {K_PAGEDOWN, "PageDown"}, + {K_KPAGEUP, "kPageUp"}, + {K_KPAGEDOWN, "kPageDown"}, + + {K_KPLUS, "kPlus"}, + {K_KMINUS, "kMinus"}, + {K_KDIVIDE, "kDivide"}, + {K_KMULTIPLY, "kMultiply"}, + {K_KENTER, "kEnter"}, + {K_KPOINT, "kPoint"}, + + {K_K0, "k0"}, + {K_K1, "k1"}, + {K_K2, "k2"}, + {K_K3, "k3"}, + {K_K4, "k4"}, + {K_K5, "k5"}, + {K_K6, "k6"}, + {K_K7, "k7"}, + {K_K8, "k8"}, + {K_K9, "k9"}, + + {'<', "lt"}, + + {K_MOUSE, "Mouse"}, + {K_LEFTMOUSE, "LeftMouse"}, + {K_LEFTMOUSE_NM, "LeftMouseNM"}, + {K_LEFTDRAG, "LeftDrag"}, + {K_LEFTRELEASE, "LeftRelease"}, + {K_LEFTRELEASE_NM, "LeftReleaseNM"}, + {K_MIDDLEMOUSE, "MiddleMouse"}, + {K_MIDDLEDRAG, "MiddleDrag"}, + {K_MIDDLERELEASE, "MiddleRelease"}, + {K_RIGHTMOUSE, "RightMouse"}, + {K_RIGHTDRAG, "RightDrag"}, + {K_RIGHTRELEASE, "RightRelease"}, + {K_MOUSEDOWN, "ScrollWheelUp"}, + {K_MOUSEUP, "ScrollWheelDown"}, + {K_MOUSELEFT, "ScrollWheelRight"}, + {K_MOUSERIGHT, "ScrollWheelLeft"}, + {K_MOUSEDOWN, "MouseDown"}, // OBSOLETE: Use ScrollWheelXXX instead + {K_MOUSEUP, "MouseUp"}, // Same + {K_X1MOUSE, "X1Mouse"}, + {K_X1DRAG, "X1Drag"}, + {K_X1RELEASE, "X1Release"}, + {K_X2MOUSE, "X2Mouse"}, + {K_X2DRAG, "X2Drag"}, + {K_X2RELEASE, "X2Release"}, + {K_DROP, "Drop"}, + {K_ZERO, "Nul"}, + {K_SNR, "SNR"}, + {K_PLUG, "Plug"}, + {K_PASTE, "Paste"}, + {K_FOCUSGAINED, "FocusGained"}, + {K_FOCUSLOST, "FocusLost"}, {0, NULL} }; @@ -320,73 +318,73 @@ static struct mousetable { {0, 0, 0, 0}, }; -/* - * Return the modifier mask bit (MOD_MASK_*) which corresponds to the given - * modifier name ('S' for Shift, 'C' for Ctrl etc). - */ +/// Return the modifier mask bit (#MOD_MASK_*) corresponding to mod name +/// +/// E.g. 'S' for shift, 'C' for ctrl. int name_to_mod_mask(int c) + FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT { - int i; - c = TOUPPER_ASC(c); - for (i = 0; mod_mask_table[i].mod_mask != 0; i++) - if (c == mod_mask_table[i].name) + for (size_t i = 0; mod_mask_table[i].mod_mask != 0; i++) { + if (c == mod_mask_table[i].name) { return mod_mask_table[i].mod_flag; + } + } return 0; } -/* - * Check if if there is a special key code for "key" that includes the - * modifiers specified. - */ -int simplify_key(int key, int *modifiers) +/// Check if there is a special key code for "key" with specified modifiers +/// +/// @param[in] key Initial key code. +/// @param[in,out] modifiers Initial modifiers, is adjusted to have simplified +/// modifiers. +/// +/// @return Simplified key code. +int simplify_key(const int key, int *modifiers) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - int i; - int key0; - int key1; - if (*modifiers & (MOD_MASK_SHIFT | MOD_MASK_CTRL | MOD_MASK_ALT)) { - /* TAB is a special case */ + // TAB is a special case. if (key == TAB && (*modifiers & MOD_MASK_SHIFT)) { *modifiers &= ~MOD_MASK_SHIFT; return K_S_TAB; } - key0 = KEY2TERMCAP0(key); - key1 = KEY2TERMCAP1(key); - for (i = 0; modifier_keys_table[i] != NUL; i += MOD_KEYS_ENTRY_SIZE) + const int key0 = KEY2TERMCAP0(key); + const int key1 = KEY2TERMCAP1(key); + for (int i = 0; modifier_keys_table[i] != NUL; i += MOD_KEYS_ENTRY_SIZE) { if (key0 == modifier_keys_table[i + 3] && key1 == modifier_keys_table[i + 4] && (*modifiers & modifier_keys_table[i])) { *modifiers &= ~modifier_keys_table[i]; return TERMCAP2KEY(modifier_keys_table[i + 1], - modifier_keys_table[i + 2]); + modifier_keys_table[i + 2]); } + } } return key; } -/* - * Change to , to , etc. - */ -int handle_x_keys(int key) +/// Change to +int handle_x_keys(const int key) + FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT { switch (key) { - case K_XUP: return K_UP; - case K_XDOWN: return K_DOWN; - case K_XLEFT: return K_LEFT; - case K_XRIGHT: return K_RIGHT; - case K_XHOME: return K_HOME; - case K_ZHOME: return K_HOME; - case K_XEND: return K_END; - case K_ZEND: return K_END; - case K_XF1: return K_F1; - case K_XF2: return K_F2; - case K_XF3: return K_F3; - case K_XF4: return K_F4; - case K_S_XF1: return K_S_F1; - case K_S_XF2: return K_S_F2; - case K_S_XF3: return K_S_F3; - case K_S_XF4: return K_S_F4; + case K_XUP: return K_UP; + case K_XDOWN: return K_DOWN; + case K_XLEFT: return K_LEFT; + case K_XRIGHT: return K_RIGHT; + case K_XHOME: return K_HOME; + case K_ZHOME: return K_HOME; + case K_XEND: return K_END; + case K_ZEND: return K_END; + case K_XF1: return K_F1; + case K_XF2: return K_F2; + case K_XF3: return K_F3; + case K_XF4: return K_F4; + case K_S_XF1: return K_S_F1; + case K_S_XF2: return K_S_F2; + case K_S_XF3: return K_S_F3; + case K_S_XF4: return K_S_F4; } return key; } @@ -510,7 +508,7 @@ unsigned int trans_special(const char_u **srcp, const size_t src_len, return 0; } - /* Put the appropriate modifier in a string */ + // Put the appropriate modifier in a string. if (modifiers != 0) { dst[dlen++] = K_SPECIAL; dst[dlen++] = KS_MODIFIER; @@ -575,11 +573,7 @@ int find_special_key(const char_u **srcp, const size_t src_len, int *const modp, if (*bp == '-') { last_dash = bp; if (bp + 1 <= end) { - if (has_mbyte) { - l = mb_ptr2len_len(bp + 1, (int) (end - bp) + 1); - } else { - l = 1; - } + l = utfc_ptr2len_len(bp + 1, (int)(end - bp) + 1); // Anything accepted, like . // or are not special in strings as " is // the string delimiter. With a backslash it works: @@ -705,32 +699,35 @@ int find_special_key_in_table(int c) int i; for (i = 0; key_names_table[i].name != NULL; i++) - if (c == key_names_table[i].key) + if (c == (uint8_t)key_names_table[i].key) break; if (key_names_table[i].name == NULL) i = -1; return i; } -/* - * Find the special key with the given name (the given string does not have to - * end with NUL, the name is assumed to end before the first non-idchar). - * If the name starts with "t_" the next two characters are interpreted as a - * termcap name. - * Return the key code, or 0 if not found. - */ +/// Find the special key with the given name +/// +/// @param[in] name Name of the special. Does not have to end with NUL, it is +/// assumed to end before the first non-idchar. If name starts +/// with "t_" the next two characters are interpreted as +/// a termcap name. +/// +/// @return Key code or 0 if ton found. int get_special_key_code(const char_u *name) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - char_u *table_name; - int i, j; - - for (i = 0; key_names_table[i].name != NULL; i++) { - table_name = key_names_table[i].name; - for (j = 0; vim_isIDc(name[j]) && table_name[j] != NUL; j++) - if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC(name[j])) + for (int i = 0; key_names_table[i].name != NULL; i++) { + const char *const table_name = key_names_table[i].name; + int j; + for (j = 0; vim_isIDc(name[j]) && table_name[j] != NUL; j++) { + if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC(name[j])) { break; - if (!vim_isIDc(name[j]) && table_name[j] == NUL) + } + } + if (!vim_isIDc(name[j]) && table_name[j] == NUL) { return key_names_table[i].key; + } } return 0; -- cgit From af38cea133f5ebb67208cedd289e408cd1dad15a Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 8 Oct 2017 21:52:38 +0300 Subject: viml/parser/expressions: Add support for string parsing --- src/nvim/mbyte.h | 2 + src/nvim/viml/parser/expressions.c | 376 ++++++++++++++++++++++++++++++++++++- src/nvim/viml/parser/expressions.h | 7 + 3 files changed, 377 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h index bf6ccb13a5..fce600d0a9 100644 --- a/src/nvim/mbyte.h +++ b/src/nvim/mbyte.h @@ -73,6 +73,8 @@ typedef struct { extern const uint8_t utf8len_tab_zero[256]; +extern const uint8_t utf8len_tab[256]; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "mbyte.h.generated.h" #endif diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 0613cc66d5..3f30fe2a0e 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -915,8 +915,6 @@ static inline void viml_pexpr_debug_print_token( // // NVimIdentifierKey -> Identifier // -// NVimFigureBrace -> NVimInternalError -// // NVimUnaryPlus -> NVimUnaryOperator // NVimBinaryPlus -> NVimBinaryOperator // NVimConcat -> NVimBinaryOperator @@ -929,6 +927,18 @@ static inline void viml_pexpr_debug_print_token( // NVimNestingParenthesis -> NVimParenthesis // NVimCallingParenthesis -> NVimParenthesis // +// NVimString -> String +// NVimStringSpecial -> SpecialChar +// NVimSingleQuote -> NVimString +// NVimSingleQuotedBody -> NVimString +// NVimSingleQuotedQuote -> NVimStringSpecial +// NVimDoubleQuote -> NVimString +// NVimDoubleQuotedBody -> NVimString +// NVimDoubleQuotedEscape -> NVimStringSpecial +// NVimDoubleQuotedUnknownEscape -> NVimInvalid +// +// " Note: NVimDoubleQuotedUnknownEscape is not actually invalid +// // NVimInvalidComma -> NVimInvalidDelimiter // NVimInvalidSpacing -> NVimInvalid // NVimInvalidTernary -> NVimInvalidOperator @@ -952,6 +962,19 @@ static inline void viml_pexpr_debug_print_token( // NVimInvalidList -> NVimInvalidDelimiter // NVimInvalidSubscript -> NVimInvalidDelimiter // NVimInvalidSubscriptColon -> NVimInvalidSubscript +// NVimInvalidString -> NVimInvalidValue +// NVimInvalidStringSpecial -> NVimInvalidString +// NVimInvalidSingleQuote -> NVimInvalidString +// NVimInvalidSingleQuotedBody -> NVimInvalidString +// NVimInvalidSingleQuotedQuote -> NVimInvalidStringSpecial +// NVimInvalidDoubleQuote -> NVimInvalidString +// NVimInvalidDoubleQuotedBody -> NVimInvalidString +// NVimInvalidDoubleQuotedEscape -> NVimInvalidStringSpecial +// NVimInvalidDoubleQuotedUnknownEscape -> NVimInvalid +// +// NVimFigureBrace -> NVimInternalError +// NVimInvalidSingleQuotedUnknownEscape -> NVimInternalError +// NVimSingleQuotedUnknownEscape -> NVimInternalError /// Allocate a new node and set some of the values /// @@ -1402,6 +1425,318 @@ static inline void east_set_error(const ParserState *const pstate, } \ } while (0) +/// Structure used to define “string shifts” necessary to map string +/// highlighting to actual strings. +typedef struct { + size_t start; ///< Where special character starts in original string. + size_t orig_len; ///< Length of orininal string (e.g. 4 for "\x80"). + size_t act_len; ///< Length of resulting character(s) (e.g. 1 for "\x80"). + bool escape_not_known; ///< True if escape sequence in original is not known. +} StringShift; + +/// Parse and highlight single- or double-quoted string +/// +/// Function is supposed to detect and highlight regular expressions (but does +/// not do now). +/// +/// @param[out] pstate Parser state which also contains a place where +/// highlighting is saved. +/// @param[out] node Node where string parsing results are saved. +/// @param[in] token Token to highlight. +/// @param[in] ast_stack Parser AST stack, used to detect whether current +/// string is a regex. +/// @param[in] is_invalid Whether currently processed token is not valid. +static void parse_quoted_string(ParserState *const pstate, + ExprASTNode *const node, + const LexExprToken token, + const ExprASTStack ast_stack, + const bool is_invalid) + FUNC_ATTR_NONNULL_ALL +{ + const ParserLine pline = pstate->reader.lines.items[token.start.line]; + const char *const s = pline.data + token.start.col; + const char *const e = s + token.len - token.data.str.closed; + const char *p = s + 1; + const bool is_double = (token.type == kExprLexDoubleQuotedString); + size_t size = token.len - token.data.str.closed - 1; + kvec_withinit_t(StringShift, 16) shifts; + kvi_init(shifts); + if (!is_double) { + viml_parser_highlight(pstate, token.start, 1, HL(SingleQuotedString)); + while (p < e) { + const char *const chunk_e = memchr(p, '\'', (size_t)(e - p)); + if (chunk_e == NULL) { + break; + } + size--; + p = chunk_e + 2; + if (pstate->colors) { + kvi_push(shifts, ((StringShift) { + .start = token.start.col + (size_t)(chunk_e - s), + .orig_len = 2, + .act_len = 1, + .escape_not_known = false, + })); + } + } + node->data.str.size = size; + if (size == 0) { + node->data.str.value = NULL; + } else { + char *v_p; + v_p = node->data.str.value = xmalloc(size); + p = s + 1; + while (p < e) { + const char *const chunk_e = memchr(p, '\'', (size_t)(e - p)); + if (chunk_e == NULL) { + memcpy(v_p, p, (size_t)(e - p)); + break; + } + memcpy(v_p, p, (size_t)(chunk_e - p)); + v_p += (size_t)(chunk_e - p) + 1; + v_p[-1] = '\''; + p = chunk_e + 2; + } + } + } else { + viml_parser_highlight(pstate, token.start, 1, HL(DoubleQuotedString)); + for (p = s + 1; p < e; p++) { + if (*p == '\\' && p + 1 < e) { + p++; + if (p + 1 == e) { + size--; + break; + } + switch (*p) { + // A "\" form occupies at least 4 characters, and produces up to + // 6 characters: reserve space for 2 extra, but do not compute actual + // length just now, it would be costy. + case '<': { + size += 2; + break; + } + // Hexadecimal, always single byte, but at least three bytes each. + case 'x': case 'X': { + size--; + if (ascii_isxdigit(p[1])) { + size--; + if (p + 2 < e && ascii_isxdigit(p[2])) { + size--; + } + } + break; + } + // Unicode + // + // \uF takes 1 byte which is 2 bytes less then escape sequence. + // \uFF: 2 bytes, 2 bytes less. + // \uFFF: 3 bytes, 2 bytes less. + // \uFFFF: 3 bytes, 3 bytes less. + // \UFFFFF: 4 bytes, 3 bytes less. + // \UFFFFFF: 5 bytes, 3 bytes less. + // \UFFFFFFF: 6 bytes, 3 bytes less. + // \U7FFFFFFF: 6 bytes, 4 bytes less. + case 'u': case 'U': { + const char *const esc_start = p; + size_t n = (*p == 'u' ? 4 : 8); + int nr = 0; + p++; + while (n-- && ascii_isxdigit(p[1])) { + p++; + nr = (nr << 4) + hex2nr(*p); + } + // Escape length: (esc_start - 1) points to "\\", esc_start to "u" + // or "U", p to the byte after last byte. So escape sequence + // occupies p - (esc_start - 1), but it stands for a utf_char2len + // bytes. + size -= (size_t)((p - (esc_start - 1)) - utf_char2len(nr)); + p--; + break; + } + // Octal, always single byte, but at least two bytes each. + case '0': case '1': case '2': case '3': case '4': case '5': case '6': + case '7': { + size--; + p++; + if (*p >= '0' && *p <= '7') { + size--; + p++; + if (*p >= '0' && *p <= '7') { + size--; + p++; + } + } + break; + } + default: { + size--; + break; + } + } + } + } + if (size == 0) { + node->data.str.value = NULL; + node->data.str.size = 0; + } else { + char *v_p; + v_p = node->data.str.value = xmalloc(size); + p = s + 1; + while (p < e) { + const char *const chunk_e = memchr(p, '\\', (size_t)(e - p)); + if (chunk_e == NULL) { + memcpy(v_p, p, (size_t)(e - p)); + v_p += e - p; + break; + } + memcpy(v_p, p, (size_t)(chunk_e - p)); + v_p += (size_t)(chunk_e - p); + p = chunk_e + 1; + if (p == e) { + *v_p++ = '\\'; + break; + } + bool is_unknown = false; + const char *const v_p_start = v_p; + switch (*p) { +#define SINGLE_CHAR_ESC(ch, real_ch) \ + case ch: { \ + *v_p++ = real_ch; \ + p++; \ + break; \ + } + SINGLE_CHAR_ESC('b', BS) + SINGLE_CHAR_ESC('e', ESC) + SINGLE_CHAR_ESC('f', FF) + SINGLE_CHAR_ESC('n', NL) + SINGLE_CHAR_ESC('r', CAR) + SINGLE_CHAR_ESC('t', TAB) + SINGLE_CHAR_ESC('"', '"') + SINGLE_CHAR_ESC('\\', '\\') +#undef SINGLE_CHAR_ESC + + // Hexadecimal or unicode. + case 'X': case 'x': case 'u': case 'U': { + if (ascii_isxdigit(p[1])) { + size_t n; + int nr; + bool is_hex = (*p == 'x' || *p == 'X'); + + if (is_hex) { + n = 2; + } else if (*p == 'u') { + n = 4; + } else { + n = 8; + } + nr = 0; + while (n-- && ascii_isxdigit(p[1])) { + p++; + nr = (nr << 4) + hex2nr(*p); + } + p++; + if (is_hex) { + *v_p++ = (char)nr; + } else { + v_p += utf_char2bytes(nr, (char_u *)v_p); + } + } else { + is_unknown = true; + *v_p++ = *p; + p++; + } + break; + } + // Octal: "\1", "\12", "\123". + case '0': case '1': case '2': case '3': case '4': case '5': case '6': + case '7': { + uint8_t ch = (uint8_t)(*p++ - '0'); + if (*p >= '0' && *p <= '7') { + ch = (uint8_t)((ch << 3) + *p++ - '0'); + if (*p >= '0' && *p <= '7') { + ch = (uint8_t)((ch << 3) + *p++ - '0'); + } + } + *v_p++ = (char)ch; + break; + } + // Special key, e.g.: "\" + case '<': { + const size_t special_len = ( + trans_special((const char_u **)&p, (size_t)(e - p), + (char_u *)v_p, true, true)); + if (special_len != 0) { + v_p += special_len; + } else { + is_unknown = true; + mb_copy_char((const char_u **)&p, (char_u **)&v_p); + } + break; + } + default: { + is_unknown = true; + mb_copy_char((const char_u **)&p, (char_u **)&v_p); + break; + } + } + if (pstate->colors) { + kvi_push(shifts, ((StringShift) { + .start = token.start.col + (size_t)(chunk_e - s), + .orig_len = (size_t)(p - chunk_e), + .act_len = (size_t)(v_p - (char *)v_p_start), + .escape_not_known = is_unknown, + })); + } + } + node->data.str.size = (size_t)(v_p - node->data.str.value); + } + } + if (pstate->colors) { + // TODO(ZyX-I): use ast_stack to determine and highlight regular expressions + // TODO(ZyX-I): use ast_stack to determine and highlight printf format str + // TODO(ZyX-I): use ast_stack to determine and highlight expression strings + size_t next_col = 1; + const char *const body_str = (is_double + ? HL(DoubleQuotedBody) + : HL(SingleQuotedBody)); + const char *const esc_str = (is_double + ? HL(DoubleQuotedEscape) + : HL(SingleQuotedQuote)); + const char *const ukn_esc_str = (is_double + ? HL(DoubleQuotedUnknownEscape) + : HL(SingleQuotedUnknownEscape)); + for (size_t i = 0; i < kv_size(shifts); i++) { + const StringShift cur_shift = kv_A(shifts, i); + if (cur_shift.start > next_col) { + viml_parser_highlight(pstate, shifted_pos(token.start, next_col), + cur_shift.start - next_col, + body_str); + } + viml_parser_highlight(pstate, shifted_pos(token.start, cur_shift.start), + cur_shift.orig_len, + (cur_shift.escape_not_known + ? ukn_esc_str + : esc_str)); + next_col = cur_shift.start + cur_shift.orig_len; + } + if (next_col < token.len - token.data.str.closed) { + viml_parser_highlight(pstate, shifted_pos(token.start, next_col), + token.len - token.data.str.closed - next_col, + body_str); + } + } + if (token.data.str.closed) { + if (is_double) { + viml_parser_highlight(pstate, shifted_pos(token.start, token.len - 1), + 1, HL(DoubleQuotedString)); + } else { + viml_parser_highlight(pstate, shifted_pos(token.start, token.len - 1), + 1, HL(SingleQuotedString)); + } + } + kvi_destroy(shifts); +} + /// Parse one VimL expression /// /// @param pstate Parser state. @@ -1714,12 +2049,7 @@ viml_pexpr_parse_invalid_comma: } else if (eastnode_lvl >= kEOpLvlComma) { can_be_ternary = false; } else { -viml_pexpr_parse_invalid_colon: - ERROR_FROM_TOKEN_AND_MSG( - cur_token, - _("E15: Colon outside of dictionary or ternary operator: " - "%.*s")); - break; + goto viml_pexpr_parse_invalid_colon; } if (i == kv_size(ast_stack) - 1) { goto viml_pexpr_parse_invalid_colon; @@ -1741,6 +2071,12 @@ viml_pexpr_parse_invalid_colon: ADD_OP_NODE(cur_node); HL_CUR_TOKEN(SubscriptColon); } else { + goto viml_pexpr_parse_valid_colon; +viml_pexpr_parse_invalid_colon: + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Colon outside of dictionary or ternary operator: %.*s")); +viml_pexpr_parse_valid_colon: ADD_VALUE_IF_MISSING(_(EXP_VAL_COLON)); NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); if (is_ternary) { @@ -2201,6 +2537,30 @@ viml_pexpr_parse_no_paren_closing_error: {} kvi_push(ast_stack, &ter_val_node->children); break; } + case kExprLexDoubleQuotedString: + case kExprLexSingleQuotedString: { + const bool is_double = (tok_type == kExprLexDoubleQuotedString); + if (!cur_token.data.str.closed) { + // It is weird, but Vim has two identical errors messages with + // different error numbers: "E114: Missing quote" and + // "E115: Missing quote". + ERROR_FROM_TOKEN_AND_MSG( + cur_token, (is_double + ? _("E114: Missing double quote: %.*s") + : _("E115: Missing single quote: %.*s"))); + } + if (want_node == kENodeOperator) { + OP_MISSING; + } + NEW_NODE_WITH_CUR_POS( + cur_node, (is_double + ? kExprNodeDoubleQuotedString + : kExprNodeSingleQuotedString)); + *top_node_p = cur_node; + parse_quoted_string(pstate, cur_node, cur_token, ast_stack, is_invalid); + want_node = kENodeOperator; + break; + } } viml_pexpr_parse_cycle_end: prev_token = cur_token; diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 0d496c87ba..a09cdde4c0 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -195,6 +195,8 @@ typedef enum { kExprNodeConcatOrSubscript = 'S', kExprNodeInteger = '0', ///< Integral number. kExprNodeFloat = '1', ///< Floating-point number. + kExprNodeSingleQuotedString = '\'', + kExprNodeDoubleQuotedString = '"', } ExprASTNodeType; typedef struct expr_ast_node ExprASTNode; @@ -249,6 +251,11 @@ struct expr_ast_node { struct { float_T value; } flt; ///< For kExprNodeFloat. + struct { + char *value; + size_t size; + } str; ///< For kExprNodeSingleQuotedString and + ///< kExprNodeDoubleQuotedString. } data; }; -- cgit From fa3cfc0dd54df125a1dbabccda47a5f45dc483ae Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 9 Oct 2017 02:55:56 +0300 Subject: viml/parser/expressions: Finish parser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Note: formatc.lua was unable to swallow some newer additions to ExprASTNodeType (specifically `kExprNodeOr = '|'` and probably something else), so all `= …` were dropped: in any case they only were there in order to not bother updating viml_pexpr_debug_print_ast_node and since it is now known all nodes which will be present it is not much of an issue. --- src/nvim/viml/parser/expressions.c | 369 +++++++++++++++++++++++++------------ src/nvim/viml/parser/expressions.h | 115 ++++++++---- src/nvim/viml/parser/parser.h | 2 +- 3 files changed, 331 insertions(+), 155 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 3f30fe2a0e..75fcb17bf6 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -361,11 +361,12 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) // Scope: `s:`, etc. } else if (ret.len == 1 && pline.size > 1 - && strchr("sgvbwtla", schar) != NULL + && memchr(EXPR_VAR_SCOPE_LIST, schar, + sizeof(EXPR_VAR_SCOPE_LIST)) != NULL && pline.data[ret.len] == ':' && !(flags & kELFlagForbidScope)) { ret.len++; - ret.data.var.scope = schar; + ret.data.var.scope = (ExprVarScope)schar; CHARREG(kExprLexPlainIdentifier, ISWORD_OR_AUTOLOAD); ret.data.var.autoload = ( memchr(pline.data + 2, AUTOLOAD_CHAR, ret.len - 2) @@ -408,14 +409,13 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) ret.type = kExprLexOption; if (pline.size > 2 && pline.data[2] == ':' - && strchr("gl", pline.data[1]) != NULL) { + && memchr(EXPR_OPT_SCOPE_LIST, pline.data[1], + sizeof(EXPR_OPT_SCOPE_LIST)) != NULL) { ret.len += 2; - ret.data.opt.scope = (pline.data[1] == 'g' - ? kExprLexOptGlobal - : kExprLexOptLocal); + ret.data.opt.scope = (ExprOptScope)pline.data[1]; ret.data.opt.name = pline.data + 3; } else { - ret.data.opt.scope = kExprLexOptUnspecified; + ret.data.opt.scope = kExprOptScopeUnspecified; ret.data.opt.name = pline.data + 1; } const char *p = ret.data.opt.name; @@ -637,9 +637,9 @@ static const char *const eltkn_mul_type_tab[] = { }; static const char *const eltkn_opt_scope_tab[] = { - [kExprLexOptUnspecified] = "Unspecified", - [kExprLexOptGlobal] = "Global", - [kExprLexOptLocal] = "Local", + [kExprOptScopeUnspecified] = "Unspecified", + [kExprOptScopeGlobal] = "Global", + [kExprOptScopeLocal] = "Local", }; /// Represent `int` character as a string @@ -990,67 +990,25 @@ static inline ExprASTNode *viml_pexpr_new_node(const ExprASTNodeType type) return ret; } -static const ExprOpLvl node_type_to_op_lvl[] = { - [kExprNodeMissing] = kEOpLvlInvalid, - [kExprNodeOpMissing] = kEOpLvlMultiplication, +static struct { + ExprOpLvl lvl; + ExprOpAssociativity ass; +} node_type_to_node_props[] = { + [kExprNodeMissing] = { kEOpLvlInvalid, kEOpAssNo, }, + [kExprNodeOpMissing] = { kEOpLvlMultiplication, kEOpAssNo }, - [kExprNodeNested] = kEOpLvlParens, + [kExprNodeNested] = { kEOpLvlParens, kEOpAssNo }, // Note: below nodes are kEOpLvlSubscript for “binary operator” itself, but // kEOpLvlParens when it comes to inside the parenthesis. - [kExprNodeCall] = kEOpLvlParens, - [kExprNodeSubscript] = kEOpLvlParens, + [kExprNodeCall] = { kEOpLvlParens, kEOpAssNo }, + [kExprNodeSubscript] = { kEOpLvlParens, kEOpAssNo }, - [kExprNodeUnknownFigure] = kEOpLvlParens, - [kExprNodeLambda] = kEOpLvlParens, - [kExprNodeDictLiteral] = kEOpLvlParens, - [kExprNodeListLiteral] = kEOpLvlParens, + [kExprNodeUnknownFigure] = { kEOpLvlParens, kEOpAssLeft }, + [kExprNodeLambda] = { kEOpLvlParens, kEOpAssNo }, + [kExprNodeDictLiteral] = { kEOpLvlParens, kEOpAssNo }, + [kExprNodeListLiteral] = { kEOpLvlParens, kEOpAssNo }, - [kExprNodeArrow] = kEOpLvlArrow, - - [kExprNodeComma] = kEOpLvlComma, - - [kExprNodeColon] = kEOpLvlColon, - - [kExprNodeTernary] = kEOpLvlTernary, - - [kExprNodeTernaryValue] = kEOpLvlTernaryValue, - - [kExprNodeComparison] = kEOpLvlComparison, - - [kExprNodeBinaryPlus] = kEOpLvlAddition, - [kExprNodeConcat] = kEOpLvlAddition, - - [kExprNodeUnaryPlus] = kEOpLvlUnary, - - [kExprNodeConcatOrSubscript] = kEOpLvlSubscript, - - [kExprNodeCurlyBracesIdentifier] = kEOpLvlComplexIdentifier, - - [kExprNodeComplexIdentifier] = kEOpLvlValue, - [kExprNodePlainIdentifier] = kEOpLvlValue, - [kExprNodePlainKey] = kEOpLvlValue, - [kExprNodeRegister] = kEOpLvlValue, - [kExprNodeInteger] = kEOpLvlValue, - [kExprNodeFloat] = kEOpLvlValue, -}; - -static const ExprOpAssociativity node_type_to_op_ass[] = { - [kExprNodeMissing] = kEOpAssNo, - [kExprNodeOpMissing] = kEOpAssNo, - - [kExprNodeNested] = kEOpAssNo, - [kExprNodeCall] = kEOpAssNo, - [kExprNodeSubscript] = kEOpAssNo, - - [kExprNodeUnknownFigure] = kEOpAssLeft, - [kExprNodeLambda] = kEOpAssNo, - [kExprNodeDictLiteral] = kEOpAssNo, - [kExprNodeListLiteral] = kEOpAssNo, - - // Does not really matter. - [kExprNodeArrow] = kEOpAssNo, - - [kExprNodeColon] = kEOpAssNo, + [kExprNodeArrow] = { kEOpLvlArrow, kEOpAssNo }, // Right associativity for comma because this means easier access to arguments // list, etc: for "[a, b, c, d]" you can access "a" in one step if it is @@ -1059,29 +1017,48 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { // traverse all three comma() structures. And with comma operator (including // actual comma operator from C which is not present in VimL) nobody cares // about associativity, only about order of execution. - [kExprNodeComma] = kEOpAssRight, + [kExprNodeComma] = { kEOpLvlComma, kEOpAssRight }, + + // Colons are not eligible for chaining, so nobody cares about associativity. + [kExprNodeColon] = { kEOpLvlColon, kEOpAssNo }, + + [kExprNodeTernary] = { kEOpLvlTernary, kEOpAssRight }, - [kExprNodeTernary] = kEOpAssRight, + [kExprNodeOr] = { kEOpLvlOr, kEOpAssLeft }, - [kExprNodeTernaryValue] = kEOpAssRight, + [kExprNodeAnd] = { kEOpLvlAnd, kEOpAssLeft }, - [kExprNodeComparison] = kEOpAssRight, + [kExprNodeTernaryValue] = { kEOpLvlTernaryValue, kEOpAssRight }, - [kExprNodeBinaryPlus] = kEOpAssLeft, - [kExprNodeConcat] = kEOpAssLeft, + [kExprNodeComparison] = { kEOpLvlComparison, kEOpAssRight }, - [kExprNodeUnaryPlus] = kEOpAssNo, + [kExprNodeBinaryPlus] = { kEOpLvlAddition, kEOpAssLeft }, + [kExprNodeBinaryMinus] = { kEOpLvlAddition, kEOpAssLeft }, + [kExprNodeConcat] = { kEOpLvlAddition, kEOpAssLeft }, - [kExprNodeConcatOrSubscript] = kEOpAssLeft, + [kExprNodeMultiplication] = { kEOpLvlMultiplication, kEOpAssLeft }, + [kExprNodeDivision] = { kEOpLvlMultiplication, kEOpAssLeft }, + [kExprNodeMod] = { kEOpLvlMultiplication, kEOpAssLeft }, - [kExprNodeCurlyBracesIdentifier] = kEOpAssLeft, + [kExprNodeUnaryPlus] = { kEOpLvlUnary, kEOpAssNo }, + [kExprNodeUnaryMinus] = { kEOpLvlUnary, kEOpAssNo }, + [kExprNodeNot] = { kEOpLvlUnary, kEOpAssNo }, - [kExprNodeComplexIdentifier] = kEOpAssLeft, - [kExprNodePlainIdentifier] = kEOpAssNo, - [kExprNodePlainKey] = kEOpAssNo, - [kExprNodeRegister] = kEOpAssNo, - [kExprNodeInteger] = kEOpAssNo, - [kExprNodeFloat] = kEOpAssNo, + [kExprNodeConcatOrSubscript] = { kEOpLvlSubscript, kEOpAssLeft }, + + [kExprNodeCurlyBracesIdentifier] = { kEOpLvlComplexIdentifier, kEOpAssLeft }, + + [kExprNodeComplexIdentifier] = { kEOpLvlValue, kEOpAssLeft }, + + [kExprNodePlainIdentifier] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodePlainKey] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeRegister] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeInteger] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeFloat] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeDoubleQuotedString] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeSingleQuotedString] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeOption] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeEnvironment] = { kEOpLvlValue, kEOpAssNo }, }; /// Get AST node priority level @@ -1094,7 +1071,7 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { static inline ExprOpLvl node_lvl(const ExprASTNode node) FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT { - return node_type_to_op_lvl[node.type]; + return node_type_to_node_props[node.type].lvl; } /// Get AST node associativity, to be used for operator nodes primary @@ -1107,7 +1084,7 @@ static inline ExprOpLvl node_lvl(const ExprASTNode node) static inline ExprOpAssociativity node_ass(const ExprASTNode node) FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT { - return node_type_to_op_ass[node.type]; + return node_type_to_node_props[node.type].ass; } /// Handle binary operator @@ -1837,7 +1814,7 @@ viml_pexpr_parse_process_token: is_concat_or_subscript && (cur_token.type == kExprLexPlainIdentifier ? (!cur_token.data.var.autoload - && cur_token.data.var.scope == 0) + && cur_token.data.var.scope == kExprVarScopeMissing) : (cur_token.type == kExprLexNumber)) && prev_token.type != kExprLexSpacing); if (is_concat_or_subscript && !node_is_key) { @@ -1856,7 +1833,7 @@ viml_pexpr_parse_process_token: && tok_type != kExprLexArrow) || (want_node == kENodeArgument && !(cur_token.type == kExprLexPlainIdentifier - && cur_token.data.var.scope == 0 + && cur_token.data.var.scope == kExprVarScopeMissing && !cur_token.data.var.autoload) && tok_type != kExprLexArrow)) { lambda_node->data.fig.type_guesses.allow_lambda = false; @@ -1885,6 +1862,8 @@ viml_pexpr_parse_process_token: || want_node == kENodeArgumentSeparator || want_node == kENodeArgument); switch (tok_type) { + case kExprLexMissing: + case kExprLexSpacing: case kExprLexEOC: { assert(false); } @@ -1894,31 +1873,111 @@ viml_pexpr_parse_process_token: goto viml_pexpr_parse_process_token; } case kExprLexRegister: { - if (want_node == kENodeValue) { - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeRegister); - cur_node->data.reg.name = cur_token.data.reg.name; - *top_node_p = cur_node; - want_node = kENodeOperator; - HL_CUR_TOKEN(Register); - } else { + if (want_node == kENodeOperator) { // Register in operator position: e.g. @a @a OP_MISSING; } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeRegister); + cur_node->data.reg.name = cur_token.data.reg.name; + *top_node_p = cur_node; + want_node = kENodeOperator; + HL_CUR_TOKEN(Register); break; } - case kExprLexPlus: { - if (want_node == kENodeValue) { - // Value level: assume unary plus - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnaryPlus); - *top_node_p = cur_node; - kvi_push(ast_stack, &cur_node->children); - HL_CUR_TOKEN(UnaryPlus); - } else { - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeBinaryPlus); - ADD_OP_NODE(cur_node); - HL_CUR_TOKEN(BinaryPlus); +#define SIMPLE_UB_OP(op) \ + case kExprLex##op: { \ + if (want_node == kENodeValue) { \ + /* Value level: assume unary operator. */ \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnary##op); \ + *top_node_p = cur_node; \ + kvi_push(ast_stack, &cur_node->children); \ + HL_CUR_TOKEN(Unary##op); \ + } else { \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeBinary##op); \ + ADD_OP_NODE(cur_node); \ + HL_CUR_TOKEN(Binary##op); \ + } \ + want_node = kENodeValue; \ + break; \ + } + SIMPLE_UB_OP(Plus) + SIMPLE_UB_OP(Minus) +#undef SIMPLE_UB_OP +#define SIMPLE_B_OP(op, msg) \ + case kExprLex##op: { \ + ADD_VALUE_IF_MISSING(_("E15: Unexpected " msg ": %.*s")); \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNode##op); \ + HL_CUR_TOKEN(op); \ + ADD_OP_NODE(cur_node); \ + break; \ + } + SIMPLE_B_OP(Or, "or operator") + SIMPLE_B_OP(And, "and operator") +#undef SIMPLE_B_OP + case kExprLexMultiplication: { + ADD_VALUE_IF_MISSING( + _("E15: Unexpected multiplication-like operator: %.*s")); + switch (cur_token.data.mul.type) { +#define MUL_OP(lex_op_tail, node_op_tail) \ + case kExprLexMul##lex_op_tail: { \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNode##node_op_tail); \ + HL_CUR_TOKEN(node_op_tail); \ + break; \ + } + MUL_OP(Mul, Multiplication) + MUL_OP(Div, Division) + MUL_OP(Mod, Mod) +#undef MUL_OP } - want_node = kENodeValue; + ADD_OP_NODE(cur_node); + break; + } + case kExprLexOption: { + if (want_node == kENodeOperator) { + OP_MISSING; + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeOption); + cur_node->data.opt.ident = cur_token.data.opt.name; + cur_node->data.opt.ident_len = cur_token.data.opt.len; + cur_node->data.opt.scope = cur_token.data.opt.scope; + *top_node_p = cur_node; + want_node = kENodeOperator; + viml_parser_highlight(pstate, cur_token.start, 1, HL(OptionSigil)); + const size_t scope_shift = ( + cur_token.data.opt.scope == kExprOptScopeUnspecified ? 0 : 2); + if (scope_shift) { + viml_parser_highlight(pstate, shifted_pos(cur_token.start, 1), 1, + HL(OptionScope)); + viml_parser_highlight(pstate, shifted_pos(cur_token.start, 2), 1, + HL(OptionScopeDelimiter)); + } + viml_parser_highlight( + pstate, shifted_pos(cur_token.start, scope_shift + 1), + cur_token.len - scope_shift + 1, HL(Option)); + break; + } + case kExprLexEnv: { + if (want_node == kENodeOperator) { + OP_MISSING; + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeEnvironment); + cur_node->data.env.ident = pline.data + cur_token.start.col + 1; + cur_node->data.env.ident_len = cur_token.len - 1; + *top_node_p = cur_node; + want_node = kENodeOperator; + viml_parser_highlight(pstate, cur_token.start, 1, HL(EnvironmentSigil)); + viml_parser_highlight(pstate, shifted_pos(cur_token.start, 1), + cur_token.len - 1, HL(Environment)); + break; + } + case kExprLexNot: { + if (want_node == kENodeOperator) { + OP_MISSING; + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeNot); + *top_node_p = cur_node; + kvi_push(ast_stack, &cur_node->children); + HL_CUR_TOKEN(Not); break; } case kExprLexComparison: { @@ -2359,9 +2418,8 @@ viml_pexpr_parse_figure_brace_closing_error: ? kExprNodePlainKey : kExprNodePlainIdentifier)); cur_node->data.var.scope = cur_token.data.var.scope; - const size_t scope_shift = (cur_token.data.var.scope == 0 - ? 0 - : 2); + const size_t scope_shift = ( + cur_token.data.var.scope == kExprVarScopeMissing ? 0 : 2); cur_node->data.var.ident = (pline.data + cur_token.start.col + scope_shift); cur_node->data.var.ident_len = cur_token.len - scope_shift; @@ -2373,16 +2431,14 @@ viml_pexpr_parse_figure_brace_closing_error: viml_parser_highlight(pstate, shifted_pos(cur_token.start, 1), 1, HL(IdentifierScopeDelimiter)); } - if (scope_shift < cur_token.len) { - viml_parser_highlight(pstate, shifted_pos(cur_token.start, - scope_shift), - cur_token.len - scope_shift, - (node_is_key - ? HL(IdentifierKey) - : HL(Identifier))); - } + viml_parser_highlight(pstate, shifted_pos(cur_token.start, + scope_shift), + cur_token.len - scope_shift, + (node_is_key + ? HL(IdentifierKey) + : HL(Identifier))); } else { - if (cur_token.data.var.scope == 0) { + if (cur_token.data.var.scope == kExprVarScopeMissing) { ADD_IDENT( do { NEW_NODE_WITH_CUR_POS(cur_node, kExprNodePlainIdentifier); @@ -2606,9 +2662,85 @@ viml_pexpr_parse_end: cur_node->start); break; } - case kExprNodeBinaryPlus: + case kExprNodeListLiteral: { + // For whatever reason "[1" yields "E696: Missing comma in list" error + // in Vim while "[1," yields E697. + east_set_error( + pstate, &ast.err, + _("E697: Missing end of List ']': %.*s"), + cur_node->start); + break; + } + case kExprNodeDictLiteral: { + // Same problem like with list literal with E722 (missing comma) vs + // E723, but additionally just "{" yields only E15. + east_set_error( + pstate, &ast.err, + _("E723: Missing end of Dictionary '}': %.*s"), + cur_node->start); + break; + } + case kExprNodeUnknownFigure: { + east_set_error( + pstate, &ast.err, + _("E15: Missing closing figure brace: %.*s"), + cur_node->start); + break; + } + case kExprNodeLambda: { + east_set_error( + pstate, &ast.err, + _("E15: Missing closing figure brace for lambda: %.*s"), + cur_node->start); + break; + } + case kExprNodeCurlyBracesIdentifier: { + // Until trailing "}" it is impossible to distinguish curly braces + // identifier and dictionary, so it must not appear in the stack like + // this. + assert(false); + } + case kExprNodeInteger: + case kExprNodeFloat: + case kExprNodeSingleQuotedString: + case kExprNodeDoubleQuotedString: + case kExprNodeOption: + case kExprNodeEnvironment: + case kExprNodeRegister: + case kExprNodePlainIdentifier: + case kExprNodePlainKey: { + // These are plain values and not containers, for them it should only + // be possible to show up in the topmost stack element, but it was + // unconditionally popped at the start. + assert(false); + } + case kExprNodeComma: + case kExprNodeColon: + case kExprNodeArrow: { + // It is actually only valid inside something else, but everything + // where one of the above is valid requires to be closed and thus is + // to be caught later. + break; + } + case kExprNodeConcatOrSubscript: + case kExprNodeComplexIdentifier: + case kExprNodeSubscript: { + // FIXME: Investigate whether above are OK to be present in the stack. + break; + } + case kExprNodeMod: + case kExprNodeDivision: + case kExprNodeMultiplication: + case kExprNodeNot: + case kExprNodeAnd: + case kExprNodeOr: + case kExprNodeConcat: + case kExprNodeComparison: + case kExprNodeUnaryMinus: case kExprNodeUnaryPlus: - case kExprNodeRegister: { + case kExprNodeBinaryMinus: + case kExprNodeTernary: + case kExprNodeBinaryPlus: { // It is OK to see these in the stack. break; } @@ -2621,7 +2753,6 @@ viml_pexpr_parse_end: } break; } - // TODO(ZyX-I): handle other values } } } diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index a09cdde4c0..0198852bed 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -61,6 +61,36 @@ typedef enum { kExprCmpIdentical, ///< `is` or `isnot` } ExprComparisonType; +/// All possible option scopes +typedef enum { + kExprOptScopeUnspecified = 0, + kExprOptScopeGlobal = 'g', + kExprOptScopeLocal = 'l', +} ExprOptScope; + +#define EXPR_OPT_SCOPE_LIST \ + ((char *)(char[]){ kExprOptScopeGlobal, kExprOptScopeLocal }) + +/// All possible variable scopes +typedef enum { + kExprVarScopeMissing = 0, + kExprVarScopeScript = 's', + kExprVarScopeGlobal = 'g', + kExprVarScopeVim = 'v', + kExprVarScopeBuffer = 'b', + kExprVarScopeWindow = 'w', + kExprVarScopeTabpage = 't', + kExprVarScopeLocal = 'l', + kExprVarScopeArguments = 'a', +} ExprVarScope; + +#define EXPR_VAR_SCOPE_LIST \ + ((char[]) { \ + kExprVarScopeScript, kExprVarScopeGlobal, kExprVarScopeVim, \ + kExprVarScopeBuffer, kExprVarScopeWindow, kExprVarScopeTabpage, \ + kExprVarScopeLocal, kExprVarScopeBuffer, kExprVarScopeArguments, \ + }) + /// Lexer token typedef struct { ParserPosition start; @@ -96,15 +126,11 @@ typedef struct { struct { const char *name; ///< Option name start. size_t len; ///< Option name length. - enum { - kExprLexOptUnspecified = 0, - kExprLexOptGlobal = 1, - kExprLexOptLocal = 2, - } scope; ///< Option scope: &l:, &g: or not specified. + ExprOptScope scope; ///< Option scope: &l:, &g: or not specified. } opt; ///< Option properties. struct { - int scope; ///< Scope character or 0 if not present. + ExprVarScope scope; ///< Scope character or 0 if not present. bool autoload; ///< Has autoload characters. } var; ///< For kExprLexPlainIdentifier @@ -150,53 +176,63 @@ typedef enum { /// Expression AST node type typedef enum { - kExprNodeMissing = 'X', - kExprNodeOpMissing = '_', - kExprNodeTernary = '?', ///< Ternary operator. - kExprNodeTernaryValue = 'C', ///< Ternary operator, colon. - kExprNodeRegister = '@', ///< Register. - kExprNodeSubscript = 's', ///< Subscript. - kExprNodeListLiteral = 'l', ///< List literal. - kExprNodeUnaryPlus = 'p', - kExprNodeBinaryPlus = '+', - kExprNodeNested = 'e', ///< Nested parenthesised expression. - kExprNodeCall = 'c', ///< Function call. + kExprNodeMissing = 0, + kExprNodeOpMissing, + kExprNodeTernary, ///< Ternary operator. + kExprNodeTernaryValue, ///< Ternary operator, colon. + kExprNodeRegister, ///< Register. + kExprNodeSubscript, ///< Subscript. + kExprNodeListLiteral, ///< List literal. + kExprNodeUnaryPlus, + kExprNodeBinaryPlus, + kExprNodeNested, ///< Nested parenthesised expression. + kExprNodeCall, ///< Function call. /// Plain identifier: simple variable/function name /// /// Looks like "string", "g:Foo", etc: consists from a single /// kExprLexPlainIdentifier token. - kExprNodePlainIdentifier = 'i', + kExprNodePlainIdentifier, /// Plain dictionary key, for use with kExprNodeConcatOrSubscript - kExprNodePlainKey = 'k', + kExprNodePlainKey, /// Complex identifier: variable/function name with curly braces - kExprNodeComplexIdentifier = 'I', + kExprNodeComplexIdentifier, /// Figure brace expression which is not yet known /// /// May resolve to any of kExprNodeDictLiteral, kExprNodeLambda or /// kExprNodeCurlyBracesIdentifier. - kExprNodeUnknownFigure = '{', - kExprNodeLambda = '\\', ///< Lambda. - kExprNodeDictLiteral = 'd', ///< Dictionary literal. - kExprNodeCurlyBracesIdentifier= '}', ///< Part of the curly braces name. - kExprNodeComma = ',', ///< Comma “operator”. - kExprNodeColon = ':', ///< Colon “operator”. - kExprNodeArrow = '>', ///< Arrow “operator”. - kExprNodeComparison = '=', ///< Various comparison operators. + kExprNodeUnknownFigure, + kExprNodeLambda, ///< Lambda. + kExprNodeDictLiteral, ///< Dictionary literal. + kExprNodeCurlyBracesIdentifier, ///< Part of the curly braces name. + kExprNodeComma, ///< Comma “operator”. + kExprNodeColon, ///< Colon “operator”. + kExprNodeArrow, ///< Arrow “operator”. + kExprNodeComparison, ///< Various comparison operators. /// Concat operator /// /// To be only used in cases when it is known for sure it is not a subscript. - kExprNodeConcat = '.', + kExprNodeConcat, /// Concat or subscript operator /// /// For cases when it is not obvious whether expression is a concat or /// a subscript. May only have either number or plain identifier as the second /// child. To make it easier to avoid curly braces in place of /// kExprNodePlainIdentifier node kExprNodePlainKey is used. - kExprNodeConcatOrSubscript = 'S', - kExprNodeInteger = '0', ///< Integral number. - kExprNodeFloat = '1', ///< Floating-point number. - kExprNodeSingleQuotedString = '\'', - kExprNodeDoubleQuotedString = '"', + kExprNodeConcatOrSubscript, + kExprNodeInteger, ///< Integral number. + kExprNodeFloat, ///< Floating-point number. + kExprNodeSingleQuotedString, + kExprNodeDoubleQuotedString, + kExprNodeOr, + kExprNodeAnd, + kExprNodeUnaryMinus, + kExprNodeBinaryMinus, + kExprNodeNot, + kExprNodeMultiplication, + kExprNodeDivision, + kExprNodeMod, + kExprNodeOption, + kExprNodeEnvironment, } ExprASTNodeType; typedef struct expr_ast_node ExprASTNode; @@ -230,7 +266,7 @@ struct expr_ast_node { size_t opening_hl_idx; } fig; ///< For kExprNodeUnknownFigure. struct { - int scope; ///< Scope character or 0 if not present. + ExprVarScope scope; ///< Scope character or 0 if not present. /// Actual identifier without scope. /// /// Points to inside parser reader state. @@ -256,6 +292,15 @@ struct expr_ast_node { size_t size; } str; ///< For kExprNodeSingleQuotedString and ///< kExprNodeDoubleQuotedString. + struct { + const char *ident; ///< Option name start. + size_t ident_len; ///< Option name length. + ExprOptScope scope; ///< Option scope: &l:, &g: or not specified. + } opt; ///< For kExprNodeOption. + struct { + const char *ident; ///< Environment variable name start. + size_t ident_len; ///< Environment variable name length. + } env; ///< For kExprNodeEnvironment. } data; }; diff --git a/src/nvim/viml/parser/parser.h b/src/nvim/viml/parser/parser.h index a17edac403..10ced57977 100644 --- a/src/nvim/viml/parser/parser.h +++ b/src/nvim/viml/parser/parser.h @@ -173,7 +173,7 @@ static inline void viml_parser_highlight(ParserState *const pstate, const size_t len, const char *const group) { - if (pstate->colors == NULL) { + if (pstate->colors == NULL || len == 0) { return; } // TODO(ZyX-I): May do some assert() sanitizing here. -- cgit From c286155bfa53c828ebe5479fd81a544740a92403 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 15 Oct 2017 19:06:41 +0300 Subject: viml/parser/expressions: Create tests for latest additions --- src/nvim/viml/parser/expressions.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 75fcb17bf6..8928179349 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -1937,9 +1937,22 @@ viml_pexpr_parse_process_token: OP_MISSING; } NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeOption); - cur_node->data.opt.ident = cur_token.data.opt.name; - cur_node->data.opt.ident_len = cur_token.data.opt.len; - cur_node->data.opt.scope = cur_token.data.opt.scope; + if (cur_token.type == kExprLexInvalid) { + assert(cur_token.len == 1 + || (cur_token.len == 3 + && pline.data[cur_token.start.col + 2] == ':')); + cur_node->data.opt.ident = ( + pline.data + cur_token.start.col + cur_token.len); + cur_node->data.opt.ident_len = 0; + cur_node->data.opt.scope = ( + cur_token.len == 3 + ? (ExprOptScope)pline.data[cur_token.start.col + 1] + : kExprOptScopeUnspecified); + } else { + cur_node->data.opt.ident = cur_token.data.opt.name; + cur_node->data.opt.ident_len = cur_token.data.opt.len; + cur_node->data.opt.scope = cur_token.data.opt.scope; + } *top_node_p = cur_node; want_node = kENodeOperator; viml_parser_highlight(pstate, cur_token.start, 1, HL(OptionSigil)); @@ -1953,7 +1966,7 @@ viml_pexpr_parse_process_token: } viml_parser_highlight( pstate, shifted_pos(cur_token.start, scope_shift + 1), - cur_token.len - scope_shift + 1, HL(Option)); + cur_token.len - (scope_shift + 1), HL(Option)); break; } case kExprLexEnv: { @@ -1963,6 +1976,10 @@ viml_pexpr_parse_process_token: NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeEnvironment); cur_node->data.env.ident = pline.data + cur_token.start.col + 1; cur_node->data.env.ident_len = cur_token.len - 1; + if (cur_node->data.env.ident_len == 0) { + ERROR_FROM_TOKEN_AND_MSG(cur_token, + _("E15: Environment variable name missing")); + } *top_node_p = cur_node; want_node = kENodeOperator; viml_parser_highlight(pstate, cur_token.start, 1, HL(EnvironmentSigil)); -- cgit From 6c19cbef2611c389da6f3e06d8c8eb635d065774 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 15 Oct 2017 20:05:35 +0300 Subject: viml/parser/expressions,tests: Add AST freeing, with sanity checks --- src/nvim/viml/parser/expressions.c | 239 +++++++++++++++++++++++++++++++------ 1 file changed, 205 insertions(+), 34 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 8928179349..2f7ec6bcca 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -642,37 +642,6 @@ static const char *const eltkn_opt_scope_tab[] = { [kExprOptScopeLocal] = "Local", }; -/// Represent `int` character as a string -/// -/// Converts -/// - ASCII digits into '{digit}' -/// - ASCII printable characters into a single-character strings -/// - everything else to numbers. -/// -/// @param[in] ch Character to convert. -/// -/// @return Converted string, stored in a static buffer (overriden after each -/// call). -static const char *intchar2str(const int ch) - FUNC_ATTR_WARN_UNUSED_RESULT -{ - static char buf[sizeof(int) * 3 + 1]; - if (' ' <= ch && ch < 0x7f) { - if (ascii_isdigit(ch)) { - buf[0] = '\''; - buf[1] = (char)ch; - buf[2] = '\''; - buf[3] = NUL; - } else { - buf[0] = (char)ch; - buf[1] = NUL; - } - } else { - snprintf(buf, sizeof(buf), "%i", ch); - } - return buf; -} - /// Represent token as a string /// /// Intended for testing and debugging purposes. @@ -756,6 +725,78 @@ viml_pexpr_repr_token_end: return ret; } +static const char *const east_node_type_tab[] = { + [kExprNodeMissing] = "Missing", + [kExprNodeOpMissing] = "OpMissing", + [kExprNodeTernary] = "Ternary", + [kExprNodeTernaryValue] = "TernaryValue", + [kExprNodeRegister] = "Register", + [kExprNodeSubscript] = "Subscript", + [kExprNodeListLiteral] = "ListLiteral", + [kExprNodeUnaryPlus] = "UnaryPlus", + [kExprNodeBinaryPlus] = "BinaryPlus", + [kExprNodeNested] = "Nested", + [kExprNodeCall] = "Call", + [kExprNodePlainIdentifier] = "PlainIdentifier", + [kExprNodePlainKey] = "PlainKey", + [kExprNodeComplexIdentifier] = "ComplexIdentifier", + [kExprNodeUnknownFigure] = "UnknownFigure", + [kExprNodeLambda] = "Lambda", + [kExprNodeDictLiteral] = "DictLiteral", + [kExprNodeCurlyBracesIdentifier] = "CurlyBracesIdentifier", + [kExprNodeComma] = "Comma", + [kExprNodeColon] = "Colon", + [kExprNodeArrow] = "Arrow", + [kExprNodeComparison] = "Comparison", + [kExprNodeConcat] = "Concat", + [kExprNodeConcatOrSubscript] = "ConcatOrSubscript", + [kExprNodeInteger] = "Integer", + [kExprNodeFloat] = "Float", + [kExprNodeSingleQuotedString] = "SingleQuotedString", + [kExprNodeDoubleQuotedString] = "DoubleQuotedString", + [kExprNodeOr] = "Or", + [kExprNodeAnd] = "And", + [kExprNodeUnaryMinus] = "UnaryMinus", + [kExprNodeBinaryMinus] = "BinaryMinus", + [kExprNodeNot] = "Not", + [kExprNodeMultiplication] = "Multiplication", + [kExprNodeDivision] = "Division", + [kExprNodeMod] = "Mod", + [kExprNodeOption] = "Option", + [kExprNodeEnvironment] = "Environment", +}; + +/// Represent `int` character as a string +/// +/// Converts +/// - ASCII digits into '{digit}' +/// - ASCII printable characters into a single-character strings +/// - everything else to numbers. +/// +/// @param[in] ch Character to convert. +/// +/// @return Converted string, stored in a static buffer (overriden after each +/// call). +static const char *intchar2str(const int ch) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + static char buf[sizeof(int) * 3 + 1]; + if (' ' <= ch && ch < 0x7f) { + if (ascii_isdigit(ch)) { + buf[0] = '\''; + buf[1] = (char)ch; + buf[2] = '\''; + buf[3] = NUL; + } else { + buf[0] = (char)ch; + buf[1] = NUL; + } + } else { + snprintf(buf, sizeof(buf), "%i", ch); + } + return buf; +} + #ifdef UNIT_TESTING #include @@ -767,9 +808,9 @@ static inline void viml_pexpr_debug_print_ast_node( if (*eastnode_p == NULL) { fprintf(stderr, "%s %p : NULL\n", prefix, (void *)eastnode_p); } else { - fprintf(stderr, "%s %p : %p : %c : %zu:%zu:%zu\n", + fprintf(stderr, "%s %p : %p : %s : %zu:%zu:%zu\n", prefix, (void *)eastnode_p, (void *)(*eastnode_p), - (*eastnode_p)->type, (*eastnode_p)->start.line, + east_node_type_tab[(*eastnode_p)->type], (*eastnode_p)->start.line, (*eastnode_p)->start.col, (*eastnode_p)->len); } } @@ -800,11 +841,141 @@ static inline void viml_pexpr_debug_print_token( #define PSTACK_P(msg) \ viml_pexpr_debug_print_ast_stack(ast_stack, #msg) #define PNODE_P(eastnode_p, msg) \ - viml_pexpr_debug_print_ast_node((const ExprASTNode *const *)ast_stack, #msg) + viml_pexpr_debug_print_ast_node((const ExprASTNode *const *)eastnode_p, \ + (#msg)) #define PTOKEN(tkn) \ viml_pexpr_debug_print_token(pstate, tkn) #endif +#ifndef NDEBUG +static const uint8_t node_maxchildren[] = { + [kExprNodeMissing] = 0, + [kExprNodeOpMissing] = 2, + [kExprNodeTernary] = 2, + [kExprNodeTernaryValue] = 2, + [kExprNodeRegister] = 0, + [kExprNodeSubscript] = 2, + [kExprNodeListLiteral] = 1, + [kExprNodeUnaryPlus] = 1, + [kExprNodeBinaryPlus] = 2, + [kExprNodeNested] = 1, + [kExprNodeCall] = 2, + [kExprNodePlainIdentifier] = 0, + [kExprNodePlainKey] = 0, + [kExprNodeComplexIdentifier] = 2, + [kExprNodeUnknownFigure] = 1, + [kExprNodeLambda] = 2, + [kExprNodeDictLiteral] = 1, + [kExprNodeCurlyBracesIdentifier] = 1, + [kExprNodeComma] = 2, + [kExprNodeColon] = 2, + [kExprNodeArrow] = 2, + [kExprNodeComparison] = 2, + [kExprNodeConcat] = 2, + [kExprNodeConcatOrSubscript] = 2, + [kExprNodeInteger] = 0, + [kExprNodeFloat] = 0, + [kExprNodeSingleQuotedString] = 0, + [kExprNodeDoubleQuotedString] = 0, + [kExprNodeOr] = 2, + [kExprNodeAnd] = 2, + [kExprNodeUnaryMinus] = 1, + [kExprNodeBinaryMinus] = 2, + [kExprNodeNot] = 1, + [kExprNodeMultiplication] = 2, + [kExprNodeDivision] = 2, + [kExprNodeMod] = 2, + [kExprNodeOption] = 0, + [kExprNodeEnvironment] = 0, +}; +#endif + +/// Free memory occupied by AST +/// +/// @param ast AST stack to free. +void viml_pexpr_free_ast(ExprAST ast) +{ + ExprASTStack ast_stack; + kvi_init(ast_stack); + kvi_push(ast_stack, &ast.root); + while (kv_size(ast_stack)) { + ExprASTNode **const cur_node = kv_last(ast_stack); +#ifndef NDEBUG + // Explicitly check for AST recursiveness. + for (size_t i = 0 ; i < kv_size(ast_stack) - 1 ; i++) { + assert(*kv_A(ast_stack, i) != *cur_node); + } +#endif + if (*cur_node == NULL) { + assert(kv_size(ast_stack) == 1); + kv_drop(ast_stack, 1); + } else if ((*cur_node)->children != NULL) { +#ifndef NDEBUG + const uint8_t maxchildren = node_maxchildren[(*cur_node)->type]; + assert(maxchildren > 0); + assert(maxchildren <= 2); + assert(maxchildren == 1 + ? (*cur_node)->children->next == NULL + : ((*cur_node)->children->next == NULL + || (*cur_node)->children->next->next == NULL)); +#endif + kvi_push(ast_stack, &(*cur_node)->children); + } else if ((*cur_node)->next != NULL) { + kvi_push(ast_stack, &(*cur_node)->next); + } else if (*cur_node != NULL) { + kv_drop(ast_stack, 1); + switch ((*cur_node)->type) { + case kExprNodeDoubleQuotedString: + case kExprNodeSingleQuotedString: { + xfree((*cur_node)->data.str.value); + break; + } + case kExprNodeMissing: + case kExprNodeOpMissing: + case kExprNodeTernary: + case kExprNodeTernaryValue: + case kExprNodeRegister: + case kExprNodeSubscript: + case kExprNodeListLiteral: + case kExprNodeUnaryPlus: + case kExprNodeBinaryPlus: + case kExprNodeNested: + case kExprNodeCall: + case kExprNodePlainIdentifier: + case kExprNodePlainKey: + case kExprNodeComplexIdentifier: + case kExprNodeUnknownFigure: + case kExprNodeLambda: + case kExprNodeDictLiteral: + case kExprNodeCurlyBracesIdentifier: + case kExprNodeComma: + case kExprNodeColon: + case kExprNodeArrow: + case kExprNodeComparison: + case kExprNodeConcat: + case kExprNodeConcatOrSubscript: + case kExprNodeInteger: + case kExprNodeFloat: + case kExprNodeOr: + case kExprNodeAnd: + case kExprNodeUnaryMinus: + case kExprNodeBinaryMinus: + case kExprNodeNot: + case kExprNodeMultiplication: + case kExprNodeDivision: + case kExprNodeMod: + case kExprNodeOption: + case kExprNodeEnvironment: { + break; + } + } + xfree(*cur_node); + *cur_node = NULL; + } + } + kvi_destroy(ast_stack); +} + // start = s ternary_expr s EOC // ternary_expr = binop_expr // ( s Question s ternary_expr s Colon s ternary_expr s )? -- cgit From 57bb3346d95f325d4894d41c88d3a1434de2d2df Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 15 Oct 2017 20:43:16 +0300 Subject: viml/parser/expressions: Update some comments and add another check --- src/nvim/viml/parser/expressions.c | 104 ++++++++++++++----------------------- 1 file changed, 38 insertions(+), 66 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 2f7ec6bcca..370034943e 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -976,59 +976,6 @@ void viml_pexpr_free_ast(ExprAST ast) kvi_destroy(ast_stack); } -// start = s ternary_expr s EOC -// ternary_expr = binop_expr -// ( s Question s ternary_expr s Colon s ternary_expr s )? -// binop_expr = unaryop_expr ( binop unaryop_expr )? -// unaryop_expr = ( unaryop )? subscript_expr -// subscript_expr = subscript_expr subscript -// | value_expr -// subscript = Bracket('[') s ternary_expr s Bracket(']') -// | s Parenthesis('(') call_args Parenthesis(')') -// | Dot ( PlainIdentifier | Number )+ -// # Note: `s` before Parenthesis('(') is only valid if preceding subscript_expr -// # is PlainIdentifier -// value_expr = ( float | Number -// | DoubleQuotedString | SingleQuotedString -// | paren_expr -// | list_literal -// | lambda_literal -// | dict_literal -// | Environment -// | Option -// | Register -// | var ) -// float = Number Dot Number ( PlainIdentifier('e') ( Plus | Minus )? Number )? -// # Note: `1.2.3` is concat and not float. `"abc".2.3` is also concat without -// # floats. -// paren_expr = Parenthesis('(') s ternary_expr s Parenthesis(')') -// list_literal = Bracket('[') s -// ( ternary_expr s Comma s )* -// ternary_expr? s -// Bracket(']') -// dict_literal = FigureBrace('{') s -// ( ternary_expr s Colon s ternary_expr s Comma s )* -// ( ternary_expr s Colon s ternary_expr s )? -// FigureBrace('}') -// lambda_literal = FigureBrace('{') s -// ( PlainIdentifier s Comma s )* -// PlainIdentifier s -// Arrow s -// ternary_expr s -// FigureBrace('}') -// var = varchunk+ -// varchunk = PlainIdentifier -// | Comparison("is" | "is#" | "isnot" | "isnot#") -// | FigureBrace('{') s ternary_expr s FigureBrace('}') -// call_args = ( s ternary_expr s Comma s )* s ternary_expr? s -// binop = s ( Plus | Minus | Dot -// | Comparison -// | Multiplication -// | Or -// | And ) s -// unaryop = s ( Not | Plus | Minus ) s -// s = Spacing? -// // Binary operator precedence and associativity: // // Operator | Precedence | Associativity @@ -1885,6 +1832,14 @@ static void parse_quoted_string(ParserState *const pstate, kvi_destroy(shifts); } +/// Additional flags to pass to lexer depending on want_node +static const int want_node_to_lexer_flags[] = { + [kENodeValue] = kELFlagIsNotCmp, + [kENodeOperator] = kELFlagForbidScope, + [kENodeArgument] = kELFlagIsNotCmp, + [kENodeArgumentSeparator] = kELFlagForbidScope, +}; + /// Parse one VimL expression /// /// @param pstate Parser state. @@ -1902,26 +1857,25 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) }, .root = NULL, }; + // Expression stack contains current branch in AST tree: that is + // - Stack item 0 contains root of the tree, i.e. &ast->root. + // - Stack item i points to the previous stack items’ last child. + // + // When parser expects “value” node that is something like identifier or "[" + // (list start) last stack item contains NULL. Otherwise last stack item is + // supposed to contain last “finished” value: e.g. "1" or "+(1, 1)" (node + // representing "1+1"). + // + // Both kENodeValue and kENodeArgument stand for “value” nodes. ExprASTStack ast_stack; kvi_init(ast_stack); kvi_push(ast_stack, &ast.root); - // Expressions stack: - // 1. *last is NULL if want_node is kExprLexValue. Indicates where expression - // is to be put. - // 2. *last is not NULL otherwise, indicates current expression to be used as - // an operator argument. ExprASTWantedNode want_node = kENodeValue; LexExprToken prev_token = { .type = kExprLexMissing }; bool highlighted_prev_spacing = false; // Lambda node, valid when parsing lambda arguments only. ExprASTNode *lambda_node = NULL; do { - const int want_node_to_lexer_flags[] = { - [kENodeValue] = kELFlagIsNotCmp, - [kENodeOperator] = kELFlagForbidScope, - [kENodeArgument] = kELFlagIsNotCmp, - [kENodeArgumentSeparator] = kELFlagForbidScope, - }; const bool is_concat_or_subscript = ( want_node == kENodeValue && kv_size(ast_stack) > 1 @@ -1965,9 +1919,27 @@ viml_pexpr_parse_process_token: } const ParserLine pline = pstate->reader.lines.items[cur_token.start.line]; ExprASTNode **const top_node_p = kv_last(ast_stack); + assert(kv_size(ast_stack) >= 1); ExprASTNode *cur_node = NULL; - assert((want_node == kENodeValue || want_node == kENodeArgument) - == (*top_node_p == NULL)); +#ifndef NDEBUG + const bool want_value = ( + want_node == kENodeValue || want_node == kENodeArgument); + assert(want_value == (*top_node_p == NULL)); + assert(kv_A(ast_stack, 0) == &ast.root); + // Check that stack item i + 1 points to stack items’ i *last* child. + for (size_t i = 0; i + 1 < kv_size(ast_stack); i++) { + const bool item_null = (want_value && i + 2 == kv_size(ast_stack)); + assert((&(*kv_A(ast_stack, i))->children == kv_A(ast_stack, i + 1) + && (item_null + ? (*kv_A(ast_stack, i))->children == NULL + : (*kv_A(ast_stack, i))->children->next == NULL)) + || ((&(*kv_A(ast_stack, i))->children->next + == kv_A(ast_stack, i + 1)) + && (item_null + ? (*kv_A(ast_stack, i))->children->next == NULL + : (*kv_A(ast_stack, i))->children->next->next == NULL))); + } +#endif // Note: in Vim whether expression "cond?d.a:2" is valid depends both on // "cond" and whether "d" is a dictionary: expression is valid if condition // is true and "d" is a dictionary (with "a" key or it will complain about -- cgit From bc386c48829c431fe5b17592c82eb4099621953a Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 15 Oct 2017 21:09:08 +0300 Subject: charset: Fix out-of-bounds array access It is incorrect to *first* access ptr[2] and *then* check whether maxlen allows it. --- src/nvim/charset.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 403ef65c4f..1147b78c9a 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1621,6 +1621,7 @@ bool vim_isblankline(char_u *lbuf) void vim_str2nr(const char_u *const start, int *const prep, int *const len, const int what, varnumber_T *const nptr, uvarnumber_T *const unptr, const int maxlen) + FUNC_ATTR_NONNULL_ARG(1) { const char_u *ptr = start; int pre = 0; // default is decimal @@ -1633,20 +1634,21 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, } // Recognize hex, octal and bin. - if ((ptr[0] == '0') && (ptr[1] != '8') && (ptr[1] != '9') - && (maxlen == 0 || maxlen > 1)) { + if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN)) + && (maxlen == 0 || maxlen > 1) + && (ptr[0] == '0') && (ptr[1] != '8') && (ptr[1] != '9')) { pre = ptr[1]; if ((what & STR2NR_HEX) + && (maxlen == 0 || maxlen > 2) && ((pre == 'X') || (pre == 'x')) - && ascii_isxdigit(ptr[2]) - && (maxlen == 0 || maxlen > 2)) { + && ascii_isxdigit(ptr[2])) { // hexadecimal ptr += 2; } else if ((what & STR2NR_BIN) + && (maxlen == 0 || maxlen > 2) && ((pre == 'B') || (pre == 'b')) - && ascii_isbdigit(ptr[2]) - && (maxlen == 0 || maxlen > 2)) { + && ascii_isbdigit(ptr[2])) { // binary ptr += 2; } else { @@ -1675,7 +1677,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, // Do the string-to-numeric conversion "manually" to avoid sscanf quirks. int n = 1; - if ((pre == 'B') || (pre == 'b') || what == STR2NR_BIN + STR2NR_FORCE) { + if (pre == 'B' || pre == 'b' || what == (STR2NR_BIN|STR2NR_FORCE)) { // bin if (pre != 0) { n += 2; // skip over "0b" @@ -1692,7 +1694,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, break; } } - } else if ((pre == '0') || what == STR2NR_OCT + STR2NR_FORCE) { + } else if (pre == '0' || what == (STR2NR_OCT|STR2NR_FORCE)) { // octal while ('0' <= *ptr && *ptr <= '7') { // avoid ubsan error for overflow @@ -1706,8 +1708,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, break; } } - } else if ((pre == 'X') || (pre == 'x') - || what == STR2NR_HEX + STR2NR_FORCE) { + } else if (pre == 'X' || pre == 'x' || what == (STR2NR_HEX|STR2NR_FORCE)) { // hex if (pre != 0) { n += 2; // skip over "0x" -- cgit From 3aa2c0d63ae488e302a89fdcdd650404cb2670fd Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 15 Oct 2017 21:11:00 +0300 Subject: viml/parser/expressions,klee: Fix some problems found by KLEE run --- src/nvim/viml/parser/expressions.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 0198852bed..025f0f766e 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -69,7 +69,7 @@ typedef enum { } ExprOptScope; #define EXPR_OPT_SCOPE_LIST \ - ((char *)(char[]){ kExprOptScopeGlobal, kExprOptScopeLocal }) + ((char[]){ kExprOptScopeGlobal, kExprOptScopeLocal }) /// All possible variable scopes typedef enum { -- cgit From 4ccaf861107f8c737ba5b749a69c75d7f097d1f1 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 15 Oct 2017 21:22:49 +0300 Subject: keymap: Readd figure braces disappeared when resolving conflicts --- src/nvim/keymap.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index a44b663cc6..55ac835663 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -719,8 +719,8 @@ int get_special_key_code(const char_u *name) for (int i = 0; key_names_table[i].name != NULL; i++) { const char *const table_name = key_names_table[i].name; int j; - for (j = 0; vim_isIDc(name[j]) && table_name[j] != NUL; j++) - if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC(name[j])) + for (j = 0; vim_isIDc(name[j]) && table_name[j] != NUL; j++) { + if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC(name[j])) { break; } } -- cgit From 2cb95bd9378d3c45f2eea527683546825e16f7d3 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 15 Oct 2017 21:39:01 +0300 Subject: viml/parser/expressions: Define east_node_type_tab only when needed --- src/nvim/viml/parser/expressions.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 370034943e..4801d66988 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -725,6 +725,7 @@ viml_pexpr_repr_token_end: return ret; } +#ifdef UNIT_TESTING static const char *const east_node_type_tab[] = { [kExprNodeMissing] = "Missing", [kExprNodeOpMissing] = "OpMissing", @@ -765,6 +766,7 @@ static const char *const east_node_type_tab[] = { [kExprNodeOption] = "Option", [kExprNodeEnvironment] = "Environment", }; +#endif /// Represent `int` character as a string /// -- cgit From 1a3635304b80b48625bcd9d48f4f38778b42e4af Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 16 Oct 2017 00:07:32 +0300 Subject: charset: Avoid overflow in vim_str2nr --- src/nvim/charset.c | 60 +++++++++++++++++++----------------------------------- 1 file changed, 21 insertions(+), 39 deletions(-) (limited to 'src') diff --git a/src/nvim/charset.c b/src/nvim/charset.c index f824717fcd..7b307b8160 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1620,13 +1620,16 @@ bool vim_isblankline(char_u *lbuf) /// @param maxlen Max length of string to check. void vim_str2nr(const char_u *const start, int *const prep, int *const len, const int what, varnumber_T *const nptr, - uvarnumber_T *const unptr, const int maxlen) + uvarnumber_T *const unptr, int maxlen) FUNC_ATTR_NONNULL_ARG(1) { - const char_u *ptr = start; + const char *ptr = (const char *)start; + if (maxlen == 0) { + maxlen = (int)strlen(ptr); + } + const char *const e = ptr + maxlen; int pre = 0; // default is decimal bool negative = false; - uvarnumber_T un = 0; if (ptr[0] == '-') { negative = true; @@ -1635,19 +1638,19 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, // Recognize hex, octal and bin. if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN)) - && (maxlen == 0 || maxlen > 1) - && (ptr[0] == '0') && (ptr[1] != '8') && (ptr[1] != '9')) { + && maxlen > 1 + && ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9') { pre = ptr[1]; if ((what & STR2NR_HEX) - && (maxlen == 0 || maxlen > 2) - && ((pre == 'X') || (pre == 'x')) + && maxlen > 2 + && (pre == 'X' || pre == 'x') && ascii_isxdigit(ptr[2])) { // hexadecimal ptr += 2; } else if ((what & STR2NR_BIN) - && (maxlen == 0 || maxlen > 2) - && ((pre == 'B') || (pre == 'b')) + && maxlen > 2 + && (pre == 'B' || pre == 'b') && ascii_isbdigit(ptr[2])) { // binary ptr += 2; @@ -1657,32 +1660,26 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, if (what & STR2NR_OCT) { // Don't interpret "0", "08" or "0129" as octal. - for (int n = 1; ascii_isdigit(ptr[n]); ++n) { - if (ptr[n] > '7') { + for (int i = 1; i < maxlen && ascii_isdigit(ptr[i]); i++) { + if (ptr[i] > '7') { // can't be octal pre = 0; break; } - if (ptr[n] >= '0') { + if (ptr[i] >= '0') { // assume octal pre = '0'; } - if (n == maxlen) { - break; - } } } } } // Do the string-to-numeric conversion "manually" to avoid sscanf quirks. - int n = 1; + uvarnumber_T un = 0; if (pre == 'B' || pre == 'b' || what == (STR2NR_BIN|STR2NR_FORCE)) { // bin - if (pre != 0) { - n += 2; // skip over "0b" - } - while ('0' <= *ptr && *ptr <= '1') { + while (ptr < e && '0' <= *ptr && *ptr <= '1') { // avoid ubsan error for overflow if (un < UVARNUMBER_MAX / 2) { un = 2 * un + (uvarnumber_T)(*ptr - '0'); @@ -1690,13 +1687,10 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, un = UVARNUMBER_MAX; } ptr++; - if (n++ == maxlen) { - break; - } } } else if (pre == '0' || what == (STR2NR_OCT|STR2NR_FORCE)) { // octal - while ('0' <= *ptr && *ptr <= '7') { + while (ptr < e && '0' <= *ptr && *ptr <= '7') { // avoid ubsan error for overflow if (un < UVARNUMBER_MAX / 8) { un = 8 * un + (uvarnumber_T)(*ptr - '0'); @@ -1704,16 +1698,10 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, un = UVARNUMBER_MAX; } ptr++; - if (n++ == maxlen) { - break; - } } } else if (pre == 'X' || pre == 'x' || what == (STR2NR_HEX|STR2NR_FORCE)) { // hex - if (pre != 0) { - n += 2; // skip over "0x" - } - while (ascii_isxdigit(*ptr)) { + while (ptr < e && ascii_isxdigit(*ptr)) { // avoid ubsan error for overflow if (un < UVARNUMBER_MAX / 16) { un = 16 * un + (uvarnumber_T)hex2nr(*ptr); @@ -1721,13 +1709,10 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, un = UVARNUMBER_MAX; } ptr++; - if (n++ == maxlen) { - break; - } } } else { // decimal - while (ascii_isdigit(*ptr)) { + while (ptr < e && ascii_isdigit(*ptr)) { // avoid ubsan error for overflow if (un < UVARNUMBER_MAX / 10) { un = 10 * un + (uvarnumber_T)(*ptr - '0'); @@ -1735,9 +1720,6 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, un = UVARNUMBER_MAX; } ptr++; - if (n++ == maxlen) { - break; - } } } @@ -1746,7 +1728,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, } if (len != NULL) { - *len = (int)(ptr - start); + *len = (int)(ptr - (const char *)start); } if (nptr != NULL) { -- cgit From 5e92ee6565233c56e7a33b12ef6a61f05eae91aa Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 16 Oct 2017 00:19:02 +0300 Subject: charset: Do not call strlen() from vim_str2nr --- src/nvim/charset.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 7b307b8160..a7ffa4bc00 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1620,14 +1620,12 @@ bool vim_isblankline(char_u *lbuf) /// @param maxlen Max length of string to check. void vim_str2nr(const char_u *const start, int *const prep, int *const len, const int what, varnumber_T *const nptr, - uvarnumber_T *const unptr, int maxlen) + uvarnumber_T *const unptr, const int maxlen) FUNC_ATTR_NONNULL_ARG(1) { const char *ptr = (const char *)start; - if (maxlen == 0) { - maxlen = (int)strlen(ptr); - } - const char *const e = ptr + maxlen; +#define STRING_ENDED(ptr) \ + (!(maxlen == 0 || (int)((ptr) - (const char *)start) < maxlen)) int pre = 0; // default is decimal bool negative = false; @@ -1638,18 +1636,18 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, // Recognize hex, octal and bin. if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN)) - && maxlen > 1 + && !STRING_ENDED(ptr + 1) && ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9') { pre = ptr[1]; if ((what & STR2NR_HEX) - && maxlen > 2 + && !STRING_ENDED(ptr + 2) && (pre == 'X' || pre == 'x') && ascii_isxdigit(ptr[2])) { // hexadecimal ptr += 2; } else if ((what & STR2NR_BIN) - && maxlen > 2 + && !STRING_ENDED(ptr + 2) && (pre == 'B' || pre == 'b') && ascii_isbdigit(ptr[2])) { // binary @@ -1660,7 +1658,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, if (what & STR2NR_OCT) { // Don't interpret "0", "08" or "0129" as octal. - for (int i = 1; i < maxlen && ascii_isdigit(ptr[i]); i++) { + for (int i = 1; !STRING_ENDED(ptr + i) && ascii_isdigit(ptr[i]); i++) { if (ptr[i] > '7') { // can't be octal pre = 0; @@ -1679,7 +1677,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, uvarnumber_T un = 0; if (pre == 'B' || pre == 'b' || what == (STR2NR_BIN|STR2NR_FORCE)) { // bin - while (ptr < e && '0' <= *ptr && *ptr <= '1') { + while (!STRING_ENDED(ptr) && '0' <= *ptr && *ptr <= '1') { // avoid ubsan error for overflow if (un < UVARNUMBER_MAX / 2) { un = 2 * un + (uvarnumber_T)(*ptr - '0'); @@ -1690,7 +1688,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, } } else if (pre == '0' || what == (STR2NR_OCT|STR2NR_FORCE)) { // octal - while (ptr < e && '0' <= *ptr && *ptr <= '7') { + while (!STRING_ENDED(ptr) && '0' <= *ptr && *ptr <= '7') { // avoid ubsan error for overflow if (un < UVARNUMBER_MAX / 8) { un = 8 * un + (uvarnumber_T)(*ptr - '0'); @@ -1701,7 +1699,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, } } else if (pre == 'X' || pre == 'x' || what == (STR2NR_HEX|STR2NR_FORCE)) { // hex - while (ptr < e && ascii_isxdigit(*ptr)) { + while (!STRING_ENDED(ptr) && ascii_isxdigit(*ptr)) { // avoid ubsan error for overflow if (un < UVARNUMBER_MAX / 16) { un = 16 * un + (uvarnumber_T)hex2nr(*ptr); @@ -1712,7 +1710,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, } } else { // decimal - while (ptr < e && ascii_isdigit(*ptr)) { + while (!STRING_ENDED(ptr) && ascii_isdigit(*ptr)) { // avoid ubsan error for overflow if (un < UVARNUMBER_MAX / 10) { un = 10 * un + (uvarnumber_T)(*ptr - '0'); @@ -1750,6 +1748,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, if (unptr != NULL) { *unptr = un; } +#undef STRING_ENDED } /// Return the value of a single hex character. -- cgit From fe81380bf5d4d161187998088aa9cff948b7c891 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 16 Oct 2017 00:30:55 +0300 Subject: viml/parser/expressions: Highlight prefix separately from number Should make accidental octals more visible. --- src/nvim/viml/parser/expressions.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 4801d66988..35f4385f33 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -1042,6 +1042,7 @@ void viml_pexpr_free_ast(ExprAST ast) // // NVimRegister -> SpecialChar // NVimNumber -> Number +// NVimNumberPrefix -> SpecialChar // NVimFloat -> NVimNumber // // NVimNestingParenthesis -> NVimParenthesis @@ -1842,6 +1843,14 @@ static const int want_node_to_lexer_flags[] = { [kENodeArgumentSeparator] = kELFlagForbidScope, }; +/// Number of characters to highlight as NumberPrefix depending on the base +static const uint8_t base_to_prefix_length[] = { + [2] = 2, + [8] = 1, + [10] = 0, + [16] = 2, +}; + /// Parse one VimL expression /// /// @param pstate Parser state. @@ -2632,7 +2641,13 @@ viml_pexpr_parse_figure_brace_closing_error: } else { NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeInteger); cur_node->data.num.value = cur_token.data.num.val.integer; - HL_CUR_TOKEN(Number); + const uint8_t prefix_length = base_to_prefix_length[ + cur_token.data.num.base]; + viml_parser_highlight(pstate, cur_token.start, prefix_length, + HL(NumberPrefix)); + viml_parser_highlight( + pstate, shifted_pos(cur_token.start, prefix_length), + cur_token.len - prefix_length, HL(Number)); } want_node = kENodeOperator; *top_node_p = cur_node; -- cgit From ed253b5fe6515840fe6dd9df83855a0316de8bad Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 16 Oct 2017 00:39:48 +0300 Subject: klee: Include colors in test --- src/nvim/viml/parser/parser.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/parser.h b/src/nvim/viml/parser/parser.h index 10ced57977..fbc5ba5f07 100644 --- a/src/nvim/viml/parser/parser.h +++ b/src/nvim/viml/parser/parser.h @@ -176,8 +176,9 @@ static inline void viml_parser_highlight(ParserState *const pstate, if (pstate->colors == NULL || len == 0) { return; } - // TODO(ZyX-I): May do some assert() sanitizing here. - // TODO(ZyX-I): May join chunks. + assert(kv_size(*pstate->colors) == 0 + || kv_Z(*pstate->colors, 0).start.line < start.line + || kv_Z(*pstate->colors, 0).end_col <= start.col); kvi_push(*pstate->colors, ((ParserHighlightChunk) { .start = start, .end_col = start.col + len, -- cgit From 15043e93b681ffdf1142d24aa918997e2e63a4b1 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 16 Oct 2017 00:41:41 +0300 Subject: klee: Update key_name_entry table --- src/nvim/keymap.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index 55ac835663..4f120e66f7 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -141,8 +141,7 @@ static char_u modifier_keys_table[] = static const struct key_name_entry { int key; // Special key code or ascii value const char *name; // Name of key -} key_names_table[] = -{ +} key_names_table[] = { { ' ', "Space" }, { TAB, "Tab" }, { K_TAB, "Tab" }, -- cgit From a535d68380e472a661478cd6a65aad243ebd6a82 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 16 Oct 2017 01:00:58 +0300 Subject: keymap: Remove incorrect cast --- src/nvim/keymap.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index 4f120e66f7..6ed54464e8 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -696,11 +696,14 @@ int find_special_key_in_table(int c) { int i; - for (i = 0; key_names_table[i].name != NULL; i++) - if (c == (uint8_t)key_names_table[i].key) + for (i = 0; key_names_table[i].name != NULL; i++) { + if (c == key_names_table[i].key) { break; - if (key_names_table[i].name == NULL) + } + } + if (key_names_table[i].name == NULL) { i = -1; + } return i; } -- cgit From 4c8ed65b608df06b4c72b641f4ecc86985295633 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 16 Oct 2017 03:04:22 +0300 Subject: viml/parser/expressions: Fix memory leak when processing ternary --- src/nvim/viml/parser/expressions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 35f4385f33..876fbc8d37 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -2308,10 +2308,10 @@ viml_pexpr_parse_invalid_colon: _("E15: Colon outside of dictionary or ternary operator: %.*s")); viml_pexpr_parse_valid_colon: ADD_VALUE_IF_MISSING(_(EXP_VAL_COLON)); - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); if (is_ternary) { HL_CUR_TOKEN(TernaryColon); } else { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); ADD_OP_NODE(cur_node); HL_CUR_TOKEN(Colon); } -- cgit From c9f511d24a64da135bef4b9874c7bec04d9330e4 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 16 Oct 2017 09:06:05 +0300 Subject: viml/parser/expressions: Remove unused flag --- src/nvim/viml/parser/expressions.h | 4 ---- 1 file changed, 4 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 025f0f766e..d783518b3a 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -314,10 +314,6 @@ enum { /// When parsing expressions input by user bar is assumed to be a binary /// operator and other two are spacings. kExprFlagsDisallowEOC = (1 << 1), - /// Print errors when encountered - /// - /// Without the flag they are only taken into account when parsing. - kExprFlagsPrintError = (1 << 2), // WARNING: whenever you add a new flag, alter klee_assume() statement in // viml_expressions_parser.c. } ExprParserFlags; -- cgit From 895793fc820e04ea2d6bdaa90c6643c4dce2f0e7 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 16 Oct 2017 09:14:02 +0300 Subject: viml/parser/expressions: Add some casts --- src/nvim/viml/parser/expressions.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 876fbc8d37..69817bf24f 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -698,8 +698,8 @@ const char *viml_pexpr_repr_token(const ParserState *const pstate, (int)token.data.num.is_float, (int)token.data.num.base, (double)(token.data.num.is_float - ? token.data.num.val.floating - : token.data.num.val.integer)) + ? (double)token.data.num.val.floating + : (double)token.data.num.val.integer)) TKNARGS(kExprLexInvalid, "(msg=%s)", token.data.err.msg) default: { // No additional arguments. -- cgit From 47938e1e22816381f26e8882eacd6e7e8baf37fd Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 19 Oct 2017 10:48:05 +0300 Subject: viml/parser/expressions: Fix some errors spotted by KLEE Not all of them are fixed yet though. --- src/nvim/viml/parser/expressions.c | 58 +++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 69817bf24f..da22cf4cdb 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -1091,7 +1091,7 @@ void viml_pexpr_free_ast(ExprAST ast) // NVimInvalidDoubleQuote -> NVimInvalidString // NVimInvalidDoubleQuotedBody -> NVimInvalidString // NVimInvalidDoubleQuotedEscape -> NVimInvalidStringSpecial -// NVimInvalidDoubleQuotedUnknownEscape -> NVimInvalid +// NVimInvalidDoubleQuotedUnknownEscape -> NVimInvalidDoubleQuotedEscape // // NVimFigureBrace -> NVimInternalError // NVimInvalidSingleQuotedUnknownEscape -> NVimInternalError @@ -1313,7 +1313,7 @@ static bool viml_pexpr_handle_bop(const ParserState *const pstate, /// ParserPosition literal based on ParserPosition pos with columns shifted /// -/// Function does not check whether remaining position is valid. +/// Function does not check whether resulting position is valid. /// /// @param[in] pos Position to shift. /// @param[in] shift Number of bytes to shift. @@ -1326,6 +1326,21 @@ static inline ParserPosition shifted_pos(const ParserPosition pos, return (ParserPosition) { .line = pos.line, .col = pos.col + shift }; } +/// ParserPosition literal based on ParserPosition pos with specified column +/// +/// Function does not check whether remaining position is valid. +/// +/// @param[in] pos Position to adjust. +/// @param[in] new_col New column. +/// +/// @return Shifted position. +static inline ParserPosition recol_pos(const ParserPosition pos, + const size_t new_col) + FUNC_ATTR_CONST FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return (ParserPosition) { .line = pos.line, .col = new_col }; +} + /// Get highlight group name #define HL(g) (is_invalid ? "NVimInvalid" #g : "NVim" #g) @@ -1639,7 +1654,7 @@ static void parse_quoted_string(ParserState *const pstate, size_t n = (*p == 'u' ? 4 : 8); int nr = 0; p++; - while (n-- && ascii_isxdigit(p[1])) { + while (p + 1 < e && n-- && ascii_isxdigit(p[1])) { p++; nr = (nr << 4) + hex2nr(*p); } @@ -1659,7 +1674,7 @@ static void parse_quoted_string(ParserState *const pstate, if (*p >= '0' && *p <= '7') { size--; p++; - if (*p >= '0' && *p <= '7') { + if (p < e && *p >= '0' && *p <= '7') { size--; p++; } @@ -1715,7 +1730,7 @@ static void parse_quoted_string(ParserState *const pstate, // Hexadecimal or unicode. case 'X': case 'x': case 'u': case 'U': { - if (ascii_isxdigit(p[1])) { + if (p + 1 < e && ascii_isxdigit(p[1])) { size_t n; int nr; bool is_hex = (*p == 'x' || *p == 'X'); @@ -1728,7 +1743,7 @@ static void parse_quoted_string(ParserState *const pstate, n = 8; } nr = 0; - while (n-- && ascii_isxdigit(p[1])) { + while (p + 1 < e && n-- && ascii_isxdigit(p[1])) { p++; nr = (nr << 4) + hex2nr(*p); } @@ -1749,9 +1764,9 @@ static void parse_quoted_string(ParserState *const pstate, case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': { uint8_t ch = (uint8_t)(*p++ - '0'); - if (*p >= '0' && *p <= '7') { + if (p < e && *p >= '0' && *p <= '7') { ch = (uint8_t)((ch << 3) + *p++ - '0'); - if (*p >= '0' && *p <= '7') { + if (p < e && *p >= '0' && *p <= '7') { ch = (uint8_t)((ch << 3) + *p++ - '0'); } } @@ -1793,7 +1808,7 @@ static void parse_quoted_string(ParserState *const pstate, // TODO(ZyX-I): use ast_stack to determine and highlight regular expressions // TODO(ZyX-I): use ast_stack to determine and highlight printf format str // TODO(ZyX-I): use ast_stack to determine and highlight expression strings - size_t next_col = 1; + size_t next_col = token.start.col + 1; const char *const body_str = (is_double ? HL(DoubleQuotedBody) : HL(SingleQuotedBody)); @@ -1806,20 +1821,23 @@ static void parse_quoted_string(ParserState *const pstate, for (size_t i = 0; i < kv_size(shifts); i++) { const StringShift cur_shift = kv_A(shifts, i); if (cur_shift.start > next_col) { - viml_parser_highlight(pstate, shifted_pos(token.start, next_col), + viml_parser_highlight(pstate, recol_pos(token.start, next_col), cur_shift.start - next_col, body_str); } - viml_parser_highlight(pstate, shifted_pos(token.start, cur_shift.start), + viml_parser_highlight(pstate, recol_pos(token.start, cur_shift.start), cur_shift.orig_len, (cur_shift.escape_not_known ? ukn_esc_str : esc_str)); next_col = cur_shift.start + cur_shift.orig_len; } - if (next_col < token.len - token.data.str.closed) { - viml_parser_highlight(pstate, shifted_pos(token.start, next_col), - token.len - token.data.str.closed - next_col, + if (next_col - token.start.col < token.len - token.data.str.closed) { + viml_parser_highlight(pstate, recol_pos(token.start, next_col), + (token.start.col + + token.len + - token.data.str.closed + - next_col), body_str); } } @@ -2580,6 +2598,9 @@ viml_pexpr_parse_figure_brace_closing_error: break; } case kExprLexPlainIdentifier: { + const ExprVarScope scope = (cur_token.type == kExprLexInvalid + ? kExprVarScopeMissing + : cur_token.data.var.scope); if (want_node == kENodeValue || want_node == kENodeArgument) { want_node = (want_node == kENodeArgument ? kENodeArgumentSeparator @@ -2588,9 +2609,8 @@ viml_pexpr_parse_figure_brace_closing_error: (node_is_key ? kExprNodePlainKey : kExprNodePlainIdentifier)); - cur_node->data.var.scope = cur_token.data.var.scope; - const size_t scope_shift = ( - cur_token.data.var.scope == kExprVarScopeMissing ? 0 : 2); + cur_node->data.var.scope = scope; + const size_t scope_shift = (scope == kExprVarScopeMissing ? 0 : 2); cur_node->data.var.ident = (pline.data + cur_token.start.col + scope_shift); cur_node->data.var.ident_len = cur_token.len - scope_shift; @@ -2609,11 +2629,11 @@ viml_pexpr_parse_figure_brace_closing_error: ? HL(IdentifierKey) : HL(Identifier))); } else { - if (cur_token.data.var.scope == kExprVarScopeMissing) { + if (scope == kExprVarScopeMissing) { ADD_IDENT( do { NEW_NODE_WITH_CUR_POS(cur_node, kExprNodePlainIdentifier); - cur_node->data.var.scope = cur_token.data.var.scope; + cur_node->data.var.scope = scope; cur_node->data.var.ident = pline.data + cur_token.start.col; cur_node->data.var.ident_len = cur_token.len; want_node = kENodeOperator; -- cgit From b574e95850c064d4e746a7373aff1a3d7bd4de27 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Oct 2017 01:17:00 +0300 Subject: charset: Some more refactoring of vim_str2nr --- src/nvim/charset.c | 78 +++++++++++++++++++++--------------------------------- src/nvim/version.c | 2 ++ 2 files changed, 32 insertions(+), 48 deletions(-) (limited to 'src') diff --git a/src/nvim/charset.c b/src/nvim/charset.c index a7ffa4bc00..c895d65eb7 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1656,18 +1656,19 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, // decimal or octal, default is decimal pre = 0; - if (what & STR2NR_OCT) { - // Don't interpret "0", "08" or "0129" as octal. - for (int i = 1; !STRING_ENDED(ptr + i) && ascii_isdigit(ptr[i]); i++) { + if (what & STR2NR_OCT + && !STRING_ENDED(ptr + 1) + && ('0' <= ptr[1] && ptr[1] <= '7')) { + // Assume octal now: what we already know is that string starts with + // zero and some octal digit. + pre = '0'; + // Don’t interpret "0", "008" or "0129" as octal. + for (int i = 2; !STRING_ENDED(ptr + i) && ascii_isdigit(ptr[i]); i++) { if (ptr[i] > '7') { - // can't be octal + // Can’t be octal. pre = 0; break; } - if (ptr[i] >= '0') { - // assume octal - pre = '0'; - } } } } @@ -1675,51 +1676,32 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, // Do the string-to-numeric conversion "manually" to avoid sscanf quirks. uvarnumber_T un = 0; +#define PARSE_NUMBER(base, cond, conv) \ + do { \ + while (!STRING_ENDED(ptr) && (cond)) { \ + /* avoid ubsan error for overflow */ \ + if (un < UVARNUMBER_MAX / base) { \ + un = base * un + (uvarnumber_T)(conv); \ + } else { \ + un = UVARNUMBER_MAX; \ + } \ + ptr++; \ + } \ + } while (0) if (pre == 'B' || pre == 'b' || what == (STR2NR_BIN|STR2NR_FORCE)) { - // bin - while (!STRING_ENDED(ptr) && '0' <= *ptr && *ptr <= '1') { - // avoid ubsan error for overflow - if (un < UVARNUMBER_MAX / 2) { - un = 2 * un + (uvarnumber_T)(*ptr - '0'); - } else { - un = UVARNUMBER_MAX; - } - ptr++; - } + // Binary number. + PARSE_NUMBER(2, (*ptr == '0' || *ptr == '1'), (*ptr - '0')); } else if (pre == '0' || what == (STR2NR_OCT|STR2NR_FORCE)) { - // octal - while (!STRING_ENDED(ptr) && '0' <= *ptr && *ptr <= '7') { - // avoid ubsan error for overflow - if (un < UVARNUMBER_MAX / 8) { - un = 8 * un + (uvarnumber_T)(*ptr - '0'); - } else { - un = UVARNUMBER_MAX; - } - ptr++; - } + // Octal number. + PARSE_NUMBER(8, ('0' <= *ptr && *ptr <= '7'), (*ptr - '0')); } else if (pre == 'X' || pre == 'x' || what == (STR2NR_HEX|STR2NR_FORCE)) { - // hex - while (!STRING_ENDED(ptr) && ascii_isxdigit(*ptr)) { - // avoid ubsan error for overflow - if (un < UVARNUMBER_MAX / 16) { - un = 16 * un + (uvarnumber_T)hex2nr(*ptr); - } else { - un = UVARNUMBER_MAX; - } - ptr++; - } + // Hexadecimal number. + PARSE_NUMBER(16, (ascii_isxdigit(*ptr)), (hex2nr(*ptr))); } else { - // decimal - while (!STRING_ENDED(ptr) && ascii_isdigit(*ptr)) { - // avoid ubsan error for overflow - if (un < UVARNUMBER_MAX / 10) { - un = 10 * un + (uvarnumber_T)(*ptr - '0'); - } else { - un = UVARNUMBER_MAX; - } - ptr++; - } + // Decimal number. + PARSE_NUMBER(10, (ascii_isdigit(*ptr)), (*ptr - '0')); } +#undef PARSE_NUMBER if (prep != NULL) { *prep = pre; diff --git a/src/nvim/version.c b/src/nvim/version.c index 7e59f3327a..3382e394f8 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -77,6 +77,8 @@ static char *features[] = { // clang-format off static const int included_patches[] = { + 1229, + 1230, // 1026, 1025, 1024, -- cgit From 568cf73c90af2966ee091f2180905a8cf9582064 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Oct 2017 01:29:48 +0300 Subject: viml/parser/expressions: Fix last error found by KLEE --- src/nvim/viml/parser/expressions.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index da22cf4cdb..b413d56592 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -2445,6 +2445,7 @@ viml_pexpr_parse_bracket_closing_error: cur_node->children = *top_node_p; } *top_node_p = cur_node; + new_top_node_p = top_node_p; goto viml_pexpr_parse_figure_brace_closing_error; } if (want_node == kENodeValue) { -- cgit From b935a12dab17c3887db9c5fd7c90b34b2c51170f Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Oct 2017 16:32:13 +0300 Subject: ex_getln: Make use of new parser to color expressions Retires g:Nvim_color_expr callback. --- src/nvim/ex_getln.c | 67 ++++++++++++++++++++++++++++++++++++++----- src/nvim/mbyte.c | 4 +-- src/nvim/mbyte.h | 6 ++++ src/nvim/viml/parser/parser.c | 13 +++++++++ src/nvim/viml/parser/parser.h | 55 +++++++++++++++++++++++++++++++++++ 5 files changed, 135 insertions(+), 10 deletions(-) create mode 100644 src/nvim/viml/parser/parser.c (limited to 'src') diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 54e5bcb9ff..386e9e81aa 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -66,6 +66,8 @@ #include "nvim/lib/kvec.h" #include "nvim/api/private/helpers.h" #include "nvim/highlight_defs.h" +#include "nvim/viml/parser/parser.h" +#include "nvim/viml/parser/expressions.h" /* * Variables shared between getcmdline(), redrawcmdline() and others. @@ -2341,6 +2343,62 @@ void free_cmdline_buf(void) enum { MAX_CB_ERRORS = 1 }; +/// Color expression cmdline using built-in expressions parser +/// +/// @param[in] colored_ccline Command-line to color. +/// @param[out] ret_ccline_colors What should be colored. +/// +/// Always colors the whole cmdline. +static void color_expr_cmdline(const CmdlineInfo *const colored_ccline, + ColoredCmdline *const ret_ccline_colors) + FUNC_ATTR_NONNULL_ALL +{ + ParserLine plines[] = { + { + .data = (const char *)colored_ccline->cmdbuff, + .size = STRLEN(colored_ccline->cmdbuff), + .allocated = false, + }, + { NULL, 0, false }, + }; + ParserLine *plines_p = plines; + ParserHighlight colors; + kvi_init(colors); + ParserState pstate; + viml_parser_init( + &pstate, parser_simple_get_line, &plines_p, &colors); + ExprAST east = viml_pexpr_parse(&pstate, kExprFlagsDisallowEOC); + viml_pexpr_free_ast(east); + viml_parser_destroy(&pstate); + kv_resize(ret_ccline_colors->colors, kv_size(colors)); + size_t prev_end = 0; + for (size_t i = 0 ; i < kv_size(colors) ; i++) { + const ParserHighlightChunk chunk = kv_A(colors, i); + if (chunk.start.col != prev_end) { + kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) { + .start = prev_end, + .end = chunk.start.col, + .attr = 0, + })); + } + const int id = syn_name2id((const char_u *)chunk.group); + const int attr = (id == 0 ? 0 : syn_id2attr(id)); + kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) { + .start = chunk.start.col, + .end = chunk.end_col, + .attr = attr, + })); + prev_end = chunk.end_col; + } + if (prev_end < (size_t)colored_ccline->cmdlen) { + kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) { + .start = prev_end, + .end = (size_t)colored_ccline->cmdlen, + .attr = 0, + })); + } +} + /// Color command-line /// /// Should use built-in command parser or user-specified one. Currently only the @@ -2422,13 +2480,8 @@ static bool color_cmdline(const CmdlineInfo *const colored_ccline, tl_ret = try_leave(&tstate, &err); can_free_cb = true; } else if (colored_ccline->cmdfirstc == '=') { - try_enter(&tstate); - err_errmsg = N_( - "E5409: Unable to get g:Nvim_color_expr callback: %s"); - dgc_ret = tv_dict_get_callback(&globvardict, S_LEN("Nvim_color_expr"), - &color_cb); - tl_ret = try_leave(&tstate, &err); - can_free_cb = true; + color_expr_cmdline(colored_ccline, ret_ccline_colors); + can_free_cb = false; } if (!tl_ret || !dgc_ret) { goto color_cmdline_error; diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index f65d7a6b13..843007b97b 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -2288,9 +2288,7 @@ int convert_setup_ext(vimconv_T *vcp, char_u *from, bool from_unicode_is_utf8, if (vcp->vc_type == CONV_ICONV && vcp->vc_fd != (iconv_t)-1) iconv_close(vcp->vc_fd); # endif - vcp->vc_type = CONV_NONE; - vcp->vc_factor = 1; - vcp->vc_fail = false; + *vcp = (vimconv_T)MBYTE_NONE_CONV; /* No conversion when one of the names is empty or they are equal. */ if (from == NULL || *from == NUL || to == NULL || *to == NUL diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h index fce600d0a9..a5ce1b0a15 100644 --- a/src/nvim/mbyte.h +++ b/src/nvim/mbyte.h @@ -60,6 +60,12 @@ typedef enum { CONV_ICONV = 5, } ConvFlags; +#define MBYTE_NONE_CONV { \ + .vc_type = CONV_NONE, \ + .vc_factor = 1, \ + .vc_fail = false, \ +} + /// Structure used for string conversions typedef struct { int vc_type; ///< Zero or more ConvFlags. diff --git a/src/nvim/viml/parser/parser.c b/src/nvim/viml/parser/parser.c new file mode 100644 index 0000000000..08d8846018 --- /dev/null +++ b/src/nvim/viml/parser/parser.c @@ -0,0 +1,13 @@ +#include "nvim/viml/parser/parser.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "viml/parser/parser.c.generated.h" +#endif + + +void parser_simple_get_line(void *cookie, ParserLine *ret_pline) +{ + ParserLine **plines_p = (ParserLine **)cookie; + *ret_pline = **plines_p; + (*plines_p)++; +} diff --git a/src/nvim/viml/parser/parser.h b/src/nvim/viml/parser/parser.h index fbc5ba5f07..7ac49709d8 100644 --- a/src/nvim/viml/parser/parser.h +++ b/src/nvim/viml/parser/parser.h @@ -8,6 +8,7 @@ #include "nvim/lib/kvec.h" #include "nvim/func_attr.h" #include "nvim/mbyte.h" +#include "nvim/memory.h" /// One parsed line typedef struct { @@ -80,6 +81,56 @@ typedef struct { bool can_continuate; } ParserState; +static inline void viml_parser_init( + ParserState *const ret_pstate, + const ParserLineGetter get_line, void *const cookie, + ParserHighlight *const colors) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1, 2); + +/// Initialize a new parser state instance +/// +/// @param[out] ret_pstate Parser state to initialize. +/// @param[in] get_line Line getter function. +/// @param[in] cookie Argument for the get_line function. +/// @param[in] colors Where to save highlighting. May be NULL if it is not +/// needed. +static inline void viml_parser_init( + ParserState *const ret_pstate, + const ParserLineGetter get_line, void *const cookie, + ParserHighlight *const colors) +{ + *ret_pstate = (ParserState) { + .reader = { + .get_line = get_line, + .cookie = cookie, + .conv = MBYTE_NONE_CONV, + }, + .pos = { 0, 0 }, + .colors = colors, + .can_continuate = false, + }; + kvi_init(ret_pstate->reader.lines); + kvi_init(ret_pstate->stack); +} + +static inline void viml_parser_destroy(ParserState *const pstate) + REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE; + +/// Free all memory allocated by the parser on heap +/// +/// @param pstate Parser state to free. +static inline void viml_parser_destroy(ParserState *const pstate) +{ + for (size_t i = 0; i < kv_size(pstate->reader.lines); i++) { + ParserLine pline = kv_A(pstate->reader.lines, i); + if (pline.allocated) { + xfree((void *)pline.data); + } + } + kvi_destroy(pstate->reader.lines); + kvi_destroy(pstate->stack); +} + static inline void viml_preader_get_line(ParserInputReader *const preader, ParserLine *const ret_pline) REAL_FATTR_NONNULL_ALL; @@ -186,4 +237,8 @@ static inline void viml_parser_highlight(ParserState *const pstate, })); } +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "viml/parser/parser.h.generated.h" +#endif + #endif // NVIM_VIML_PARSER_PARSER_H -- cgit From 1be29dc5acc071591bd776890fc3b61aba1488f9 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Oct 2017 16:54:50 +0300 Subject: gen_declarations: Do not generate line numbers by default --- src/nvim/generators/gen_declarations.lua | 43 ++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/generators/gen_declarations.lua b/src/nvim/generators/gen_declarations.lua index e999e53e4a..0c73376ba0 100755 --- a/src/nvim/generators/gen_declarations.lua +++ b/src/nvim/generators/gen_declarations.lua @@ -164,9 +164,40 @@ local pattern = concat( ) if fname == '--help' then - print'Usage:' - print() - print' gendeclarations.lua definitions.c static.h non-static.h preprocessor.i' + print([[ +Usage: + + gendeclarations.lua definitions.c static.h non-static.h definitions.i + +Generates declarations for a C file defitions.c, putting declarations for +static functions into static.h and declarations for non-static functions into +non-static.h. File `definitions.i' should contain an already preprocessed +version of defintions.c and it is the only one which is actually parsed, +definitions.c is needed only to determine functions from which file out of all +functions found in definitions.i are needed. + +Additionally uses the following environment variables: + + NVIM_GEN_DECLARATIONS_LINE_NUMBERS: + If set to 1 then all generated declarations receive a comment with file + name and line number after the declaration. This may be useful for + debugging gen_declarations script, but not much beyound that with + configured development environment (i.e. with ctags/cscope/finding + definitions with clang/etc). + + WARNING: setting this to 1 will cause extensive rebuilds: declarations + generator script will not regenerate non-static.h file if its + contents did not change, but including line numbers will make + contents actually change. + + With contents changed timestamp of the file is regenerated even + when no real changes were made (e.g. a few lines were added to + a function which is not at the bottom of the file). + + With changed timestamp build system will assume that header + changed, triggering rebuilds of all C files which depend on the + "changed" header. +]]) os.exit() end @@ -249,8 +280,10 @@ while init ~= nil do declaration = declaration:gsub(' $', '') declaration = declaration:gsub('^ ', '') declaration = declaration .. ';' - declaration = declaration .. (' // %s/%s:%u'):format( - curdir, curfile, declline) + if os.getenv('NVIM_GEN_DECLARATIONS_LINE_NUMBERS') == '1' then + declaration = declaration .. (' // %s/%s:%u'):format( + curdir, curfile, declline) + end declaration = declaration .. '\n' if declaration:sub(1, 6) == 'static' then static = static .. declaration -- cgit From 22d161a5dd1c519f998916f45d61be92662fbb44 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Oct 2017 20:11:44 +0300 Subject: api/vim: Add nvim_parse_expression function --- src/nvim/api/vim.c | 66 ++++++++++++++++++++++++++++++++++++++ src/nvim/viml/parser/expressions.c | 4 +-- src/nvim/viml/parser/expressions.h | 3 ++ 3 files changed, 70 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 98f4410347..9daa2fb398 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -886,6 +886,72 @@ theend: return rv; } +/// Parse a VimL expression +/// +/// @param[in] expr Expression to parse. Is always treated as a single line. +/// @param[in] flags Flags: "m" if multiple expressions in a row are allowed +/// (only the first one will be parsed), "E" if EOC tokens +/// are not allowed (determines whether they will stop +/// parsing process or be recognized as an operator/space, +/// though also yielding an error). +/// +/// Use only "m" to parse like for "=", only "E" to +/// parse like for ":echo", empty string for ":let". +/// +/// @return AST: top-level dectionary holds keys +/// +/// "error": Dictionary with error, present only if parser saw some +/// error. Contains the following keys: +/// +/// "message": String, error message in printf format, translated. +/// Must contain exactly one "%.*s". +/// "arg": String, error message argument. +/// +/// "ast": actual AST, a dictionary with the following keys: +/// +/// "type": node type, one of the value names from ExprASTNodeType +/// stringified without "kExprNode" prefix. +/// "start": a pair [line, column] describing where node is “started” +/// where "line" is always 0 (will not be 0 if you will be +/// using nvim_parse_viml() on e.g. ":let", but that is not +/// present yet). Both elements are Integers. +/// "len": “length” of the node. This and "start" are there for +/// debugging purposes primary (debugging parser and providing +/// debug information). +/// "children": a list of nodes described in top/"ast". There always +/// is zero, one or two children, key will contain an +/// empty array if node can have children, but has no and +/// will not be present at all if node can’t have any +/// children. Maximum number of children may be found in +/// node_maxchildren array. +/// +/// Local values (present only for certain nodes): +/// +/// "scope": a single Integer, specifies scope for "Option" and +/// "PlainIdentifier" nodes. For "Option" it is one of +/// ExprOptScope values, for "PlainIdentifier" it is one of +/// ExprVarScope values. +/// "ident": identifier (without scope, if any), present for "Option", +/// "PlainIdentifier", "PlainKey" and "Environment" nodes. +/// "name": Integer, register name (one character) or -1. Only present +/// for "Register" nodes. +/// "cmp_type": String, comparison type, one of the value names from +/// ExprComparisonType, stringified without "kExprCmp" +/// prefix. Only present for "Comparison" nodes. +/// "ccs_strategy": String, case comparison strategy, one of the +/// value names from ExprCaseCompareStrategy, +/// stringified without "kCCStrategy" prefix. Only +/// present for "Comparison" nodes. +/// "ivalue": Integer, integer value for "Integer" nodes. +/// "fvalue": Float, floating-point value for "Float" nodes. +/// "svalue": String, value for "SingleQuotedString" and +/// "DoubleQuotedString" nodes. +Dictionary nvim_parse_expression(String expr, String flags, Error *err) + FUNC_API_SINCE(4) +{ + return (Dictionary)ARRAY_DICT_INIT; +} + /// Writes a message to vim output or error buffer. The string is split /// and flushed after each newline. Incomplete lines are kept for writing diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index b413d56592..4e8a9b8523 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -849,8 +849,7 @@ static inline void viml_pexpr_debug_print_token( viml_pexpr_debug_print_token(pstate, tkn) #endif -#ifndef NDEBUG -static const uint8_t node_maxchildren[] = { +const uint8_t node_maxchildren[] = { [kExprNodeMissing] = 0, [kExprNodeOpMissing] = 2, [kExprNodeTernary] = 2, @@ -890,7 +889,6 @@ static const uint8_t node_maxchildren[] = { [kExprNodeOption] = 0, [kExprNodeEnvironment] = 0, }; -#endif /// Free memory occupied by AST /// diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index d783518b3a..d00d4855f3 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -338,6 +338,9 @@ typedef struct { ExprASTNode *root; } ExprAST; +/// Array mapping ExprASTNodeType to maximum amount of children node may have +extern const uint8_t node_maxchildren[]; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "viml/parser/expressions.h.generated.h" #endif -- cgit From 748f3ad5bbf9706dddddeea5df693221d6ae5e94 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Oct 2017 21:30:06 +0300 Subject: syntax,viml/expressions/parser: Create defaults for expr highlighting --- src/nvim/ex_docmd.c | 5 +- src/nvim/syntax.c | 599 +++++++++++++++++++++++-------------- src/nvim/viml/parser/expressions.c | 115 +------ 3 files changed, 386 insertions(+), 333 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 3130747e08..664ab69792 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -5911,9 +5911,10 @@ static void ex_colorscheme(exarg_T *eap) static void ex_highlight(exarg_T *eap) { - if (*eap->arg == NUL && eap->cmd[2] == '!') + if (*eap->arg == NUL && eap->cmd[2] == '!') { MSG(_("Greetings, Vim user!")); - do_highlight(eap->arg, eap->forceit, FALSE); + } + do_highlight((const char *)eap->arg, eap->forceit, false); } diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 70bda42d83..b418df77a4 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -5929,8 +5929,7 @@ static void syntime_report(void) // // When making changes here, also change runtime/colors/default.vim! -static char *highlight_init_both[] = -{ +static const char *highlight_init_both[] = { "Conceal ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey", "Cursor guibg=fg guifg=bg", "lCursor guibg=fg guifg=bg", @@ -5954,8 +5953,7 @@ static char *highlight_init_both[] = NULL }; -static char *highlight_init_light[] = -{ +static const char *highlight_init_light[] = { "ColorColumn ctermbg=LightRed guibg=LightRed", "CursorColumn ctermbg=LightGrey guibg=Grey90", "CursorLine cterm=underline guibg=Grey90", @@ -5988,8 +5986,7 @@ static char *highlight_init_light[] = NULL }; -static char *highlight_init_dark[] = -{ +static const char *highlight_init_dark[] = { "ColorColumn ctermbg=DarkRed guibg=DarkRed", "CursorColumn ctermbg=DarkGrey guibg=Grey40", "CursorLine cterm=underline guibg=Grey40", @@ -6022,6 +6019,157 @@ static char *highlight_init_dark[] = NULL }; +static const char *highlight_init_cmdline[] = { + // NVimInternalError should appear only when highlighter has a bug. + "NVimInternalError ctermfg=Red ctermbg=Red guifg=Red guibg=Red", + + // Highlight groups (links) used by parser: + + "default link NVimOperator Operator", + + "default link NVimUnaryOperator NVimOperator", + "default link NVimUnaryPlus NVimUnaryOperator", + + "default link NVimBinaryOperator NVimOperator", + "default link NVimComparison NVimBinaryOperator", + "default link NVimComparisonModifier NVimComparison", + "default link NVimBinaryPlus NVimBinaryOperator", + "default link NVimConcat NVimBinaryOperator", + "default link NVimConcatOrSubscript NVimConcat", + + "default link NVimTernary NVimOperator", + "default link NVimTernaryColon NVimTernary", + + "default link NVimParenthesis Delimiter", + "default link NVimLambda NVimParenthesis", + "default link NVimNestingParenthesis NVimParenthesis", + "default link NVimCallingParenthesis NVimParenthesis", + + "default link NVimSubscript NVimParenthesis", + "default link NVimSubscriptBracket NVimSubscript", + "default link NVimSubscriptColon NVimSubscript", + "default link NVimSubscriptColon NVimSubscript", + "default link NVimCurly NVimSubscript", + + "default link NVimContainer NVimParenthesis", + "default link NVimDict NVimContainer", + "default link NVimList NVimContainer", + + "default link NVimIdentifier Identifier", + "default link NVimIdentifierScope NVimIdentifier", + "default link NVimIdentifierScopeDelimiter NVimIdentifier", + "default link NVimIdentifierName NVimIdentifier", + "default link NVimIdentifierKey NVimIdentifier", + + "default link NVimColon Delimiter", + "default link NVimComma Delimiter", + "default link NVimArrow Delimiter", + + "default link NVimRegister SpecialChar", + "default link NVimNumber Number", + "default link NVimFloat NVimNumber", + "default link NVimNumberPrefix SpecialChar", + + "default link NVimString String", + "default link NVimStringBody NVimString", + "default link NVimStringQuote NVimString", + "default link NVimStringSpecial SpecialChar", + + "default link NVimSingleQuote NVimStringQuote", + "default link NVimSingleQuotedBody NVimStringBody", + "default link NVimSingleQuotedQuote NVimStringSpecial", + + "default link NVimDoubleQuote NVimStringQuote", + "default link NVimDoubleQuotedBody NVimStringBody", + "default link NVimDoubleQuotedEscape NVimStringSpecial", + // Not actually invalid, but we highlight user that he is doing something + // wrong. + "default link NVimDoubleQuotedUnknownEscape NVimInvalidValue", + + "default link NVimFigureBrace NVimInternalError", + "default link NVimSingleQuotedUnknownEscape NVimInternalError", + + // NVimInvalid groups: + + "default link NVimInvalidSingleQuotedUnknownEscape NVimInternalError", + + "default link NVimInvalid Error", + + "default link NVimInvalidOperator NVimInvalid", + + "default link NVimInvalidUnaryOperator NVimInvalidOperator", + "default link NVimInvalidUnaryPlus NVimInvalidUnaryOperator", + + "default link NVimInvalidBinaryOperator NVimInvalidOperator", + "default link NVimInvalidComparison NVimInvalidBinaryOperator", + "default link NVimInvalidComparisonModifier NVimInvalidComparison", + "default link NVimInvalidBinaryPlus NVimInvalidBinaryOperator", + "default link NVimInvalidConcat NVimInvalidBinaryOperator", + "default link NVimInvalidConcatOrSubscript NVimInvalidConcat", + + "default link NVimInvalidTernary NVimInvalidOperator", + "default link NVimInvalidTernaryColon NVimInvalidTernary", + + "default link NVimInvalidDelimiter NVimInvalid", + + "default link NVimInvalidParenthesis NVimInvalidDelimiter", + "default link NVimInvalidLambda NVimInvalidParenthesis", + "default link NVimInvalidNestingParenthesis NVimInvalidParenthesis", + "default link NVimInvalidCallingParenthesis NVimInvalidParenthesis", + + "default link NVimInvalidSubscript NVimInvalidParenthesis", + "default link NVimInvalidSubscriptBracket NVimInvalidSubscript", + "default link NVimInvalidSubscriptColon NVimInvalidSubscript", + "default link NVimInvalidSubscriptColon NVimInvalidSubscript", + "default link NVimInvalidCurly NVimInvalidSubscript", + + "default link NVimInvalidContainer NVimInvalidParenthesis", + "default link NVimInvalidDict NVimInvalidContainer", + "default link NVimInvalidList NVimInvalidContainer", + + "default link NVimInvalidIdentifier Identifier", + "default link NVimInvalidIdentifierScope NVimIdentifier", + "default link NVimInvalidIdentifierScopeDelimiter NVimIdentifier", + "default link NVimInvalidIdentifierName NVimIdentifier", + "default link NVimInvalidIdentifierKey NVimIdentifier", + + "default link NVimInvalidColon NVimInvalidDelimiter", + "default link NVimInvalidComma NVimInvalidDelimiter", + "default link NVimInvalidArrow NVimInvalidDelimiter", + + "default link NVimInvalidValue NVimInvalid", + + "default link NVimInvalidRegister NVimInvalidValue", + "default link NVimInvalidNumber NVimInvalidValue", + "default link NVimInvalidFloat NVimInvalidNumber", + "default link NVimInvalidNumberPrefix NVimInvalidNumber", + + // Invalid string bodies and specials are still highlighted as valid ones to + // minimize the red area. + "default link NVimInvalidString NVimInvalidValue", + "default link NVimInvalidStringBody NVimString", + "default link NVimInvalidStringQuote NVimInvalidString", + "default link NVimInvalidStringSpecial NVimStringSpecial", + + "default link NVimInvalidSingleQuote NVimInvalidStringQuote", + "default link NVimInvalidSingleQuotedBody NVimInvalidStringBody", + "default link NVimInvalidSingleQuotedQuote NVimInvalidStringSpecial", + + "default link NVimInvalidDoubleQuote NVimInvalidStringQuote", + "default link NVimInvalidDoubleQuotedBody NVimInvalidStringBody", + "default link NVimInvalidDoubleQuotedEscape NVimInvalidStringSpecial", + "default link NVimInvalidDoubleQuotedUnknownEscape NVimInvalidValue", + + "default link NVimInvalidFigureBrace NVimInternalError", +}; + +/// Create default links for NVim* highlight groups used for cmdline coloring +void syn_init_cmdline_highlight(bool reset, bool init) +{ + for (size_t i = 0 ; i < ARRAY_SIZE(highlight_init_cmdline) ; i++) { + do_highlight(highlight_init_cmdline[i], reset, init); + } +} /// Load colors from a file if "g:colors_name" is set, otherwise load builtin /// colors @@ -6032,7 +6180,6 @@ void init_highlight(int both, int reset) { int i; - char **pp; static int had_both = FALSE; // Try finding the color scheme file. Used when a color file was loaded @@ -6054,9 +6201,9 @@ init_highlight(int both, int reset) */ if (both) { had_both = TRUE; - pp = highlight_init_both; + const char *const *const pp = highlight_init_both; for (i = 0; pp[i] != NULL; i++) { - do_highlight((char_u *)pp[i], reset, true); + do_highlight(pp[i], reset, true); } } else if (!had_both) { // Don't do anything before the call with both == TRUE from main(). @@ -6065,10 +6212,11 @@ init_highlight(int both, int reset) return; } - pp = (*p_bg == 'l') ? highlight_init_light : highlight_init_dark; - + const char *const *const pp = ((*p_bg == 'l') + ? highlight_init_light + : highlight_init_dark); for (i = 0; pp[i] != NULL; i++) { - do_highlight((char_u *)pp[i], reset, true); + do_highlight(pp[i], reset, true); } /* Reverse looks ugly, but grey may not work for 8 colors. Thus let it @@ -6078,15 +6226,13 @@ init_highlight(int both, int reset) * Clear the attributes, needed when changing the t_Co value. */ if (t_colors > 8) { do_highlight( - (char_u *)(*p_bg == 'l' - ? "Visual cterm=NONE ctermbg=LightGrey" - : "Visual cterm=NONE ctermbg=DarkGrey"), false, - true); + (*p_bg == 'l' + ? "Visual cterm=NONE ctermbg=LightGrey" + : "Visual cterm=NONE ctermbg=DarkGrey"), false, true); } else { - do_highlight((char_u *)"Visual cterm=reverse ctermbg=NONE", - FALSE, TRUE); + do_highlight("Visual cterm=reverse ctermbg=NONE", false, true); if (*p_bg == 'l') - do_highlight((char_u *)"Search ctermfg=black", FALSE, TRUE); + do_highlight("Search ctermfg=black", false, true); } /* @@ -6102,6 +6248,10 @@ init_highlight(int both, int reset) (void)source_runtime((char_u *)"syntax/syncolor.vim", DIP_ALL); recursive--; } + // Without syncolor.vim it is going to screw everything over by defining + // cleared highlight groups by creating links to non-existent groups. This + // effectively prevents ":highlight default" from working properly. + syn_init_cmdline_highlight(reset, true); } } @@ -6137,17 +6287,22 @@ int load_colors(char_u *name) } -/// Handle the ":highlight .." command. -/// When using ":hi clear" this is called recursively for each group with -/// "forceit" and "init" both TRUE. -/// @param init TRUE when called for initializing -void -do_highlight(char_u *line, int forceit, int init) { - char_u *name_end; - char_u *linep; - char_u *key_start; - char_u *arg_start; - char_u *key = NULL, *arg = NULL; +/// Handle ":highlight" command +/// +/// When using ":highlight clear" this is called recursively for each group with +/// forceit and init being both true. +/// +/// @param[in] line Command arguments. +/// @param[in] forceit True when bang is given, allows to link group even if +/// it has its own settings. +/// @param[in] init True when initializing. +void do_highlight(const char *line, const bool forceit, const bool init) + FUNC_ATTR_NONNULL_ALL +{ + const char *name_end; + const char *linep; + const char *key_start; + const char *arg_start; long i; int off; int len; @@ -6161,94 +6316,87 @@ do_highlight(char_u *line, int forceit, int init) { int color; bool is_normal_group = false; // "Normal" group - /* - * If no argument, list current highlighting. - */ - if (ends_excmd(*line)) { + // If no argument, list current highlighting. + if (ends_excmd((uint8_t)(*line))) { for (int i = 1; i <= highlight_ga.ga_len && !got_int; i++) { - // todo(vim): only call when the group has attributes set + // TODO(brammool): only call when the group has attributes set highlight_list_one(i); } return; } - /* - * Isolate the name. - */ - name_end = skiptowhite(line); - linep = skipwhite(name_end); + // Isolate the name. + name_end = (const char *)skiptowhite((const char_u *)line); + linep = (const char *)skipwhite((const char_u *)name_end); - /* - * Check for "default" argument. - */ - if (STRNCMP(line, "default", name_end - line) == 0) { - dodefault = TRUE; + // Check for "default" argument. + if (strncmp(line, "default", name_end - line) == 0) { + dodefault = true; line = linep; - name_end = skiptowhite(line); - linep = skipwhite(name_end); + name_end = (const char *)skiptowhite((const char_u *)line); + linep = (const char *)skipwhite((const char_u *)name_end); } - /* - * Check for "clear" or "link" argument. - */ - if (STRNCMP(line, "clear", name_end - line) == 0) - doclear = TRUE; - if (STRNCMP(line, "link", name_end - line) == 0) - dolink = TRUE; + // Check for "clear" or "link" argument. + if (strncmp(line, "clear", name_end - line) == 0) { + doclear = true; + } else if (strncmp(line, "link", name_end - line) == 0) { + dolink = true; + } - /* - * ":highlight {group-name}": list highlighting for one group. - */ - if (!doclear && !dolink && ends_excmd(*linep)) { - id = syn_namen2id(line, (int)(name_end - line)); - if (id == 0) - EMSG2(_("E411: highlight group not found: %s"), line); - else + // ":highlight {group-name}": list highlighting for one group. + if (!doclear && !dolink && ends_excmd((uint8_t)(*linep))) { + id = syn_namen2id((const char_u *)line, (int)(name_end - line)); + if (id == 0) { + emsgf(_("E411: highlight group not found: %s"), line); + } else { highlight_list_one(id); + } return; } - /* - * Handle ":highlight link {from} {to}" command. - */ + // Handle ":highlight link {from} {to}" command. if (dolink) { - char_u *from_start = linep; - char_u *from_end; - char_u *to_start; - char_u *to_end; + const char *from_start = linep; + const char *from_end; + const char *to_start; + const char *to_end; int from_id; int to_id; - from_end = skiptowhite(from_start); - to_start = skipwhite(from_end); - to_end = skiptowhite(to_start); + from_end = (const char *)skiptowhite((const char_u *)from_start); + to_start = (const char *)skipwhite((const char_u *)from_end); + to_end = (const char *)skiptowhite((const char_u *)to_start); - if (ends_excmd(*from_start) || ends_excmd(*to_start)) { - EMSG2(_("E412: Not enough arguments: \":highlight link %s\""), - from_start); + if (ends_excmd((uint8_t)(*from_start)) + || ends_excmd((uint8_t)(*to_start))) { + emsgf(_("E412: Not enough arguments: \":highlight link %s\""), + from_start); return; } - if (!ends_excmd(*skipwhite(to_end))) { - EMSG2(_("E413: Too many arguments: \":highlight link %s\""), from_start); + if (!ends_excmd(*skipwhite((const char_u *)to_end))) { + emsgf(_("E413: Too many arguments: \":highlight link %s\""), from_start); return; } - from_id = syn_check_group(from_start, (int)(from_end - from_start)); - if (STRNCMP(to_start, "NONE", 4) == 0) + from_id = syn_check_group((const char_u *)from_start, + (int)(from_end - from_start)); + if (strncmp(to_start, "NONE", 4) == 0) { to_id = 0; - else - to_id = syn_check_group(to_start, (int)(to_end - to_start)); + } else { + to_id = syn_check_group((const char_u *)to_start, + (int)(to_end - to_start)); + } if (from_id > 0 && (!init || HL_TABLE()[from_id - 1].sg_set == 0)) { - /* - * Don't allow a link when there already is some highlighting - * for the group, unless '!' is used - */ + // Don't allow a link when there already is some highlighting + // for the group, unless '!' is used if (to_id > 0 && !forceit && !init && hl_has_settings(from_id - 1, dodefault)) { - if (sourcing_name == NULL && !dodefault) + if (sourcing_name == NULL && !dodefault) { EMSG(_("E414: group has settings, highlight link ignored")); + } } else { if (!init) HL_TABLE()[from_id - 1].sg_set |= SG_LINK; @@ -6258,43 +6406,38 @@ do_highlight(char_u *line, int forceit, int init) { } } - /* Only call highlight_changed() once, after sourcing a syntax file */ - need_highlight_changed = TRUE; + // Only call highlight_changed() once, after sourcing a syntax file. + need_highlight_changed = true; return; } if (doclear) { - /* - * ":highlight clear [group]" command. - */ + // ":highlight clear [group]" command. line = linep; - if (ends_excmd(*line)) { + if (ends_excmd((uint8_t)(*line))) { do_unlet(S_LEN("colors_name"), true); restore_cterm_colors(); - /* - * Clear all default highlight groups and load the defaults. - */ + // Clear all default highlight groups and load the defaults. for (int idx = 0; idx < highlight_ga.ga_len; ++idx) { highlight_clear(idx); } - init_highlight(TRUE, TRUE); + init_highlight(true, true); highlight_changed(); redraw_later_clear(); return; } - name_end = skiptowhite(line); - linep = skipwhite(name_end); + name_end = (const char *)skiptowhite((const char_u *)line); + linep = (const char *)skipwhite((const char_u *)name_end); } - /* - * Find the group name in the table. If it does not exist yet, add it. - */ - id = syn_check_group(line, (int)(name_end - line)); - if (id == 0) /* failed (out of memory) */ + // Find the group name in the table. If it does not exist yet, add it. + id = syn_check_group((const char_u *)line, (int)(name_end - line)); + if (id == 0) { // Failed (out of memory). return; - idx = id - 1; /* index is ID minus one */ + } + idx = id - 1; // Index is ID minus one. // Return if "default" was used and the group already has settings if (dodefault && hl_has_settings(idx, true)) { @@ -6303,19 +6446,21 @@ do_highlight(char_u *line, int forceit, int init) { is_normal_group = (STRCMP(HL_TABLE()[idx].sg_name_u, "NORMAL") == 0); - /* Clear the highlighting for ":hi clear {group}" and ":hi clear". */ + // Clear the highlighting for ":hi clear {group}" and ":hi clear". if (doclear || (forceit && init)) { highlight_clear(idx); if (!doclear) HL_TABLE()[idx].sg_set = 0; } + char *key = NULL; + char *arg = NULL; if (!doclear) { - while (!ends_excmd(*linep)) { + while (!ends_excmd((uint8_t)(*linep))) { key_start = linep; if (*linep == '=') { - EMSG2(_("E415: unexpected equal sign: %s"), key_start); - error = TRUE; + emsgf(_("E415: unexpected equal sign: %s"), key_start); + error = true; break; } @@ -6325,61 +6470,58 @@ do_highlight(char_u *line, int forceit, int init) { linep++; } xfree(key); - key = vim_strnsave_up(key_start, (int)(linep - key_start)); - linep = skipwhite(linep); + key = (char *)vim_strnsave_up((const char_u *)key_start, + (int)(linep - key_start)); + linep = (const char *)skipwhite((const char_u *)linep); - if (STRCMP(key, "NONE") == 0) { + if (strcmp(key, "NONE") == 0) { if (!init || HL_TABLE()[idx].sg_set == 0) { - if (!init) + if (!init) { HL_TABLE()[idx].sg_set |= SG_CTERM+SG_GUI; + } highlight_clear(idx); } continue; } - /* - * Check for the equal sign. - */ + // Check for the equal sign. if (*linep != '=') { - EMSG2(_("E416: missing equal sign: %s"), key_start); - error = TRUE; + emsgf(_("E416: missing equal sign: %s"), key_start); + error = true; break; } - ++linep; + linep++; - /* - * Isolate the argument. - */ - linep = skipwhite(linep); - if (*linep == '\'') { /* guifg='color name' */ + // Isolate the argument. + linep = (const char *)skipwhite((const char_u *)linep); + if (*linep == '\'') { // guifg='color name' arg_start = ++linep; - linep = vim_strchr(linep, '\''); + linep = strchr(linep, '\''); if (linep == NULL) { - EMSG2(_(e_invarg2), key_start); - error = TRUE; + emsgf(_(e_invarg2), key_start); + error = true; break; } } else { arg_start = linep; - linep = skiptowhite(linep); + linep = (const char *)skiptowhite((const char_u *)linep); } if (linep == arg_start) { - EMSG2(_("E417: missing argument: %s"), key_start); - error = TRUE; + emsgf(_("E417: missing argument: %s"), key_start); + error = true; break; } xfree(arg); - arg = vim_strnsave(arg_start, (int)(linep - arg_start)); + arg = xstrndup(arg_start, (size_t)(linep - arg_start)); - if (*linep == '\'') - ++linep; + if (*linep == '\'') { + linep++; + } - /* - * Store the argument. - */ - if ( STRCMP(key, "TERM") == 0 - || STRCMP(key, "CTERM") == 0 - || STRCMP(key, "GUI") == 0) { + // Store the argument. + if (strcmp(key, "TERM") == 0 + || strcmp(key, "CTERM") == 0 + || strcmp(key, "GUI") == 0) { attr = 0; off = 0; while (arg[off] != NUL) { @@ -6392,26 +6534,30 @@ do_highlight(char_u *line, int forceit, int init) { } } if (i < 0) { - EMSG2(_("E418: Illegal value: %s"), arg); - error = TRUE; + emsgf(_("E418: Illegal value: %s"), arg); + error = true; break; } - if (arg[off] == ',') /* another one follows */ - ++off; + if (arg[off] == ',') { // Another one follows. + off++; + } } - if (error) + if (error) { break; + } if (*key == 'C') { if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM)) { - if (!init) + if (!init) { HL_TABLE()[idx].sg_set |= SG_CTERM; + } HL_TABLE()[idx].sg_cterm = attr; HL_TABLE()[idx].sg_cterm_bold = FALSE; } } else if (*key == 'G') { if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { - if (!init) + if (!init) { HL_TABLE()[idx].sg_set |= SG_GUI; + } HL_TABLE()[idx].sg_gui = attr; } } @@ -6430,14 +6576,14 @@ do_highlight(char_u *line, int forceit, int init) { HL_TABLE()[idx].sg_cterm_bold = FALSE; } - if (ascii_isdigit(*arg)) + if (ascii_isdigit(*arg)) { color = atoi((char *)arg); - else if (STRICMP(arg, "fg") == 0) { - if (cterm_normal_fg_color) + } else if (STRICMP(arg, "fg") == 0) { + if (cterm_normal_fg_color) { color = cterm_normal_fg_color - 1; - else { + } else { EMSG(_("E419: FG color unknown")); - error = TRUE; + error = true; break; } } else if (STRICMP(arg, "bg") == 0) { @@ -6445,70 +6591,85 @@ do_highlight(char_u *line, int forceit, int init) { color = cterm_normal_bg_color - 1; else { EMSG(_("E420: BG color unknown")); - error = TRUE; + error = true; break; } } else { - static char *(color_names[28]) = { + static const char *color_names[] = { "Black", "DarkBlue", "DarkGreen", "DarkCyan", "DarkRed", "DarkMagenta", "Brown", "DarkYellow", "Gray", "Grey", "LightGray", "LightGrey", "DarkGray", "DarkGrey", "Blue", "LightBlue", "Green", "LightGreen", "Cyan", "LightCyan", "Red", "LightRed", "Magenta", - "LightMagenta", "Yellow", "LightYellow", "White", "NONE" + "LightMagenta", "Yellow", "LightYellow", "White", + "NONE" + }; + static const int color_numbers_16[] = { + 0, 1, 2, 3, + 4, 5, 6, 6, + 7, 7, + 7, 7, 8, 8, + 9, 9, 10, 10, + 11, 11, 12, 12, 13, + 13, 14, 14, 15, + -1 + }; + // For xterm with 88 colors: + static int color_numbers_88[] = { + 0, 4, 2, 6, + 1, 5, 32, 72, + 84, 84, + 7, 7, 82, 82, + 12, 43, 10, 61, + 14, 63, 9, 74, 13, + 75, 11, 78, 15, + -1 + }; + // For xterm with 256 colors: + static int color_numbers_256[] = { + 0, 4, 2, 6, + 1, 5, 130, 130, + 248, 248, + 7, 7, 242, 242, + 12, 81, 10, 121, + 14, 159, 9, 224, 13, + 225, 11, 229, 15, + -1 }; - static int color_numbers_16[28] = {0, 1, 2, 3, - 4, 5, 6, 6, - 7, 7, - 7, 7, 8, 8, - 9, 9, 10, 10, - 11, 11, 12, 12, 13, - 13, 14, 14, 15, -1}; - /* for xterm with 88 colors... */ - static int color_numbers_88[28] = {0, 4, 2, 6, - 1, 5, 32, 72, - 84, 84, - 7, 7, 82, 82, - 12, 43, 10, 61, - 14, 63, 9, 74, 13, - 75, 11, 78, 15, -1}; - /* for xterm with 256 colors... */ - static int color_numbers_256[28] = {0, 4, 2, 6, - 1, 5, 130, 130, - 248, 248, - 7, 7, 242, 242, - 12, 81, 10, 121, - 14, 159, 9, 224, 13, - 225, 11, 229, 15, -1}; - /* for terminals with less than 16 colors... */ - static int color_numbers_8[28] = {0, 4, 2, 6, - 1, 5, 3, 3, - 7, 7, - 7, 7, 0+8, 0+8, - 4+8, 4+8, 2+8, 2+8, - 6+8, 6+8, 1+8, 1+8, 5+8, - 5+8, 3+8, 3+8, 7+8, -1}; - - /* reduce calls to STRICMP a bit, it can be slow */ + // For terminals with less than 16 colors: + static int color_numbers_8[28] = { + 0, 4, 2, 6, + 1, 5, 3, 3, + 7, 7, + 7, 7, 0+8, 0+8, + 4+8, 4+8, 2+8, 2+8, + 6+8, 6+8, 1+8, 1+8, 5+8, + 5+8, 3+8, 3+8, 7+8, + -1 + }; + + // Reduce calls to STRICMP a bit, it can be slow. off = TOUPPER_ASC(*arg); - for (i = ARRAY_SIZE(color_names); --i >= 0; ) + for (i = ARRAY_SIZE(color_names); --i >= 0; ) { if (off == color_names[i][0] - && STRICMP(arg + 1, color_names[i] + 1) == 0) + && STRICMP(arg + 1, color_names[i] + 1) == 0) { break; + } + } if (i < 0) { - EMSG2(_( + emsgf(_( "E421: Color name or number not recognized: %s"), key_start); - error = TRUE; + error = true; break; } - /* Use the _16 table to check if its a valid color name. */ + // Use the _16 table to check if its a valid color name. color = color_numbers_16[i]; if (color >= 0) { if (t_colors == 8) { - /* t_Co is 8: use the 8 colors table */ + // t_Co is 8: use the 8 colors table. color = color_numbers_8[i]; if (key[5] == 'F') { /* set/reset bold attribute to get light foreground @@ -6532,16 +6693,14 @@ do_highlight(char_u *line, int forceit, int init) { } } } - /* Add one to the argument, to avoid zero. Zero is used for - * "NONE", then "color" is -1. */ + // Add one to the argument, to avoid zero. Zero is used for + // "NONE", then "color" is -1. if (key[5] == 'F') { HL_TABLE()[idx].sg_cterm_fg = color + 1; if (is_normal_group) { cterm_normal_fg_color = color + 1; cterm_normal_fg_bold = (HL_TABLE()[idx].sg_cterm & HL_BOLD); - { - must_redraw = CLEAR; - } + must_redraw = CLEAR; } } else { HL_TABLE()[idx].sg_cterm_bg = color + 1; @@ -6565,15 +6724,15 @@ do_highlight(char_u *line, int forceit, int init) { } } } - } else if (STRCMP(key, "GUIFG") == 0) { + } else if (strcmp(key, "GUIFG") == 0) { if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { if (!init) HL_TABLE()[idx].sg_set |= SG_GUI; xfree(HL_TABLE()[idx].sg_rgb_fg_name); - if (STRCMP(arg, "NONE")) { - HL_TABLE()[idx].sg_rgb_fg_name = (uint8_t *)xstrdup((char *)arg); - HL_TABLE()[idx].sg_rgb_fg = name_to_color(arg); + if (strcmp(arg, "NONE")) { + HL_TABLE()[idx].sg_rgb_fg_name = (char_u *)xstrdup((char *)arg); + HL_TABLE()[idx].sg_rgb_fg = name_to_color((const char_u *)arg); } else { HL_TABLE()[idx].sg_rgb_fg_name = NULL; HL_TABLE()[idx].sg_rgb_fg = -1; @@ -6590,8 +6749,8 @@ do_highlight(char_u *line, int forceit, int init) { xfree(HL_TABLE()[idx].sg_rgb_bg_name); if (STRCMP(arg, "NONE") != 0) { - HL_TABLE()[idx].sg_rgb_bg_name = (uint8_t *)xstrdup((char *)arg); - HL_TABLE()[idx].sg_rgb_bg = name_to_color(arg); + HL_TABLE()[idx].sg_rgb_bg_name = (char_u *)xstrdup((char *)arg); + HL_TABLE()[idx].sg_rgb_bg = name_to_color((const char_u *)arg); } else { HL_TABLE()[idx].sg_rgb_bg_name = NULL; HL_TABLE()[idx].sg_rgb_bg = -1; @@ -6601,15 +6760,15 @@ do_highlight(char_u *line, int forceit, int init) { if (is_normal_group) { normal_bg = HL_TABLE()[idx].sg_rgb_bg; } - } else if (STRCMP(key, "GUISP") == 0) { + } else if (strcmp(key, "GUISP") == 0) { if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { if (!init) HL_TABLE()[idx].sg_set |= SG_GUI; xfree(HL_TABLE()[idx].sg_rgb_sp_name); - if (STRCMP(arg, "NONE") != 0) { - HL_TABLE()[idx].sg_rgb_sp_name = (uint8_t *)xstrdup((char *)arg); - HL_TABLE()[idx].sg_rgb_sp = name_to_color(arg); + if (strcmp(arg, "NONE") != 0) { + HL_TABLE()[idx].sg_rgb_sp_name = (char_u *)xstrdup((char *)arg); + HL_TABLE()[idx].sg_rgb_sp = name_to_color((const char_u *)arg); } else { HL_TABLE()[idx].sg_rgb_sp_name = NULL; HL_TABLE()[idx].sg_rgb_sp = -1; @@ -6619,31 +6778,25 @@ do_highlight(char_u *line, int forceit, int init) { if (is_normal_group) { normal_sp = HL_TABLE()[idx].sg_rgb_sp; } - } else if (STRCMP(key, "START") == 0 || STRCMP(key, "STOP") == 0) { + } else if (strcmp(key, "START") == 0 || strcmp(key, "STOP") == 0) { // Ignored for now } else { - EMSG2(_("E423: Illegal argument: %s"), key_start); - error = TRUE; + emsgf(_("E423: Illegal argument: %s"), key_start); + error = true; break; } - /* - * When highlighting has been given for a group, don't link it. - */ + // When highlighting has been given for a group, don't link it. if (!init || !(HL_TABLE()[idx].sg_set & SG_LINK)) { HL_TABLE()[idx].sg_link = 0; } - /* - * Continue with next argument. - */ - linep = skipwhite(linep); + // Continue with next argument. + linep = (const char *)skipwhite((const char_u *)linep); } } - /* - * If there is an error, and it's a new entry, remove it from the table. - */ + // If there is an error, and it's a new entry, remove it from the table. if (error && idx == highlight_ga.ga_len) { syn_unadd_group(); } else { @@ -7201,7 +7354,7 @@ char_u *syn_id2name(int id) /* * Like syn_name2id(), but take a pointer + length argument. */ -int syn_namen2id(char_u *linep, int len) +int syn_namen2id(const char_u *linep, int len) { char_u *name = vim_strnsave(linep, len); int id = syn_name2id(name); @@ -7217,7 +7370,7 @@ int syn_namen2id(char_u *linep, int len) /// @param len length of \p pp /// /// @return 0 for failure else the id of the group -int syn_check_group(char_u *pp, int len) +int syn_check_group(const char_u *pp, int len) { char_u *name = vim_strnsave(pp, len); int id = syn_name2id(name); @@ -8220,7 +8373,7 @@ color_name_table_T color_name_table[] = { /// /// @param[in] name string value to convert to RGB /// return the hex value or -1 if could not find a correct value -RgbValue name_to_color(const uint8_t *name) +RgbValue name_to_color(const char_u *name) { if (name[0] == '#' && isxdigit(name[1]) && isxdigit(name[2]) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 4e8a9b8523..615a59573f 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -993,107 +993,6 @@ void viml_pexpr_free_ast(ExprAST ast) // > ># >? <= <=# <=? // < <# = >=# >=? // is is# is? isnot isnot# isnot? -// -// Used highlighting groups and assumed linkage: -// -// NVimInternalError -> highlight as fg:red/bg:red -// -// NVimInvalid -> Error -// NVimInvalidValue -> NVimInvalid -// NVimInvalidOperator -> NVimInvalid -// NVimInvalidDelimiter -> NVimInvalid -// -// NVimOperator -> Operator -// NVimUnaryOperator -> NVimOperator -// NVimBinaryOperator -> NVimOperator -// -// NVimComparisonOperator -> NVimBinaryOperator -// NVimComparisonOperatorModifier -> NVimComparisonOperator -// -// NVimTernary -> NVimOperator -// NVimTernaryColon -> NVimTernary -// -// NVimParenthesis -> Delimiter -// -// NVimColon -> Delimiter -// NVimComma -> Delimiter -// NVimArrow -> Delimiter -// -// NVimLambda -> Delimiter -// NVimDict -> Delimiter -// NVimCurly -> Delimiter -// -// NVimList -> Delimiter -// NVimSubscript -> Delimiter -// NVimSubscriptColon -> NVimSubscript -// -// NVimIdentifier -> Identifier -// NVimIdentifierScope -> NVimIdentifier -// NVimIdentifierScopeDelimiter -> NVimIdentifier -// -// NVimIdentifierKey -> Identifier -// -// NVimUnaryPlus -> NVimUnaryOperator -// NVimBinaryPlus -> NVimBinaryOperator -// NVimConcat -> NVimBinaryOperator -// NVimConcatOrSubscript -> NVimConcat -// -// NVimRegister -> SpecialChar -// NVimNumber -> Number -// NVimNumberPrefix -> SpecialChar -// NVimFloat -> NVimNumber -// -// NVimNestingParenthesis -> NVimParenthesis -// NVimCallingParenthesis -> NVimParenthesis -// -// NVimString -> String -// NVimStringSpecial -> SpecialChar -// NVimSingleQuote -> NVimString -// NVimSingleQuotedBody -> NVimString -// NVimSingleQuotedQuote -> NVimStringSpecial -// NVimDoubleQuote -> NVimString -// NVimDoubleQuotedBody -> NVimString -// NVimDoubleQuotedEscape -> NVimStringSpecial -// NVimDoubleQuotedUnknownEscape -> NVimInvalid -// -// " Note: NVimDoubleQuotedUnknownEscape is not actually invalid -// -// NVimInvalidComma -> NVimInvalidDelimiter -// NVimInvalidSpacing -> NVimInvalid -// NVimInvalidTernary -> NVimInvalidOperator -// NVimInvalidTernaryColon -> NVimInvalidTernary -// NVimInvalidRegister -> NVimInvalidValue -// NVimInvalidClosingBracket -> NVimInvalidDelimiter -// NVimInvalidSpacing -> NVimInvalid -// NVimInvalidArrow -> NVimInvalidDelimiter -// NVimInvalidLambda -> NVimInvalidDelimiter -// NVimInvalidDict -> NVimInvalidDelimiter -// NVimInvalidCurly -> NVimInvalidDelimiter -// NVimInvalidFigureBrace -> NVimInvalidDelimiter -// NVimInvalidIdentifier -> NVimInvalidValue -// NVimInvalidIdentifierScope -> NVimInvalidValue -// NVimInvalidIdentifierScopeDelimiter -> NVimInvalidValue -// NVimInvalidComparisonOperator -> NVimInvalidOperator -// NVimInvalidComparisonOperatorModifier -> NVimInvalidComparisonOperator -// NVimInvalidNumber -> NVimInvalidValue -// NVimInvalidFloat -> NVimInvalidValue -// NVimInvalidIdentifierKey -> NVimInvalidIdentifier -// NVimInvalidList -> NVimInvalidDelimiter -// NVimInvalidSubscript -> NVimInvalidDelimiter -// NVimInvalidSubscriptColon -> NVimInvalidSubscript -// NVimInvalidString -> NVimInvalidValue -// NVimInvalidStringSpecial -> NVimInvalidString -// NVimInvalidSingleQuote -> NVimInvalidString -// NVimInvalidSingleQuotedBody -> NVimInvalidString -// NVimInvalidSingleQuotedQuote -> NVimInvalidStringSpecial -// NVimInvalidDoubleQuote -> NVimInvalidString -// NVimInvalidDoubleQuotedBody -> NVimInvalidString -// NVimInvalidDoubleQuotedEscape -> NVimInvalidStringSpecial -// NVimInvalidDoubleQuotedUnknownEscape -> NVimInvalidDoubleQuotedEscape -// -// NVimFigureBrace -> NVimInternalError -// NVimInvalidSingleQuotedUnknownEscape -> NVimInternalError -// NVimSingleQuotedUnknownEscape -> NVimInternalError /// Allocate a new node and set some of the values /// @@ -2183,12 +2082,12 @@ viml_pexpr_parse_process_token: ADD_OP_NODE(cur_node); if (cur_token.data.cmp.ccs != kCCStrategyUseOption) { viml_parser_highlight(pstate, cur_token.start, cur_token.len - 1, - HL(ComparisonOperator)); + HL(Comparison)); viml_parser_highlight( pstate, shifted_pos(cur_token.start, cur_token.len - 1), 1, - HL(ComparisonOperatorModifier)); + HL(ComparisonModifier)); } else { - HL_CUR_TOKEN(ComparisonOperator); + HL_CUR_TOKEN(Comparison); } want_node = kENodeValue; break; @@ -2390,7 +2289,7 @@ viml_pexpr_parse_valid_colon: break; } case kExprNodeSubscript: { - HL_CUR_TOKEN(Subscript); + HL_CUR_TOKEN(SubscriptBracket); break; } default: { @@ -2418,7 +2317,7 @@ viml_pexpr_parse_bracket_closing_error: } NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeSubscript); ADD_OP_NODE(cur_node); - HL_CUR_TOKEN(Subscript); + HL_CUR_TOKEN(SubscriptBracket); } } break; @@ -2626,7 +2525,7 @@ viml_pexpr_parse_figure_brace_closing_error: cur_token.len - scope_shift, (node_is_key ? HL(IdentifierKey) - : HL(Identifier))); + : HL(IdentifierName))); } else { if (scope == kExprVarScopeMissing) { ADD_IDENT( @@ -2637,7 +2536,7 @@ viml_pexpr_parse_figure_brace_closing_error: cur_node->data.var.ident_len = cur_token.len; want_node = kENodeOperator; } while (0), - Identifier); + IdentifierName); } else { OP_MISSING; } -- cgit From b91cb18c3688a4a936c14484af57de05ca113641 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Oct 2017 21:42:37 +0300 Subject: syntax: Adjust position and arguments of syn_init_cmdline_highlight This way it works both after `nvim -u NORC` and after that and `colorscheme wombat256mod`. Removed the comment because I do not actually know why it works here with these arguments and not in previous position with previous arguments. --- src/nvim/syntax.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index b418df77a4..f8a62423ce 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -6248,11 +6248,8 @@ init_highlight(int both, int reset) (void)source_runtime((char_u *)"syntax/syncolor.vim", DIP_ALL); recursive--; } - // Without syncolor.vim it is going to screw everything over by defining - // cleared highlight groups by creating links to non-existent groups. This - // effectively prevents ":highlight default" from working properly. - syn_init_cmdline_highlight(reset, true); } + syn_init_cmdline_highlight(false, false); } /* -- cgit From 538af1c90a4ac9928f60e97338869e516def4956 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Oct 2017 22:02:19 +0300 Subject: syntax,viml/parser/expressions: Add missing highlight groups Also adjusts some names. --- src/nvim/syntax.c | 39 ++++++++++++++++++++++++++++++++++++-- src/nvim/viml/parser/expressions.c | 12 ++++++------ 2 files changed, 43 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index f8a62423ce..d787790bc3 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -6029,6 +6029,8 @@ static const char *highlight_init_cmdline[] = { "default link NVimUnaryOperator NVimOperator", "default link NVimUnaryPlus NVimUnaryOperator", + "default link NVimUnaryMinus NVimUnaryOperator", + "default link NVimNot NVimUnaryOperator", "default link NVimBinaryOperator NVimOperator", "default link NVimComparison NVimBinaryOperator", @@ -6036,6 +6038,11 @@ static const char *highlight_init_cmdline[] = { "default link NVimBinaryPlus NVimBinaryOperator", "default link NVimConcat NVimBinaryOperator", "default link NVimConcatOrSubscript NVimConcat", + "default link NVimOr NVimBinaryOperator", + "default link NVimAnd NVimBinaryOperator", + "default link NVimMultiplication NVimBinaryOperator", + "default link NVimDivision NVimBinaryOperator", + "default link NVimMod NVimBinaryOperator", "default link NVimTernary NVimOperator", "default link NVimTernaryColon NVimTernary", @@ -6068,7 +6075,15 @@ static const char *highlight_init_cmdline[] = { "default link NVimRegister SpecialChar", "default link NVimNumber Number", "default link NVimFloat NVimNumber", - "default link NVimNumberPrefix SpecialChar", + "default link NVimNumberPrefix Type", + + "default link NVimOptionSigil Type", + "default link NVimOptionName NVimIdentifier", + "default link NVimOptionScope NVimIdentifierScope", + "default link NVimOptionScopeDelimiter NVimIdentifierScopeDelimiter", + + "default link NVimEnvironmentSigil NVimOptionSigil", + "default link NVimEnvironmentName NVimIdentifier", "default link NVimString String", "default link NVimStringBody NVimString", @@ -6089,6 +6104,8 @@ static const char *highlight_init_cmdline[] = { "default link NVimFigureBrace NVimInternalError", "default link NVimSingleQuotedUnknownEscape NVimInternalError", + "default link NVimSpacing Normal", + // NVimInvalid groups: "default link NVimInvalidSingleQuotedUnknownEscape NVimInternalError", @@ -6099,6 +6116,8 @@ static const char *highlight_init_cmdline[] = { "default link NVimInvalidUnaryOperator NVimInvalidOperator", "default link NVimInvalidUnaryPlus NVimInvalidUnaryOperator", + "default link NVimInvalidUnaryMinus NVimInvalidUnaryOperator", + "default link NVimInvalidNot NVimInvalidUnaryOperator", "default link NVimInvalidBinaryOperator NVimInvalidOperator", "default link NVimInvalidComparison NVimInvalidBinaryOperator", @@ -6106,6 +6125,11 @@ static const char *highlight_init_cmdline[] = { "default link NVimInvalidBinaryPlus NVimInvalidBinaryOperator", "default link NVimInvalidConcat NVimInvalidBinaryOperator", "default link NVimInvalidConcatOrSubscript NVimInvalidConcat", + "default link NVimInvalidOr NVimInvalidBinaryOperator", + "default link NVimInvalidAnd NVimInvalidBinaryOperator", + "default link NVimInvalidMultiplication NVimInvalidBinaryOperator", + "default link NVimInvalidDivision NVimInvalidBinaryOperator", + "default link NVimInvalidMod NVimInvalidBinaryOperator", "default link NVimInvalidTernary NVimInvalidOperator", "default link NVimInvalidTernaryColon NVimInvalidTernary", @@ -6144,6 +6168,15 @@ static const char *highlight_init_cmdline[] = { "default link NVimInvalidFloat NVimInvalidNumber", "default link NVimInvalidNumberPrefix NVimInvalidNumber", + "default link NVimInvalidOptionSigil NVimInvalidIdentifier", + "default link NVimInvalidOptionName NVimInvalidIdentifier", + "default link NVimInvalidOptionScope NVimInvalidIdentifierScope", + "default link NVimInvalidOptionScopeDelimiter " + "NVimInvalidIdentifierScopeDelimiter", + + "default link NVimInvalidEnvironmentSigil NVimInvalidOptionSigil", + "default link NVimInvalidEnvironmentName NVimInvalidIdentifier", + // Invalid string bodies and specials are still highlighted as valid ones to // minimize the red area. "default link NVimInvalidString NVimInvalidValue", @@ -6160,7 +6193,9 @@ static const char *highlight_init_cmdline[] = { "default link NVimInvalidDoubleQuotedEscape NVimInvalidStringSpecial", "default link NVimInvalidDoubleQuotedUnknownEscape NVimInvalidValue", - "default link NVimInvalidFigureBrace NVimInternalError", + "default link NVimInvalidFigureBrace NVimInvalidDelimiter", + + "default link NVimInvalidSpacing ErrorMsg", }; /// Create default links for NVim* highlight groups used for cmdline coloring diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 615a59573f..f5bc547d54 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -1472,7 +1472,7 @@ static void parse_quoted_string(ParserState *const pstate, kvec_withinit_t(StringShift, 16) shifts; kvi_init(shifts); if (!is_double) { - viml_parser_highlight(pstate, token.start, 1, HL(SingleQuotedString)); + viml_parser_highlight(pstate, token.start, 1, HL(SingleQuote)); while (p < e) { const char *const chunk_e = memchr(p, '\'', (size_t)(e - p)); if (chunk_e == NULL) { @@ -1509,7 +1509,7 @@ static void parse_quoted_string(ParserState *const pstate, } } } else { - viml_parser_highlight(pstate, token.start, 1, HL(DoubleQuotedString)); + viml_parser_highlight(pstate, token.start, 1, HL(DoubleQuote)); for (p = s + 1; p < e; p++) { if (*p == '\\' && p + 1 < e) { p++; @@ -1741,10 +1741,10 @@ static void parse_quoted_string(ParserState *const pstate, if (token.data.str.closed) { if (is_double) { viml_parser_highlight(pstate, shifted_pos(token.start, token.len - 1), - 1, HL(DoubleQuotedString)); + 1, HL(DoubleQuote)); } else { viml_parser_highlight(pstate, shifted_pos(token.start, token.len - 1), - 1, HL(SingleQuotedString)); + 1, HL(SingleQuote)); } } kvi_destroy(shifts); @@ -2035,7 +2035,7 @@ viml_pexpr_parse_process_token: } viml_parser_highlight( pstate, shifted_pos(cur_token.start, scope_shift + 1), - cur_token.len - (scope_shift + 1), HL(Option)); + cur_token.len - (scope_shift + 1), HL(OptionName)); break; } case kExprLexEnv: { @@ -2053,7 +2053,7 @@ viml_pexpr_parse_process_token: want_node = kENodeOperator; viml_parser_highlight(pstate, cur_token.start, 1, HL(EnvironmentSigil)); viml_parser_highlight(pstate, shifted_pos(cur_token.start, 1), - cur_token.len - 1, HL(Environment)); + cur_token.len - 1, HL(EnvironmentName)); break; } case kExprLexNot: { -- cgit From a9b203d23fcc6ba8c4e298b8e082db990e1ec04f Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 30 Oct 2017 01:32:10 +0300 Subject: *: Fix linter errors Big function in expressions.c may be refactored, if I ever catch the idea how to split it right. --- src/nvim/edit.c | 2 +- src/nvim/keymap.c | 24 ++++++++++++------------ src/nvim/mbyte.c | 26 +++++++++++++++++--------- src/nvim/syntax.c | 25 ++++++++++++------------- src/nvim/viml/parser/expressions.c | 8 ++++---- src/nvim/viml/parser/expressions.h | 2 +- 6 files changed, 47 insertions(+), 40 deletions(-) (limited to 'src') diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 28722a4d10..859f98d2ad 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -6080,7 +6080,7 @@ char_u *add_char2buf(int c, char_u *s) { char_u temp[MB_MAXBYTES + 1]; const int len = utf_char2bytes(c, temp); - for (int i = 0; i < len; ++i) { + for (int i = 0; i < len; i++) { c = temp[i]; // Need to escape K_SPECIAL and CSI like in the typeahead buffer. if (c == K_SPECIAL) { diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index 6ed54464e8..0c8e47b02e 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -25,21 +25,21 @@ */ static const struct modmasktable { - short mod_mask; ///< Bit-mask for particular key modifier. - short mod_flag; ///< Bit(s) for particular key modifier. + uint16_t mod_mask; ///< Bit-mask for particular key modifier. + uint16_t mod_flag; ///< Bit(s) for particular key modifier. char_u name; ///< Single letter name of modifier. } mod_mask_table[] = { - {MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'M'}, - {MOD_MASK_META, MOD_MASK_META, (char_u)'T'}, - {MOD_MASK_CTRL, MOD_MASK_CTRL, (char_u)'C'}, - {MOD_MASK_SHIFT, MOD_MASK_SHIFT, (char_u)'S'}, - {MOD_MASK_MULTI_CLICK, MOD_MASK_2CLICK, (char_u)'2'}, - {MOD_MASK_MULTI_CLICK, MOD_MASK_3CLICK, (char_u)'3'}, - {MOD_MASK_MULTI_CLICK, MOD_MASK_4CLICK, (char_u)'4'}, - {MOD_MASK_CMD, MOD_MASK_CMD, (char_u)'D'}, + { MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'M' }, + { MOD_MASK_META, MOD_MASK_META, (char_u)'T' }, + { MOD_MASK_CTRL, MOD_MASK_CTRL, (char_u)'C' }, + { MOD_MASK_SHIFT, MOD_MASK_SHIFT, (char_u)'S' }, + { MOD_MASK_MULTI_CLICK, MOD_MASK_2CLICK, (char_u)'2' }, + { MOD_MASK_MULTI_CLICK, MOD_MASK_3CLICK, (char_u)'3' }, + { MOD_MASK_MULTI_CLICK, MOD_MASK_4CLICK, (char_u)'4' }, + { MOD_MASK_CMD, MOD_MASK_CMD, (char_u)'D' }, // 'A' must be the last one - {MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'A'}, - {0, 0, NUL} + { MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'A' }, + { 0, 0, NUL } }; /* diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 843007b97b..008bce6df6 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -98,15 +98,23 @@ const uint8_t utf8len_tab[] = { // Like utf8len_tab above, but using a zero for illegal lead bytes. const uint8_t utf8len_tab_zero[] = { - //1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0 - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 2 - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 4 - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 6 - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 8 - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // A - 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // C - 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,0,0, // E + // ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?A ?B ?C ?D ?E ?F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B? + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C? + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // D? + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // E? + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 0, 0, // F? }; /* diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index d787790bc3..e0bf74567d 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -5930,7 +5930,8 @@ static void syntime_report(void) // When making changes here, also change runtime/colors/default.vim! static const char *highlight_init_both[] = { - "Conceal ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey", + "Conceal " + "ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey", "Cursor guibg=fg guifg=bg", "lCursor guibg=fg guifg=bg", "DiffText cterm=bold ctermbg=Red gui=bold guibg=Red", @@ -6211,11 +6212,9 @@ void syn_init_cmdline_highlight(bool reset, bool init) /// /// @param both include groups where 'bg' doesn't matter /// @param reset clear groups first -void -init_highlight(int both, int reset) +void init_highlight(bool both, bool reset) { - int i; - static int had_both = FALSE; + static int had_both = false; // Try finding the color scheme file. Used when a color file was loaded // and 'background' or 't_Co' is changed. @@ -6235,9 +6234,9 @@ init_highlight(int both, int reset) * Didn't use a color file, use the compiled-in colors. */ if (both) { - had_both = TRUE; + had_both = true; const char *const *const pp = highlight_init_both; - for (i = 0; pp[i] != NULL; i++) { + for (size_t i = 0; pp[i] != NULL; i++) { do_highlight(pp[i], reset, true); } } else if (!had_both) { @@ -6250,7 +6249,7 @@ init_highlight(int both, int reset) const char *const *const pp = ((*p_bg == 'l') ? highlight_init_light : highlight_init_dark); - for (i = 0; pp[i] != NULL; i++) { + for (size_t i = 0; pp[i] != NULL; i++) { do_highlight(pp[i], reset, true); } @@ -6266,8 +6265,9 @@ init_highlight(int both, int reset) : "Visual cterm=NONE ctermbg=DarkGrey"), false, true); } else { do_highlight("Visual cterm=reverse ctermbg=NONE", false, true); - if (*p_bg == 'l') + if (*p_bg == 'l') { do_highlight("Search ctermfg=black", false, true); + } } /* @@ -6452,7 +6452,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) restore_cterm_colors(); // Clear all default highlight groups and load the defaults. - for (int idx = 0; idx < highlight_ga.ga_len; ++idx) { + for (int idx = 0; idx < highlight_ga.ga_len; idx++) { highlight_clear(idx); } init_highlight(true, true); @@ -6690,9 +6690,8 @@ void do_highlight(const char *line, const bool forceit, const bool init) } } if (i < 0) { - emsgf(_( - "E421: Color name or number not recognized: %s"), - key_start); + emsgf(_("E421: Color name or number not recognized: %s"), + key_start); error = true; break; } diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index f5bc547d54..fc184f56f5 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -1814,8 +1814,8 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) || ((*kv_Z(ast_stack, 1))->type != kExprNodeConcat && ((*kv_Z(ast_stack, 1))->type != kExprNodeConcatOrSubscript)))) - ? kELFlagAllowFloat - : 0)); + ? kELFlagAllowFloat + : 0)); LexExprToken cur_token = viml_pexpr_next_token( pstate, want_node_to_lexer_flags[want_node] | lexer_additional_flags); if (cur_token.type == kExprLexEOC) { @@ -1876,7 +1876,7 @@ viml_pexpr_parse_process_token: // time. // // Here example will always contain a concat with "a:2" sucking colon, - // making expression invalid both because there is no longer a spare colon + // making expression invalid both because there is no longer a spare colon // for ternary and because concatenating dictionary with anything is not // valid. There are more cases when this will make a difference though. const bool node_is_key = ( @@ -2853,7 +2853,7 @@ viml_pexpr_parse_end: } kvi_destroy(ast_stack); return ast; -} +} // NOLINT(readability/fn_size) #undef NEW_NODE #undef HL diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index d00d4855f3..668c2a4c84 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -189,7 +189,7 @@ typedef enum { kExprNodeCall, ///< Function call. /// Plain identifier: simple variable/function name /// - /// Looks like "string", "g:Foo", etc: consists from a single + /// Looks like "string", "g:Foo", etc: consists from a single /// kExprLexPlainIdentifier token. kExprNodePlainIdentifier, /// Plain dictionary key, for use with kExprNodeConcatOrSubscript -- cgit From 0356dbbb36ffa7934c35e9dbfc6667f9118c244f Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 30 Oct 2017 01:38:02 +0300 Subject: ex_getln: Fix variable name which is wrong after the merge --- src/nvim/ex_getln.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 785038f5d6..f64efe08a1 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -2500,7 +2500,7 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) tl_ret = try_leave(&tstate, &err); can_free_cb = true; } else if (colored_ccline->cmdfirstc == '=') { - color_expr_cmdline(colored_ccline, ret_ccline_colors); + color_expr_cmdline(colored_ccline, ccline_colors); can_free_cb = false; } if (!tl_ret || !dgc_ret) { -- cgit From d98199de9c2969d430ba489187ffa02d8c489dea Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 2 Nov 2017 10:44:20 +0300 Subject: charset: Refactor vim_str2nr --- src/nvim/charset.c | 135 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 88 insertions(+), 47 deletions(-) (limited to 'src') diff --git a/src/nvim/charset.c b/src/nvim/charset.c index c895d65eb7..5aebf90194 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1611,8 +1611,9 @@ bool vim_isblankline(char_u *lbuf) /// If maxlen > 0, check at a maximum maxlen chars. /// /// @param start -/// @param prep Returns type of number 0 = decimal, 'x' or 'X' is hex, -/// '0' = octal, 'b' or 'B' is bin +/// @param prep Returns guessed type of number 0 = decimal, 'x' or 'X' is +/// hexadecimal, '0' = octal, 'b' or 'B' is binary. When using +/// STR2NR_FORCE is always zero. /// @param len Returns the detected length of number. /// @param what Recognizes what number passed. /// @param nptr Returns the signed result. @@ -1627,55 +1628,84 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, #define STRING_ENDED(ptr) \ (!(maxlen == 0 || (int)((ptr) - (const char *)start) < maxlen)) int pre = 0; // default is decimal - bool negative = false; + const bool negative = (ptr[0] == '-'); + uvarnumber_T un = 0; - if (ptr[0] == '-') { - negative = true; + if (negative) { ptr++; } - // Recognize hex, octal and bin. - if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN)) - && !STRING_ENDED(ptr + 1) - && ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9') { + if (what & STR2NR_FORCE) { + // When forcing main consideration is skipping the prefix. Octal and decimal + // numbers have no prefixes to skip. pre is not set. + switch ((unsigned)what & (~(unsigned)STR2NR_FORCE)) { + case STR2NR_HEX: { + if (!STRING_ENDED(ptr + 2) + && ptr[0] == '0' + && (ptr[1] == 'x' || ptr[1] == 'X') + && ascii_isxdigit(ptr[2])) { + ptr += 2; + } + goto vim_str2nr_hex; + } + case STR2NR_BIN: { + if (!STRING_ENDED(ptr + 2) + && ptr[0] == '0' + && (ptr[1] == 'b' || ptr[1] == 'B') + && ascii_isbdigit(ptr[2])) { + ptr += 2; + } + goto vim_str2nr_bin; + } + case STR2NR_OCT: { + goto vim_str2nr_oct; + } + case 0: { + goto vim_str2nr_dec; + } + default: { + assert(false); + } + } + } else if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN)) + && !STRING_ENDED(ptr + 1) + && ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9') { pre = ptr[1]; - + // Detect hexadecimal: 0x or 0X follwed by hex digit if ((what & STR2NR_HEX) && !STRING_ENDED(ptr + 2) && (pre == 'X' || pre == 'x') && ascii_isxdigit(ptr[2])) { - // hexadecimal ptr += 2; - } else if ((what & STR2NR_BIN) - && !STRING_ENDED(ptr + 2) - && (pre == 'B' || pre == 'b') - && ascii_isbdigit(ptr[2])) { - // binary + goto vim_str2nr_hex; + } + // Detect binary: 0b or 0B follwed by 0 or 1 + if ((what & STR2NR_BIN) + && !STRING_ENDED(ptr + 2) + && (pre == 'B' || pre == 'b') + && ascii_isbdigit(ptr[2])) { ptr += 2; - } else { - // decimal or octal, default is decimal - pre = 0; - - if (what & STR2NR_OCT - && !STRING_ENDED(ptr + 1) - && ('0' <= ptr[1] && ptr[1] <= '7')) { - // Assume octal now: what we already know is that string starts with - // zero and some octal digit. - pre = '0'; - // Don’t interpret "0", "008" or "0129" as octal. - for (int i = 2; !STRING_ENDED(ptr + i) && ascii_isdigit(ptr[i]); i++) { - if (ptr[i] > '7') { - // Can’t be octal. - pre = 0; - break; - } - } + goto vim_str2nr_bin; + } + // Detect octal number: zero followed by octal digits without '8' or '9' + pre = 0; + if (!(what & STR2NR_OCT) + || !('0' <= ptr[1] && ptr[1] <= '7')) { + goto vim_str2nr_dec; + } + for (int i = 2; !STRING_ENDED(ptr + i) && ascii_isdigit(ptr[i]); i++) { + if (ptr[i] > '7') { + goto vim_str2nr_dec; } } + pre = '0'; + goto vim_str2nr_oct; + } else { + goto vim_str2nr_dec; } // Do the string-to-numeric conversion "manually" to avoid sscanf quirks. - uvarnumber_T un = 0; + assert(false); // Should’ve used goto earlier. #define PARSE_NUMBER(base, cond, conv) \ do { \ while (!STRING_ENDED(ptr) && (cond)) { \ @@ -1688,18 +1718,29 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, ptr++; \ } \ } while (0) - if (pre == 'B' || pre == 'b' || what == (STR2NR_BIN|STR2NR_FORCE)) { - // Binary number. - PARSE_NUMBER(2, (*ptr == '0' || *ptr == '1'), (*ptr - '0')); - } else if (pre == '0' || what == (STR2NR_OCT|STR2NR_FORCE)) { - // Octal number. - PARSE_NUMBER(8, ('0' <= *ptr && *ptr <= '7'), (*ptr - '0')); - } else if (pre == 'X' || pre == 'x' || what == (STR2NR_HEX|STR2NR_FORCE)) { - // Hexadecimal number. - PARSE_NUMBER(16, (ascii_isxdigit(*ptr)), (hex2nr(*ptr))); - } else { - // Decimal number. - PARSE_NUMBER(10, (ascii_isdigit(*ptr)), (*ptr - '0')); + switch (pre) { + case 'b': + case 'B': { +vim_str2nr_bin: + PARSE_NUMBER(2, (*ptr == '0' || *ptr == '1'), (*ptr - '0')); + break; + } + case '0': { +vim_str2nr_oct: + PARSE_NUMBER(8, ('0' <= *ptr && *ptr <= '7'), (*ptr - '0')); + break; + } + case 0: { +vim_str2nr_dec: + PARSE_NUMBER(10, (ascii_isdigit(*ptr)), (*ptr - '0')); + break; + } + case 'x': + case 'X': { +vim_str2nr_hex: + PARSE_NUMBER(16, (ascii_isxdigit(*ptr)), (hex2nr(*ptr))); + break; + } } #undef PARSE_NUMBER -- cgit From b9d5aea073521f3278bea257be306b7ac2b8d83c Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 3 Nov 2017 11:38:59 +0300 Subject: api/vim: Create part of nvim_parse_expression function --- src/nvim/api/vim.c | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 9daa2fb398..446a049897 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -33,6 +33,8 @@ #include "nvim/syntax.h" #include "nvim/getchar.h" #include "nvim/os/input.h" +#include "nvim/viml/parser/expressions.h" +#include "nvim/viml/parser/parser.h" #define LINE_BUFFER_SIZE 4096 @@ -897,6 +899,12 @@ theend: /// /// Use only "m" to parse like for "=", only "E" to /// parse like for ":echo", empty string for ":let". +/// @param[in] highlight If true, return value will also include "highlight" +/// key containing array of 4-tuples (arrays) (Integer, +/// Integer, Integer, String), where first three numbers +/// define the highlighted region and represent line, +/// starting column and ending column (latter exclusive: +/// one should highlight region [start_col, end_col)). /// /// @return AST: top-level dectionary holds keys /// @@ -946,9 +954,105 @@ theend: /// "fvalue": Float, floating-point value for "Float" nodes. /// "svalue": String, value for "SingleQuotedString" and /// "DoubleQuotedString" nodes. -Dictionary nvim_parse_expression(String expr, String flags, Error *err) +Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, + Error *err) FUNC_API_SINCE(4) { + int pflags = 0; + for (size_t i = 0 ; i < flags.size ; i++) { + switch (flags.data[i]) { + case 'm': { pflags |= kExprFlagsMulti; break; } + case 'E': { pflags |= kExprFlagsDisallowEOC; break; } + default: { + api_set_error(err, kErrorTypeValidation, "Invalid flag: '%c' (%u)", + flags.data[i], (unsigned)flags.data[i]); + return (Dictionary)ARRAY_DICT_INIT; + } + } + } + ParserLine plines[] = { + { + .data = expr.data, + .size = expr.size, + .allocated = false, + }, + { NULL, 0, false }, + }; + ParserLine *plines_p = plines; + ParserHighlight colors; + kvi_init(colors); + ParserHighlight *const colors_p = (highlight ? &colors : NULL); + ParserState pstate; + viml_parser_init( + &pstate, parser_simple_get_line, &plines_p, colors_p); + ExprAST east = viml_pexpr_parse(&pstate, pflags); + + const size_t dict_size = ( + 1 // "ast" + + (size_t)(east.err.arg != NULL) // "error" + + (size_t)highlight // "highlight" + ); + Dictionary ret = { + .items = xmalloc(dict_size * sizeof(ret.items[0])), + .size = 0, + .capacity = dict_size, + }; + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_AS_STRING("ast"), + .value = NIL, + }; + if (east.err.arg != NULL) { + Dictionary err_dict = { + .items = xmalloc(2 * sizeof(err_dict.items[0])), + .size = 2, + .capacity = 2, + }; + err_dict.items[0] = (KeyValuePair) { + .key = STATIC_CSTR_AS_STRING("message"), + .value = STRING_OBJ(cstr_to_string(east.err.arg)), + }; + err_dict.items[1] = (KeyValuePair) { + .key = STATIC_CSTR_AS_STRING("arg"), + .value = STRING_OBJ(((String) { + .data = xmemdup(east.err.arg, (size_t)east.err.arg_len), + .size = (size_t)east.err.arg_len, + })), + }; + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_AS_STRING("error"), + .value = DICTIONARY_OBJ(err_dict), + }; + } + if (highlight) { + Array hl = (Array) { + .items = xmalloc(kv_size(colors) * sizeof(hl.items[0])), + .capacity = kv_size(colors), + .size = kv_size(colors), + }; + for (size_t i = 0 ; i < kv_size(colors) ; i++) { + const ParserHighlightChunk chunk = kv_A(colors, i); + Array chunk_arr = (Array) { + .items = xmalloc(4), + .capacity = 4, + .size = 4, + }; + chunk_arr.items[0] = INTEGER_OBJ((Integer)chunk.start.line); + chunk_arr.items[1] = INTEGER_OBJ((Integer)chunk.start.col); + chunk_arr.items[2] = INTEGER_OBJ((Integer)chunk.end_col); + chunk_arr.items[3] = STRING_OBJ(cstr_to_string(chunk.group)); + hl.items[i] = ARRAY_OBJ(chunk_arr); + } + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_AS_STRING("highlight"), + .value = ARRAY_OBJ(hl), + }; + } + + // FIXME: populate AST + + assert(ret.size == ret.capacity); + viml_pexpr_free_ast(east); + viml_parser_destroy(&pstate); return (Dictionary)ARRAY_DICT_INIT; } -- cgit From 07ec709141886c6db4f944665e07a36ef7302eb4 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 5 Nov 2017 01:33:44 +0300 Subject: vim/api: Actually dump AST, fix some bugs in nvim_parse_expression --- src/nvim/api/vim.c | 264 ++++++++++++++++++++++++++++++++++--- src/nvim/viml/parser/expressions.c | 10 +- src/nvim/viml/parser/expressions.h | 9 ++ 3 files changed, 262 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 446a049897..1aab87010e 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -888,6 +888,13 @@ theend: return rv; } +typedef struct { + ExprASTNode **node_p; + Object *ret_node_p; +} ExprASTConvStackItem; + +typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; + /// Parse a VimL expression /// /// @param[in] expr Expression to parse. Is always treated as a single line. @@ -915,7 +922,8 @@ theend: /// Must contain exactly one "%.*s". /// "arg": String, error message argument. /// -/// "ast": actual AST, a dictionary with the following keys: +/// "ast": actual AST, either nil or a dictionary with the following +/// keys: /// /// "type": node type, one of the value names from ExprASTNodeType /// stringified without "kExprNode" prefix. @@ -927,11 +935,9 @@ theend: /// debugging purposes primary (debugging parser and providing /// debug information). /// "children": a list of nodes described in top/"ast". There always -/// is zero, one or two children, key will contain an -/// empty array if node can have children, but has no and -/// will not be present at all if node can’t have any -/// children. Maximum number of children may be found in -/// node_maxchildren array. +/// is zero, one or two children, key will not be present +/// if node has no children. Maximum number of children +/// may be found in node_maxchildren array. /// /// Local values (present only for certain nodes): /// @@ -950,6 +956,8 @@ theend: /// value names from ExprCaseCompareStrategy, /// stringified without "kCCStrategy" prefix. Only /// present for "Comparison" nodes. +/// "invert": Boolean, true if result of comparison needs to be +/// inverted. Only present for "Comparison" nodes. /// "ivalue": Integer, integer value for "Integer" nodes. /// "fvalue": Float, floating-point value for "Float" nodes. /// "svalue": String, value for "SingleQuotedString" and @@ -998,7 +1006,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, .capacity = dict_size, }; ret.items[ret.size++] = (KeyValuePair) { - .key = STATIC_CSTR_AS_STRING("ast"), + .key = STATIC_CSTR_TO_STRING("ast"), .value = NIL, }; if (east.err.arg != NULL) { @@ -1008,18 +1016,18 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, .capacity = 2, }; err_dict.items[0] = (KeyValuePair) { - .key = STATIC_CSTR_AS_STRING("message"), + .key = STATIC_CSTR_TO_STRING("message"), .value = STRING_OBJ(cstr_to_string(east.err.arg)), }; err_dict.items[1] = (KeyValuePair) { - .key = STATIC_CSTR_AS_STRING("arg"), + .key = STATIC_CSTR_TO_STRING("arg"), .value = STRING_OBJ(((String) { - .data = xmemdup(east.err.arg, (size_t)east.err.arg_len), + .data = xmemdupz(east.err.arg, (size_t)east.err.arg_len), .size = (size_t)east.err.arg_len, })), }; ret.items[ret.size++] = (KeyValuePair) { - .key = STATIC_CSTR_AS_STRING("error"), + .key = STATIC_CSTR_TO_STRING("error"), .value = DICTIONARY_OBJ(err_dict), }; } @@ -1032,7 +1040,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, for (size_t i = 0 ; i < kv_size(colors) ; i++) { const ParserHighlightChunk chunk = kv_A(colors, i); Array chunk_arr = (Array) { - .items = xmalloc(4), + .items = xmalloc(4 * sizeof(chunk_arr.items[0])), .capacity = 4, .size = 4, }; @@ -1043,17 +1051,243 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, hl.items[i] = ARRAY_OBJ(chunk_arr); } ret.items[ret.size++] = (KeyValuePair) { - .key = STATIC_CSTR_AS_STRING("highlight"), + .key = STATIC_CSTR_TO_STRING("highlight"), .value = ARRAY_OBJ(hl), }; } - // FIXME: populate AST + // Walk over the AST, freeing nodes in process. + ExprASTConvStack ast_conv_stack; + kvi_init(ast_conv_stack); + kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { + .node_p = &east.root, + .ret_node_p = &ret.items[0].value, + })); + while (kv_size(ast_conv_stack)) { + ExprASTConvStackItem cur_item = kv_last(ast_conv_stack); + if (*cur_item.node_p == NULL) { + assert(kv_size(ast_conv_stack) == 1); + kv_drop(ast_conv_stack, 1); + } else { + ExprASTNode *const node = *cur_item.node_p; + if (cur_item.ret_node_p->type == kObjectTypeNil) { + const size_t ret_node_items_size = (size_t)( + 3 // "type", "start" and "len" + + (node->children != NULL) // "children" + + (node->type == kExprNodeOption + || node->type == kExprNodePlainIdentifier) // "scope" + + (node->type == kExprNodeOption + || node->type == kExprNodePlainIdentifier + || node->type == kExprNodePlainKey + || node->type == kExprNodeEnvironment) // "ident" + + (node->type == kExprNodeRegister) // "name" + + (3 // "cmp_type", "ccs_strategy", "invert" + * (node->type == kExprNodeComparison)) + + (node->type == kExprNodeInteger) // "ivalue" + + (node->type == kExprNodeFloat) // "fvalue" + + (node->type == kExprNodeDoubleQuotedString + || node->type == kExprNodeSingleQuotedString) // "svalue" + + 0); + Dictionary ret_node = { + .items = xmalloc(ret_node_items_size * sizeof(ret_node.items[0])), + .capacity = ret_node_items_size, + .size = 0, + }; + *cur_item.ret_node_p = DICTIONARY_OBJ(ret_node); + } + Dictionary *ret_node = &cur_item.ret_node_p->data.dictionary; + if (node->children != NULL) { + const size_t num_children = 1 + (node->children->next != NULL); + Array children_array = { + .items = xmalloc(num_children * sizeof(children_array.items[0])), + .capacity = num_children, + .size = num_children, + }; + for (size_t i = 0; i < num_children; i++) { + children_array.items[i] = NIL; + } + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("children"), + .value = ARRAY_OBJ(children_array), + }; + kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { + .node_p = &node->children, + .ret_node_p = &children_array.items[0], + })); + } else if (node->next != NULL) { + kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { + .node_p = &node->children, + .ret_node_p = cur_item.ret_node_p + 1, + })); + } else if (node != NULL) { + kv_drop(ast_conv_stack, 1); + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("type"), + .value = STRING_OBJ(cstr_to_string(east_node_type_tab[node->type])), + }; + Array start_array = { + .items = xmalloc(2 * sizeof(start_array.items[0])), + .capacity = 2, + .size = 2, + }; + start_array.items[0] = INTEGER_OBJ((Integer)node->start.line); + start_array.items[1] = INTEGER_OBJ((Integer)node->start.col); + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("start"), + .value = ARRAY_OBJ(start_array), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("len"), + .value = INTEGER_OBJ((Integer)node->len), + }; + switch (node->type) { + case kExprNodeDoubleQuotedString: + case kExprNodeSingleQuotedString: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("svalue"), + .value = STRING_OBJ(cstr_as_string(node->data.str.value)), + }; + break; + } + case kExprNodeOption: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("scope"), + .value = INTEGER_OBJ(node->data.opt.scope), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.opt.ident, + node->data.opt.ident_len), + .size = node->data.opt.ident_len, + })), + }; + break; + } + case kExprNodePlainIdentifier: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("scope"), + .value = INTEGER_OBJ(node->data.var.scope), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.var.ident, + node->data.var.ident_len), + .size = node->data.var.ident_len, + })), + }; + break; + } + case kExprNodePlainKey: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.var.ident, + node->data.var.ident_len), + .size = node->data.var.ident_len, + })), + }; + break; + } + case kExprNodeEnvironment: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.env.ident, + node->data.env.ident_len), + .size = node->data.env.ident_len, + })), + }; + break; + } + case kExprNodeRegister: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("name"), + .value = INTEGER_OBJ(node->data.reg.name), + }; + break; + } + case kExprNodeComparison: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("cmp_type"), + .value = STRING_OBJ(cstr_to_string( + eltkn_cmp_type_tab[node->data.cmp.type])), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ccs_strategy"), + .value = STRING_OBJ(cstr_to_string( + eltkn_cmp_type_tab[node->data.cmp.ccs])), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("invert"), + .value = BOOLEAN_OBJ(node->data.cmp.inv), + }; + break; + } + case kExprNodeFloat: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("fvalue"), + .value = FLOAT_OBJ(node->data.flt.value), + }; + break; + } + case kExprNodeInteger: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ivalue"), + .value = INTEGER_OBJ((Integer)( + node->data.num.value > API_INTEGER_MAX + ? API_INTEGER_MAX + : (Integer)node->data.num.value)), + }; + break; + } + case kExprNodeMissing: + case kExprNodeOpMissing: + case kExprNodeTernary: + case kExprNodeTernaryValue: + case kExprNodeSubscript: + case kExprNodeListLiteral: + case kExprNodeUnaryPlus: + case kExprNodeBinaryPlus: + case kExprNodeNested: + case kExprNodeCall: + case kExprNodeComplexIdentifier: + case kExprNodeUnknownFigure: + case kExprNodeLambda: + case kExprNodeDictLiteral: + case kExprNodeCurlyBracesIdentifier: + case kExprNodeComma: + case kExprNodeColon: + case kExprNodeArrow: + case kExprNodeConcat: + case kExprNodeConcatOrSubscript: + case kExprNodeOr: + case kExprNodeAnd: + case kExprNodeUnaryMinus: + case kExprNodeBinaryMinus: + case kExprNodeNot: + case kExprNodeMultiplication: + case kExprNodeDivision: + case kExprNodeMod: { + break; + } + } + assert(cur_item.ret_node_p->data.dictionary.size + == cur_item.ret_node_p->data.dictionary.capacity); + xfree(*cur_item.node_p); + *cur_item.node_p = NULL; + } + } + } + kvi_destroy(ast_conv_stack); assert(ret.size == ret.capacity); + // Should be a no-op actually, leaving it in case non-nodes will need to be + // freed later. viml_pexpr_free_ast(east); viml_parser_destroy(&pstate); - return (Dictionary)ARRAY_DICT_INIT; + return ret; } diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index fc184f56f5..b19aab22af 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -616,7 +616,7 @@ static const char *const eltkn_type_tab[] = { [kExprLexArrow] = "Arrow", }; -static const char *const eltkn_cmp_type_tab[] = { +const char *const eltkn_cmp_type_tab[] = { [kExprCmpEqual] = "Equal", [kExprCmpMatches] = "Matches", [kExprCmpGreater] = "Greater", @@ -624,7 +624,7 @@ static const char *const eltkn_cmp_type_tab[] = { [kExprCmpIdentical] = "Identical", }; -static const char *const ccs_tab[] = { +const char *const ccs_tab[] = { [kCCStrategyUseOption] = "UseOption", [kCCStrategyMatchCase] = "MatchCase", [kCCStrategyIgnoreCase] = "IgnoreCase", @@ -725,8 +725,7 @@ viml_pexpr_repr_token_end: return ret; } -#ifdef UNIT_TESTING -static const char *const east_node_type_tab[] = { +const char *const east_node_type_tab[] = { [kExprNodeMissing] = "Missing", [kExprNodeOpMissing] = "OpMissing", [kExprNodeTernary] = "Ternary", @@ -766,7 +765,6 @@ static const char *const east_node_type_tab[] = { [kExprNodeOption] = "Option", [kExprNodeEnvironment] = "Environment", }; -#endif /// Represent `int` character as a string /// @@ -2148,10 +2146,10 @@ viml_pexpr_parse_invalid_comma: } #define EXP_VAL_COLON "E15: Expected value, got colon: %.*s" case kExprLexColon: { + bool is_ternary = false; if (kv_size(ast_stack) < 2) { goto viml_pexpr_parse_invalid_colon; } - bool is_ternary = false; bool can_be_ternary = true; bool is_subscript = false; for (size_t i = 1; i < kv_size(ast_stack); i++) { diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 668c2a4c84..648f8cbc1f 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -341,6 +341,15 @@ typedef struct { /// Array mapping ExprASTNodeType to maximum amount of children node may have extern const uint8_t node_maxchildren[]; +/// Array mapping ExprASTNodeType values to their stringified versions +extern const char *const east_node_type_tab[]; + +/// Array mapping ExprComparisonType values to their stringified versions +extern const char *const eltkn_cmp_type_tab[]; + +/// Array mapping ExprCaseCompareStrategy values to their stringified versions +extern const char *const ccs_tab[]; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "viml/parser/expressions.h.generated.h" #endif -- cgit From 7bc6de75263f58c6c4f999bc86a6454ae9f28b80 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 5 Nov 2017 02:41:44 +0300 Subject: api/vim,functests: Add tests for nvim_parse_expression, fix found bugs --- src/nvim/api/vim.c | 47 +++++++++++++++++++++++--------------- src/nvim/viml/parser/expressions.c | 2 +- 2 files changed, 30 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 1aab87010e..5f725acaf7 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -995,21 +995,21 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, &pstate, parser_simple_get_line, &plines_p, colors_p); ExprAST east = viml_pexpr_parse(&pstate, pflags); - const size_t dict_size = ( + const size_t ret_size = ( 1 // "ast" - + (size_t)(east.err.arg != NULL) // "error" + + (size_t)(east.err.msg != NULL) // "error" + (size_t)highlight // "highlight" ); Dictionary ret = { - .items = xmalloc(dict_size * sizeof(ret.items[0])), + .items = xmalloc(ret_size * sizeof(ret.items[0])), .size = 0, - .capacity = dict_size, + .capacity = ret_size, }; ret.items[ret.size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("ast"), .value = NIL, }; - if (east.err.arg != NULL) { + if (east.err.msg != NULL) { Dictionary err_dict = { .items = xmalloc(2 * sizeof(err_dict.items[0])), .size = 2, @@ -1017,15 +1017,22 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, }; err_dict.items[0] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("message"), - .value = STRING_OBJ(cstr_to_string(east.err.arg)), - }; - err_dict.items[1] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("arg"), - .value = STRING_OBJ(((String) { - .data = xmemdupz(east.err.arg, (size_t)east.err.arg_len), - .size = (size_t)east.err.arg_len, - })), + .value = STRING_OBJ(cstr_to_string(east.err.msg)), }; + if (east.err.arg == NULL) { + err_dict.items[1] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("arg"), + .value = STRING_OBJ(STRING_INIT), + }; + } else { + err_dict.items[1] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("arg"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(east.err.arg, (size_t)east.err.arg_len), + .size = (size_t)east.err.arg_len, + })), + }; + } ret.items[ret.size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("error"), .value = DICTIONARY_OBJ(err_dict), @@ -1055,6 +1062,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, .value = ARRAY_OBJ(hl), }; } + kvi_destroy(colors); // Walk over the AST, freeing nodes in process. ExprASTConvStack ast_conv_stack; @@ -1065,11 +1073,11 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, })); while (kv_size(ast_conv_stack)) { ExprASTConvStackItem cur_item = kv_last(ast_conv_stack); - if (*cur_item.node_p == NULL) { + ExprASTNode *const node = *cur_item.node_p; + if (node == NULL) { assert(kv_size(ast_conv_stack) == 1); kv_drop(ast_conv_stack, 1); } else { - ExprASTNode *const node = *cur_item.node_p; if (cur_item.ret_node_p->type == kObjectTypeNil) { const size_t ret_node_items_size = (size_t)( 3 // "type", "start" and "len" @@ -1116,7 +1124,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, })); } else if (node->next != NULL) { kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { - .node_p = &node->children, + .node_p = &node->next, .ret_node_p = cur_item.ret_node_p + 1, })); } else if (node != NULL) { @@ -1145,7 +1153,10 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, case kExprNodeSingleQuotedString: { ret_node->items[ret_node->size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("svalue"), - .value = STRING_OBJ(cstr_as_string(node->data.str.value)), + .value = STRING_OBJ(((String) { + .data = node->data.str.value, + .size = node->data.str.size, + })), }; break; } @@ -1217,7 +1228,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, ret_node->items[ret_node->size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("ccs_strategy"), .value = STRING_OBJ(cstr_to_string( - eltkn_cmp_type_tab[node->data.cmp.ccs])), + ccs_tab[node->data.cmp.ccs])), }; ret_node->items[ret_node->size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("invert"), diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index b19aab22af..b10952a8ac 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -1492,7 +1492,7 @@ static void parse_quoted_string(ParserState *const pstate, node->data.str.value = NULL; } else { char *v_p; - v_p = node->data.str.value = xmalloc(size); + v_p = node->data.str.value = xmallocz(size); p = s + 1; while (p < e) { const char *const chunk_e = memchr(p, '\'', (size_t)(e - p)); -- cgit From 7849070f998902bb6aae5d6f9147d4daf5421b36 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 5 Nov 2017 21:06:12 +0300 Subject: tests: Add missing test cases --- src/nvim/api/vim.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 5f725acaf7..b12c595cb5 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -971,6 +971,11 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, switch (flags.data[i]) { case 'm': { pflags |= kExprFlagsMulti; break; } case 'E': { pflags |= kExprFlagsDisallowEOC; break; } + case NUL: { + api_set_error(err, kErrorTypeValidation, "Invalid flag: '\\0' (%u)", + (unsigned)flags.data[i]); + return (Dictionary)ARRAY_DICT_INIT; + } default: { api_set_error(err, kErrorTypeValidation, "Invalid flag: '%c' (%u)", flags.data[i], (unsigned)flags.data[i]); @@ -995,6 +1000,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, &pstate, parser_simple_get_line, &plines_p, colors_p); ExprAST east = viml_pexpr_parse(&pstate, pflags); + // FIXME add parse_length key const size_t ret_size = ( 1 // "ast" + (size_t)(east.err.msg != NULL) // "error" -- cgit From ebb59778371784ba28d37fc615ae02a5338f365d Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 6 Nov 2017 01:15:18 +0300 Subject: api/vim: Add “len” dictionary key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows determining where parsing ended which may be needed for e.g. parsing `:echo` with that API function. --- src/nvim/api/vim.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index b12c595cb5..8de37e2cf3 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -922,6 +922,12 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; /// Must contain exactly one "%.*s". /// "arg": String, error message argument. /// +/// "len": Amount of bytes successfully parsed. With flags equal to "" +/// that should be equal to the length of expr string. +/// +/// @note: “Sucessfully parsed” here means “participated in AST +/// creation”, not “till the first error”. +/// /// "ast": actual AST, either nil or a dictionary with the following /// keys: /// @@ -1000,9 +1006,8 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, &pstate, parser_simple_get_line, &plines_p, colors_p); ExprAST east = viml_pexpr_parse(&pstate, pflags); - // FIXME add parse_length key const size_t ret_size = ( - 1 // "ast" + 2 // "ast", "len" + (size_t)(east.err.msg != NULL) // "error" + (size_t)highlight // "highlight" ); @@ -1015,6 +1020,12 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, .key = STATIC_CSTR_TO_STRING("ast"), .value = NIL, }; + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("len"), + .value = INTEGER_OBJ((Integer)(pstate.pos.line == 1 + ? plines[0].size + : pstate.pos.col)), + }; if (east.err.msg != NULL) { Dictionary err_dict = { .items = xmalloc(2 * sizeof(err_dict.items[0])), -- cgit From 05f775b5f248d922c9539432235738cc53e7edd7 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 6 Nov 2017 01:57:22 +0300 Subject: viml/parser/expressions: Briefly document some differences --- src/nvim/viml/parser/expressions.c | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index b10952a8ac..998edb1ed4 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -3,6 +3,44 @@ /// VimL expression parser +// Planned incompatibilities (to be included into vim_diff.txt when this parser +// will be an actual part of VimL evaluation process): +// +// 1. Expressions are first fully parsed and only then executed. This means +// that while ":echo [system('touch abc')" will create file "abc" in Vim and +// only then raise syntax error regarding missing comma in list in Neovim +// trying to execute that will immediately raise syntax error regarding +// missing list end without actually executing anything. +// 2. Expressions are first fully parsed, without considering any runtime +// information. This means things like that "d.a" does not change its +// meaning depending on type of "d" (or whether Vim is currently executing or +// skipping). For compatibility reasons the dot thus may either be “concat +// or subscript” operator or just “concat” operator. +// 3. Expressions parser is aware whether it is called for :echo or =. +// This means that while "=1 | 2" is equivalent to "=1" +// because "| 2" part is left to be treated as a command separator and then +// ignored in Neovim it is an error. +// 4. Expressions parser has generally better error reporting. But for +// compatibility reasons most errors have error code E15 while error messages +// are significantly different from Vim’s E15. Also some error codes were +// retired because of being harder to emulate or because of them being +// a result of differences in parsing process: e.g. with ":echo {a, b}" Vim +// will attempt to parse expression as lambda, fail, check whether it is +// a curly-braces-name, fail again, and evaluate that as a dictionary, giving +// error regarding undefined variable "a" (or about missing colon). Neovim +// will not try to evaluate anything here: comma right after an argument name +// means that expression may not be anything, but lambda, so the resulting +// error message will never be about missing variable or colon: it will be +// about missing arrow (or a continuation of argument list). +// 5. Failing to parse expression always gives exactly one error message: no +// more stack of error messages like > +// +// :echo [1, +// E697: Missing end of List ']': +// E15: Invalid expression: [1, +// +// < , just exactly one E697 message. + #include #include #include -- cgit From c85f485aa78352f31421d209176b2ed57b91b86e Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 6 Nov 2017 19:06:24 +0300 Subject: charset: Move vim_str2nr flags from vim.h to charset.h --- src/nvim/charset.c | 2 +- src/nvim/charset.h | 15 +++++++++++++++ src/nvim/vim.h | 7 ------- 3 files changed, 16 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 5aebf90194..980b4ed426 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1615,7 +1615,7 @@ bool vim_isblankline(char_u *lbuf) /// hexadecimal, '0' = octal, 'b' or 'B' is binary. When using /// STR2NR_FORCE is always zero. /// @param len Returns the detected length of number. -/// @param what Recognizes what number passed. +/// @param what Recognizes what number passed, @see ChStr2NrFlags. /// @param nptr Returns the signed result. /// @param unptr Returns the unsigned result. /// @param maxlen Max length of string to check. diff --git a/src/nvim/charset.h b/src/nvim/charset.h index c69582c4c6..7198c8d405 100644 --- a/src/nvim/charset.h +++ b/src/nvim/charset.h @@ -15,6 +15,21 @@ ?((int)(uint8_t)(c)) \ :((int)(c))) +/// Flags for vim_str2nr() +typedef enum { + STR2NR_DEC = 0, + STR2NR_BIN = (1 << 0), ///< Allow binary numbers. + STR2NR_OCT = (1 << 1), ///< Allow octal numbers. + STR2NR_HEX = (1 << 2), ///< Allow hexadecimal numbers. + /// Force one of the above variants. + /// + /// STR2NR_FORCE|STR2NR_DEC is actually not different from supplying zero + /// as flags, but still present for completeness. + STR2NR_FORCE = (1 << 3), + /// Recognize all formats vim_str2nr() can recognize. + STR2NR_ALL = STR2NR_BIN | STR2NR_OCT | STR2NR_HEX, +} ChStr2NrFlags; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "charset.h.generated.h" #endif diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 62ffc7433e..096c57450f 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -28,13 +28,6 @@ /// length of a buffer to store a number in ASCII (64 bits binary + NUL) enum { NUMBUFLEN = 65 }; -// flags for vim_str2nr() -#define STR2NR_BIN 1 -#define STR2NR_OCT 2 -#define STR2NR_HEX 4 -#define STR2NR_ALL (STR2NR_BIN + STR2NR_OCT + STR2NR_HEX) -#define STR2NR_FORCE 8 // only when ONE of the above is used - #define MAX_TYPENR 65535 #define ROOT_UID 0 -- cgit From f2660bee6aca35be3d0ddb1d225784476c13cd27 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 6 Nov 2017 20:20:31 +0300 Subject: *: Fix some typos found by oni-link --- src/nvim/ex_getln.c | 1 - src/nvim/generators/gen_declarations.lua | 6 +++--- src/nvim/keymap.c | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index f64efe08a1..3b751530e8 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -2501,7 +2501,6 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) can_free_cb = true; } else if (colored_ccline->cmdfirstc == '=') { color_expr_cmdline(colored_ccline, ccline_colors); - can_free_cb = false; } if (!tl_ret || !dgc_ret) { goto color_cmdline_error; diff --git a/src/nvim/generators/gen_declarations.lua b/src/nvim/generators/gen_declarations.lua index 0c73376ba0..065c043557 100755 --- a/src/nvim/generators/gen_declarations.lua +++ b/src/nvim/generators/gen_declarations.lua @@ -169,10 +169,10 @@ Usage: gendeclarations.lua definitions.c static.h non-static.h definitions.i -Generates declarations for a C file defitions.c, putting declarations for +Generates declarations for a C file definitions.c, putting declarations for static functions into static.h and declarations for non-static functions into non-static.h. File `definitions.i' should contain an already preprocessed -version of defintions.c and it is the only one which is actually parsed, +version of definitions.c and it is the only one which is actually parsed, definitions.c is needed only to determine functions from which file out of all functions found in definitions.i are needed. @@ -181,7 +181,7 @@ Additionally uses the following environment variables: NVIM_GEN_DECLARATIONS_LINE_NUMBERS: If set to 1 then all generated declarations receive a comment with file name and line number after the declaration. This may be useful for - debugging gen_declarations script, but not much beyound that with + debugging gen_declarations script, but not much beyond that with configured development environment (i.e. with ctags/cscope/finding definitions with clang/etc). diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index 0c8e47b02e..aca21c20a5 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -714,7 +714,7 @@ int find_special_key_in_table(int c) /// with "t_" the next two characters are interpreted as /// a termcap name. /// -/// @return Key code or 0 if ton found. +/// @return Key code or 0 if not found. int get_special_key_code(const char_u *name) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { -- cgit From 4aebd00a9eeeb2f56ff53dd4e383825e997ee7be Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 6 Nov 2017 20:28:37 +0300 Subject: *: Fix linter errors --- src/nvim/api/vim.c | 20 ++++++++++---------- src/nvim/viml/parser/expressions.c | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 0596c3ebd8..bac19ef363 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -930,7 +930,7 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; /// @note: “Sucessfully parsed” here means “participated in AST /// creation”, not “till the first error”. /// -/// "ast": actual AST, either nil or a dictionary with the following +/// "ast": actual AST, either nil or a dictionary with the following /// keys: /// /// "type": node type, one of the value names from ExprASTNodeType @@ -1009,10 +1009,10 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, ExprAST east = viml_pexpr_parse(&pstate, pflags); const size_t ret_size = ( - 2 // "ast", "len" - + (size_t)(east.err.msg != NULL) // "error" - + (size_t)highlight // "highlight" - ); + 2 // "ast", "len" + + (size_t)(east.err.msg != NULL) // "error" + + (size_t)highlight // "highlight" + + 0); Dictionary ret = { .items = xmalloc(ret_size * sizeof(ret.items[0])), .size = 0, @@ -1242,12 +1242,12 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, ret_node->items[ret_node->size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("cmp_type"), .value = STRING_OBJ(cstr_to_string( - eltkn_cmp_type_tab[node->data.cmp.type])), + eltkn_cmp_type_tab[node->data.cmp.type])), }; ret_node->items[ret_node->size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("ccs_strategy"), .value = STRING_OBJ(cstr_to_string( - ccs_tab[node->data.cmp.ccs])), + ccs_tab[node->data.cmp.ccs])), }; ret_node->items[ret_node->size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("invert"), @@ -1266,9 +1266,9 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, ret_node->items[ret_node->size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("ivalue"), .value = INTEGER_OBJ((Integer)( - node->data.num.value > API_INTEGER_MAX - ? API_INTEGER_MAX - : (Integer)node->data.num.value)), + node->data.num.value > API_INTEGER_MAX + ? API_INTEGER_MAX + : (Integer)node->data.num.value)), }; break; } diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 998edb1ed4..71eda2cdc1 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -22,7 +22,7 @@ // ignored in Neovim it is an error. // 4. Expressions parser has generally better error reporting. But for // compatibility reasons most errors have error code E15 while error messages -// are significantly different from Vim’s E15. Also some error codes were +// are significantly different from Vim’s E15. Also some error codes were // retired because of being harder to emulate or because of them being // a result of differences in parsing process: e.g. with ":echo {a, b}" Vim // will attempt to parse expression as lambda, fail, check whether it is -- cgit From 1aa6276c29d562a6287519e6755a613eabca5c31 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 12 Nov 2017 00:03:45 +0300 Subject: viml/parser/expressions: Replace lambda-specific WantedNode entries This way code will be easier to adapt to handling (partially) non-expressions like :let lvalue part or :function definitions, and that would be needed in the future both for proper completion support and for the Ex commands parser. --- src/nvim/viml/parser/expressions.c | 101 +++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 49 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 71eda2cdc1..e23c58bfd1 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -73,12 +73,21 @@ typedef enum { /// For unrestricted expressions as well, implies that top item in AST stack /// points to NULL. kENodeValue, - /// Argument: only allows simple argument names. - kENodeArgument, - /// Argument separator: only allows commas. - kENodeArgumentSeparator, } ExprASTWantedNode; +/// Parse type: what is being parsed currently +typedef enum { + /// Parsing regular VimL expression + kEPTExpr = 0, + /// Parsing lambda arguments + /// + /// Just like parsing function arguments, but it is valid to be ended with an + /// arrow only. + kEPTLambdaArguments, +} ExprASTParseType; + +typedef kvec_withinit_t(ExprASTParseType, 4) ExprASTParseTypeStack; + /// Operator priority level typedef enum { kEOpLvlInvalid = 0, @@ -1238,9 +1247,7 @@ static bool viml_pexpr_handle_bop(const ParserState *const pstate, ret = false; } } - *want_node_p = (*want_node_p == kENodeArgumentSeparator - ? kENodeArgument - : kENodeValue); + *want_node_p = kENodeValue; return ret; } @@ -1790,8 +1797,6 @@ static void parse_quoted_string(ParserState *const pstate, static const int want_node_to_lexer_flags[] = { [kENodeValue] = kELFlagIsNotCmp, [kENodeOperator] = kELFlagForbidScope, - [kENodeArgument] = kELFlagIsNotCmp, - [kENodeArgumentSeparator] = kELFlagForbidScope, }; /// Number of characters to highlight as NumberPrefix depending on the base @@ -1827,12 +1832,13 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) // (list start) last stack item contains NULL. Otherwise last stack item is // supposed to contain last “finished” value: e.g. "1" or "+(1, 1)" (node // representing "1+1"). - // - // Both kENodeValue and kENodeArgument stand for “value” nodes. ExprASTStack ast_stack; kvi_init(ast_stack); kvi_push(ast_stack, &ast.root); ExprASTWantedNode want_node = kENodeValue; + ExprASTParseTypeStack pt_stack; + kvi_init(pt_stack); + kvi_push(pt_stack, kEPTExpr); LexExprToken prev_token = { .type = kExprLexMissing }; bool highlighted_prev_spacing = false; // Lambda node, valid when parsing lambda arguments only. @@ -1884,8 +1890,7 @@ viml_pexpr_parse_process_token: assert(kv_size(ast_stack) >= 1); ExprASTNode *cur_node = NULL; #ifndef NDEBUG - const bool want_value = ( - want_node == kENodeValue || want_node == kENodeArgument); + const bool want_value = (want_node == kENodeValue); assert(want_value == (*top_node_p == NULL)); assert(kv_A(ast_stack, 0) == &ast.root); // Check that stack item i + 1 points to stack items’ i *last* child. @@ -1933,14 +1938,15 @@ viml_pexpr_parse_process_token: // circumstances, and in any case runtime and not parse time errors. (*kv_Z(ast_stack, 1))->type = kExprNodeConcat; } - if ((want_node == kENodeArgumentSeparator - && tok_type != kExprLexComma - && tok_type != kExprLexArrow) - || (want_node == kENodeArgument - && !(cur_token.type == kExprLexPlainIdentifier - && cur_token.data.var.scope == kExprVarScopeMissing - && !cur_token.data.var.autoload) - && tok_type != kExprLexArrow)) { + if (kv_last(pt_stack) == kEPTLambdaArguments + && ((want_node == kENodeOperator + && tok_type != kExprLexComma + && tok_type != kExprLexArrow) + || (want_node == kENodeValue + && !(cur_token.type == kExprLexPlainIdentifier + && cur_token.data.var.scope == kExprVarScopeMissing + && !cur_token.data.var.autoload) + && tok_type != kExprLexArrow))) { lambda_node->data.fig.type_guesses.allow_lambda = false; if (lambda_node->children != NULL && lambda_node->children->type == kExprNodeComma) { @@ -1956,16 +1962,11 @@ viml_pexpr_parse_process_token: // Else it may appear that possibly-lambda node is actually a dictionary // or curly-braces-name identifier. lambda_node = NULL; - if (want_node == kENodeArgumentSeparator) { - want_node = kENodeOperator; - } else { - want_node = kENodeValue; - } + kv_drop(pt_stack, 1); } } - assert(lambda_node == NULL - || want_node == kENodeArgumentSeparator - || want_node == kENodeArgument); + const ExprASTParseType cur_pt = kv_last(pt_stack); + assert(lambda_node == NULL || cur_pt == kEPTLambdaArguments); switch (tok_type) { case kExprLexMissing: case kExprLexSpacing: @@ -2129,7 +2130,7 @@ viml_pexpr_parse_process_token: break; } case kExprLexComma: { - assert(want_node != kENodeArgument); + assert(!(want_node == kENodeValue && cur_pt == kEPTLambdaArguments)); if (want_node == kENodeValue) { // Value level: comma appearing here is not valid. // Note: in Vim string(,x) will give E116, this is not the case here. @@ -2138,13 +2139,11 @@ viml_pexpr_parse_process_token: NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeMissing); cur_node->len = 0; *top_node_p = cur_node; - want_node = (want_node == kENodeArgument - ? kENodeArgumentSeparator - : kENodeOperator); + want_node = kENodeOperator; } - if (want_node == kENodeArgumentSeparator) { - assert(lambda_node->data.fig.type_guesses.allow_lambda); + if (cur_pt == kEPTLambdaArguments) { assert(lambda_node != NULL); + assert(lambda_node->data.fig.type_guesses.allow_lambda); SELECT_FIGURE_BRACE_TYPE(lambda_node, Lambda, Lambda); } if (kv_size(ast_stack) < 2) { @@ -2156,7 +2155,8 @@ viml_pexpr_parse_process_token: const ExprASTNodeType eastnode_type = (*eastnode_p)->type; const ExprOpLvl eastnode_lvl = node_lvl(**eastnode_p); if (eastnode_type == kExprNodeLambda) { - assert(want_node == kENodeArgumentSeparator); + assert(cur_pt == kEPTLambdaArguments + && want_node == kENodeOperator); break; } else if (eastnode_type == kExprNodeDictLiteral || eastnode_type == kExprNodeListLiteral @@ -2410,9 +2410,6 @@ viml_pexpr_parse_bracket_closing_error: case kExprNodeUnknownFigure: { if (new_top_node->children == NULL) { // No children of curly braces node indicates empty dictionary. - - // Should actually be kENodeArgument, but that was changed - // earlier. assert(want_node == kENodeValue); assert(new_top_node->data.fig.type_guesses.allow_dict); SELECT_FIGURE_BRACE_TYPE(new_top_node, DictLiteral, Dict); @@ -2475,7 +2472,7 @@ viml_pexpr_parse_figure_brace_closing_error: } *top_node_p = cur_node; kvi_push(ast_stack, &cur_node->children); - want_node = kENodeArgument; + kvi_push(pt_stack, kEPTLambdaArguments); lambda_node = cur_node; } else { ADD_IDENT( @@ -2495,9 +2492,12 @@ viml_pexpr_parse_figure_brace_closing_error: break; } case kExprLexArrow: { - if (want_node == kENodeArgumentSeparator - || want_node == kENodeArgument) { - if (want_node == kENodeArgument) { + if (cur_pt == kEPTLambdaArguments) { + kv_drop(pt_stack, 1); + assert(kv_size(pt_stack)); + if (want_node == kENodeValue) { + // Wanting value means trailing comma and NULL at the top of the + // stack. kv_drop(ast_stack, 1); } assert(kv_size(ast_stack) >= 1); @@ -2509,7 +2509,7 @@ viml_pexpr_parse_figure_brace_closing_error: SELECT_FIGURE_BRACE_TYPE(lambda_node, Lambda, Lambda); NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeArrow); if (lambda_node->children == NULL) { - assert(want_node == kENodeArgument); + assert(want_node == kENodeValue); lambda_node->children = cur_node; kvi_push(ast_stack, &lambda_node->children); } else { @@ -2535,10 +2535,8 @@ viml_pexpr_parse_figure_brace_closing_error: const ExprVarScope scope = (cur_token.type == kExprLexInvalid ? kExprVarScopeMissing : cur_token.data.var.scope); - if (want_node == kENodeValue || want_node == kENodeArgument) { - want_node = (want_node == kENodeArgument - ? kENodeArgumentSeparator - : kENodeOperator); + if (want_node == kENodeValue) { + want_node = kENodeOperator; NEW_NODE_WITH_CUR_POS(cur_node, (node_is_key ? kExprNodePlainKey @@ -2755,7 +2753,12 @@ viml_pexpr_parse_cycle_end: viml_parser_advance(pstate, cur_token.len); } while (true); viml_pexpr_parse_end: - if (want_node == kENodeValue) { + assert(kv_size(pt_stack)); + assert(kv_size(ast_stack)); + if (want_node == kENodeValue + // Blacklist some parse type entries as their presence means better error + // message in the other branch. + && kv_last(pt_stack) != kEPTLambdaArguments) { east_set_error(pstate, &ast.err, _("E15: Expected value, got EOC: %.*s"), pstate->pos); } else if (kv_size(ast_stack) != 1) { -- cgit From c7495ebcc0918ffd682083408895451318e41d1f Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 12 Nov 2017 02:18:43 +0300 Subject: viml/parser/expressions: Add support for parsing assignments --- src/nvim/api/vim.c | 39 ++++-- src/nvim/syntax.c | 20 ++++ src/nvim/viml/parser/expressions.c | 239 +++++++++++++++++++++++++++++++------ src/nvim/viml/parser/expressions.h | 40 ++++++- 4 files changed, 292 insertions(+), 46 deletions(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index bac19ef363..ce554d351c 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -900,14 +900,21 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; /// Parse a VimL expression /// /// @param[in] expr Expression to parse. Is always treated as a single line. -/// @param[in] flags Flags: "m" if multiple expressions in a row are allowed -/// (only the first one will be parsed), "E" if EOC tokens -/// are not allowed (determines whether they will stop -/// parsing process or be recognized as an operator/space, -/// though also yielding an error). -/// -/// Use only "m" to parse like for "=", only "E" to -/// parse like for ":echo", empty string for ":let". +/// @param[in] flags Flags: +/// +/// - "m" if multiple expressions in a row are allowed (only +/// the first one will be parsed), +/// - "E" if EOC tokens are not allowed (determines whether +/// they will stop parsing process or be recognized as an +/// operator/space, though also yielding an error). +/// - "l" when needing to start parsing with lvalues for +/// ":let" or ":for". +/// +/// Common flag sets: +/// - "m" to parse like for ":echo". +/// - "E" to parse like for "=". +/// - empty string for ":call". +/// - "lm" to parse for ":let". /// @param[in] highlight If true, return value will also include "highlight" /// key containing array of 4-tuples (arrays) (Integer, /// Integer, Integer, String), where first three numbers @@ -964,6 +971,9 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; /// value names from ExprCaseCompareStrategy, /// stringified without "kCCStrategy" prefix. Only /// present for "Comparison" nodes. +/// "augmentation": String, augmentation type for "Assignment" nodes. +/// Is either an empty string, "Add", "Subtract" or +/// "Concat" for "=", "+=", "-=" or ".=" respectively. /// "invert": Boolean, true if result of comparison needs to be /// inverted. Only present for "Comparison" nodes. /// "ivalue": Integer, integer value for "Integer" nodes. @@ -979,6 +989,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, switch (flags.data[i]) { case 'm': { pflags |= kExprFlagsMulti; break; } case 'E': { pflags |= kExprFlagsDisallowEOC; break; } + case 'l': { pflags |= kExprFlagsParseLet; break; } case NUL: { api_set_error(err, kErrorTypeValidation, "Invalid flag: '\\0' (%u)", (unsigned)flags.data[i]); @@ -1114,6 +1125,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, + (node->type == kExprNodeFloat) // "fvalue" + (node->type == kExprNodeDoubleQuotedString || node->type == kExprNodeSingleQuotedString) // "svalue" + + (node->type == kExprNodeAssignment) // "augmentation" + 0); Dictionary ret_node = { .items = xmalloc(ret_node_items_size * sizeof(ret_node.items[0])), @@ -1272,6 +1284,17 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, }; break; } + case kExprNodeAssignment: { + const ExprAssignmentType asgn_type = node->data.ass.type; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("augmentation"), + .value = STRING_OBJ( + asgn_type == kExprAsgnPlain + ? (String)STRING_INIT + : cstr_to_string(expr_asgn_type_tab[asgn_type])), + }; + break; + } case kExprNodeMissing: case kExprNodeOpMissing: case kExprNodeTernary: diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index e0bf74567d..9f98b26905 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -6021,11 +6021,21 @@ static const char *highlight_init_dark[] = { }; static const char *highlight_init_cmdline[] = { + // XXX When modifying a list modify it in both valid and invalid halfs. + // TODO(ZyX-I): merge valid and invalid groups via a macros. + // NVimInternalError should appear only when highlighter has a bug. "NVimInternalError ctermfg=Red ctermbg=Red guifg=Red guibg=Red", // Highlight groups (links) used by parser: + "default link NVimAssignment Operator", + "default link NVimPlainAssignment NVimAssignment", + "default link NVimAugmentedAssignment NVimAssignment", + "default link NVimAssignmentWithAddition NVimAugmentedAssignment", + "default link NVimAssignmentWithSubtraction NVimAugmentedAssignment", + "default link NVimAssignmentWithConcatenation NVimAugmentedAssignment", + "default link NVimOperator Operator", "default link NVimUnaryOperator NVimOperator", @@ -6113,6 +6123,16 @@ static const char *highlight_init_cmdline[] = { "default link NVimInvalid Error", + "default link NVimInvalidAssignment NVimInvalid", + "default link NVimInvalidPlainAssignment NVimInvalidAssignment", + "default link NVimInvalidAugmentedAssignment NVimInvalidAssignment", + "default link NVimInvalidAssignmentWithAddition " + "NVimInvalidAugmentedAssignment", + "default link NVimInvalidAssignmentWithSubtraction " + "NVimInvalidAugmentedAssignment", + "default link NVimInvalidAssignmentWithConcatenation " + "NVimInvalidAugmentedAssignment", + "default link NVimInvalidOperator NVimInvalid", "default link NVimInvalidUnaryOperator NVimInvalidOperator", diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index e23c58bfd1..13f7131744 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -84,6 +84,10 @@ typedef enum { /// Just like parsing function arguments, but it is valid to be ended with an /// arrow only. kEPTLambdaArguments, + /// Assignment: parsing for :let + kEPTAssignment, + /// Single assignment: used when lists are not allowed (i.e. when nesting) + kEPTSingleAssignment, } ExprASTParseType; typedef kvec_withinit_t(ExprASTParseType, 4) ExprASTParseTypeStack; @@ -93,6 +97,7 @@ typedef enum { kEOpLvlInvalid = 0, kEOpLvlComplexIdentifier, kEOpLvlParens, + kEOpLvlAssignment, kEOpLvlArrow, kEOpLvlComma, kEOpLvlColon, @@ -217,8 +222,6 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) } CHAR(kExprLexQuestion, '?') CHAR(kExprLexColon, ':') - CHAR(kExprLexDot, '.') - CHAR(kExprLexPlus, '+') CHAR(kExprLexComma, ',') #undef CHAR @@ -532,12 +535,8 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) case '!': case '=': { if (pline.size == 1) { -viml_pexpr_next_token_invalid_comparison: - ret.type = (schar == '!' ? kExprLexNot : kExprLexInvalid); - if (ret.type == kExprLexInvalid) { - ret.data.err.msg = _("E15: Expected == or =~: %.*s"); - ret.data.err.type = kExprLexComparison; - } + ret.type = (schar == '!' ? kExprLexNot : kExprLexAssignment); + ret.data.ass.type = kExprAsgnPlain; break; } ret.type = kExprLexComparison; @@ -548,8 +547,11 @@ viml_pexpr_next_token_invalid_comparison: } else if (pline.data[1] == '~') { ret.data.cmp.type = kExprCmpMatches; ret.len++; + } else if (schar == '!') { + ret.type = kExprLexNot; } else { - goto viml_pexpr_next_token_invalid_comparison; + ret.type = kExprLexAssignment; + ret.data.ass.type = kExprAsgnPlain; } GET_CCS(ret, pline); break; @@ -571,17 +573,37 @@ viml_pexpr_next_token_invalid_comparison: break; } - // Minus sign or arrow from lambdas. + // Minus sign, arrow from lambdas or augmented assignment. case '-': { if (pline.size > 1 && pline.data[1] == '>') { ret.len++; ret.type = kExprLexArrow; + } else if (pline.size > 1 && pline.data[1] == '=') { + ret.len++; + ret.type = kExprLexAssignment; + ret.data.ass.type = kExprAsgnSubtract; } else { ret.type = kExprLexMinus; } break; } + // Sign or augmented assignment. +#define CHAR_OR_ASSIGN(ch, ch_type, ass_type) \ + case ch: { \ + if (pline.size > 1 && pline.data[1] == '=') { \ + ret.len++; \ + ret.type = kExprLexAssignment; \ + ret.data.ass.type = ass_type; \ + } else { \ + ret.type = ch_type; \ + } \ + break; \ + } + CHAR_OR_ASSIGN('+', kExprLexPlus, kExprAsgnAdd) + CHAR_OR_ASSIGN('.', kExprLexDot, kExprAsgnConcat) +#undef CHAR_OR_ASSIGN + // Expression end because Ex command ended. case NUL: case NL: { @@ -661,6 +683,7 @@ static const char *const eltkn_type_tab[] = { [kExprLexParenthesis] = "Parenthesis", [kExprLexComma] = "Comma", [kExprLexArrow] = "Arrow", + [kExprLexAssignment] = "Assignment", }; const char *const eltkn_cmp_type_tab[] = { @@ -671,6 +694,13 @@ const char *const eltkn_cmp_type_tab[] = { [kExprCmpIdentical] = "Identical", }; +const char *const expr_asgn_type_tab[] = { + [kExprAsgnPlain] = "Plain", + [kExprAsgnAdd] = "Add", + [kExprAsgnSubtract] = "Subtract", + [kExprAsgnConcat] = "Concat", +}; + const char *const ccs_tab[] = { [kCCStrategyUseOption] = "UseOption", [kCCStrategyMatchCase] = "MatchCase", @@ -732,6 +762,8 @@ const char *viml_pexpr_repr_token(const ParserState *const pstate, (int)token.data.cmp.inv) TKNARGS(kExprLexMultiplication, "(type=%s)", eltkn_mul_type_tab[token.data.mul.type]) + TKNARGS(kExprLexAssignment, "(type=%s)", + expr_asgn_type_tab[token.data.ass.type]) TKNARGS(kExprLexRegister, "(name=%s)", intchar2str(token.data.reg.name)) case kExprLexDoubleQuotedString: TKNARGS(kExprLexSingleQuotedString, "(closed=%i)", @@ -811,6 +843,7 @@ const char *const east_node_type_tab[] = { [kExprNodeMod] = "Mod", [kExprNodeOption] = "Option", [kExprNodeEnvironment] = "Environment", + [kExprNodeAssignment] = "Assignment", }; /// Represent `int` character as a string @@ -933,6 +966,7 @@ const uint8_t node_maxchildren[] = { [kExprNodeMod] = 2, [kExprNodeOption] = 0, [kExprNodeEnvironment] = 0, + [kExprNodeAssignment] = 2, }; /// Free memory occupied by AST @@ -993,6 +1027,7 @@ void viml_pexpr_free_ast(ExprAST ast) case kExprNodeLambda: case kExprNodeDictLiteral: case kExprNodeCurlyBracesIdentifier: + case kExprNodeAssignment: case kExprNodeComma: case kExprNodeColon: case kExprNodeArrow: @@ -1111,6 +1146,8 @@ static struct { [kExprNodeCurlyBracesIdentifier] = { kEOpLvlComplexIdentifier, kEOpAssLeft }, + [kExprNodeAssignment] = { kEOpLvlAssignment, kEOpAssLeft }, + [kExprNodeComplexIdentifier] = { kEOpLvlValue, kEOpAssLeft }, [kExprNodePlainIdentifier] = { kEOpLvlValue, kEOpAssNo }, @@ -1478,6 +1515,17 @@ static inline void east_set_error(const ParserState *const pstate, } \ } while (0) +/// Determine whether given parse type is an assignment +/// +/// @param[in] pt Checked parse type. +/// +/// @return true if parsing an assignment, false otherwise. +static inline bool pt_is_assignment(const ExprASTParseType pt) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT +{ + return (pt == kEPTAssignment || pt == kEPTSingleAssignment); +} + /// Structure used to define “string shifts” necessary to map string /// highlighting to actual strings. typedef struct { @@ -1839,6 +1887,9 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) ExprASTParseTypeStack pt_stack; kvi_init(pt_stack); kvi_push(pt_stack, kEPTExpr); + if (flags & kExprFlagsParseLet) { + kvi_push(pt_stack, kEPTAssignment); + } LexExprToken prev_token = { .type = kExprLexMissing }; bool highlighted_prev_spacing = false; // Lambda node, valid when parsing lambda arguments only. @@ -1938,33 +1989,83 @@ viml_pexpr_parse_process_token: // circumstances, and in any case runtime and not parse time errors. (*kv_Z(ast_stack, 1))->type = kExprNodeConcat; } - if (kv_last(pt_stack) == kEPTLambdaArguments - && ((want_node == kENodeOperator + // Pop some stack pt_stack items in case of misplaced nodes. + const bool is_single_assignment = kv_last(pt_stack) == kEPTSingleAssignment; + switch (kv_last(pt_stack)) { + case kEPTExpr: { + break; + } + case kEPTLambdaArguments: { + if ((want_node == kENodeOperator && tok_type != kExprLexComma && tok_type != kExprLexArrow) || (want_node == kENodeValue && !(cur_token.type == kExprLexPlainIdentifier && cur_token.data.var.scope == kExprVarScopeMissing && !cur_token.data.var.autoload) - && tok_type != kExprLexArrow))) { - lambda_node->data.fig.type_guesses.allow_lambda = false; - if (lambda_node->children != NULL - && lambda_node->children->type == kExprNodeComma) { - // If lambda has comma child this means that parser has already seen at - // least "{arg1,", so node cannot possibly be anything, but lambda. - - // Vim may give E121 or E720 in this case, but it does not look right to - // have either because both are results of reevaluation possibly-lambda - // node as a dictionary and here this is not going to happen. - ERROR_FROM_TOKEN_AND_MSG( - cur_token, _("E15: Expected lambda arguments list or arrow: %.*s")); - } else { - // Else it may appear that possibly-lambda node is actually a dictionary - // or curly-braces-name identifier. - lambda_node = NULL; - kv_drop(pt_stack, 1); + && tok_type != kExprLexArrow)) { + lambda_node->data.fig.type_guesses.allow_lambda = false; + if (lambda_node->children != NULL + && lambda_node->children->type == kExprNodeComma) { + // If lambda has comma child this means that parser has already seen at + // least "{arg1,", so node cannot possibly be anything, but lambda. + + // Vim may give E121 or E720 in this case, but it does not look right to + // have either because both are results of reevaluation possibly-lambda + // node as a dictionary and here this is not going to happen. + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Expected lambda arguments list or arrow: %.*s")); + } else { + // Else it may appear that possibly-lambda node is actually a dictionary + // or curly-braces-name identifier. + lambda_node = NULL; + kv_drop(pt_stack, 1); + } + } + break; + } + case kEPTSingleAssignment: { + if (tok_type == kExprLexBracket && !cur_token.data.brc.closing) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E475: Nested lists not allowed when assigning: %.*s")); + kv_drop(pt_stack, 2); + assert(kv_size(pt_stack)); + assert(kv_last(pt_stack) == kEPTExpr); + break; + } + FALLTHROUGH; + } + case kEPTAssignment: { + if (want_node == kENodeValue + && tok_type != kExprLexBracket + && tok_type != kExprLexPlainIdentifier + && (tok_type != kExprLexFigureBrace || cur_token.data.brc.closing) + && !(node_is_key && tok_type == kExprLexNumber) + && tok_type != kExprLexEnv + && tok_type != kExprLexOption + && tok_type != kExprLexRegister) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Expected value part of assignment lvalue: %.*s")); + kv_drop(pt_stack, 1); + } else if (want_node == kENodeOperator + && tok_type != kExprLexBracket + && (tok_type != kExprLexFigureBrace + || cur_token.data.brc.closing) + && tok_type != kExprLexDot + && (tok_type != kExprLexComma || !is_single_assignment) + && tok_type != kExprLexAssignment) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Expected assignment operator or subscript: %.*s")); + kv_drop(pt_stack, 1); + } + assert(kv_size(pt_stack)); + break; } } + assert(kv_size(pt_stack)); const ExprASTParseType cur_pt = kv_last(pt_stack); assert(lambda_node == NULL || cur_pt == kEPTLambdaArguments); switch (tok_type) { @@ -2339,21 +2440,41 @@ viml_pexpr_parse_bracket_closing_error: } kvi_push(ast_stack, new_top_node_p); want_node = kENodeOperator; + if (cur_pt == kEPTSingleAssignment) { + kv_drop(pt_stack, 1); + } else if (cur_pt == kEPTAssignment) { + assert(ast.err.msg); + } else if (cur_pt == kEPTExpr + && kv_size(pt_stack) > 1 + && pt_is_assignment(kv_Z(pt_stack, 1))) { + kv_drop(pt_stack, 1); + } } else { if (want_node == kENodeValue) { - // Value means list literal. + // Value means list literal or list assignment. HL_CUR_TOKEN(List); NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeListLiteral); *top_node_p = cur_node; kvi_push(ast_stack, &cur_node->children); want_node = kENodeValue; + if (cur_pt == kEPTAssignment) { + // Additional assignment parse type allows to easily forbid nested + // lists. + kvi_push(pt_stack, kEPTSingleAssignment); + } } else { + // Operator means subscript, also in assignment. But in assignment + // subscript may be pretty much any expression, so need to push + // kEPTExpr. if (prev_token.type == kExprLexSpacing) { OP_MISSING; } NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeSubscript); ADD_OP_NODE(cur_node); HL_CUR_TOKEN(SubscriptBracket); + if (pt_is_assignment(cur_pt)) { + kvi_push(pt_stack, kEPTExpr); + } } } break; @@ -2458,15 +2579,31 @@ viml_pexpr_parse_figure_brace_closing_error: } kvi_push(ast_stack, new_top_node_p); want_node = kENodeOperator; + if (cur_pt == kEPTExpr + && kv_size(pt_stack) > 1 + && pt_is_assignment(kv_Z(pt_stack, 1))) { + kv_drop(pt_stack, 1); + } } else { if (want_node == kENodeValue) { HL_CUR_TOKEN(FigureBrace); // Value: may be any of lambda, dictionary literal and curly braces // name. - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnknownFigure); - cur_node->data.fig.type_guesses.allow_lambda = true; - cur_node->data.fig.type_guesses.allow_dict = true; - cur_node->data.fig.type_guesses.allow_ident = true; + + // Though if we are in an assignment this may only be a curly braces + // name. + if (pt_is_assignment(cur_pt)) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeCurlyBracesIdentifier); + cur_node->data.fig.type_guesses.allow_lambda = false; + cur_node->data.fig.type_guesses.allow_dict = false; + cur_node->data.fig.type_guesses.allow_ident = true; + kvi_push(pt_stack, kEPTExpr); + } else { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnknownFigure); + cur_node->data.fig.type_guesses.allow_lambda = true; + cur_node->data.fig.type_guesses.allow_dict = true; + cur_node->data.fig.type_guesses.allow_ident = true; + } if (pstate->colors) { cur_node->data.fig.opening_hl_idx = kv_size(*pstate->colors) - 1; } @@ -2484,6 +2621,9 @@ viml_pexpr_parse_figure_brace_closing_error: cur_node->data.fig.type_guesses.allow_dict = false; cur_node->data.fig.type_guesses.allow_ident = true; kvi_push(ast_stack, &cur_node->children); + if (pt_is_assignment(cur_pt)) { + kvi_push(pt_stack, kEPTExpr); + } want_node = kENodeValue; } while (0), Curly); @@ -2746,6 +2886,36 @@ viml_pexpr_parse_no_paren_closing_error: {} want_node = kENodeOperator; break; } + case kExprLexAssignment: { + if (cur_pt == kEPTAssignment) { + kv_drop(pt_stack, 1); + } else if (cur_pt == kEPTSingleAssignment) { + kv_drop(pt_stack, 2); + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E475: Expected closing bracket to end list assignment " + "lvalue: %.*s")); + } else { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Misplaced assignment: %.*s")); + } + assert(kv_size(pt_stack)); + assert(kv_last(pt_stack) == kEPTExpr); + ADD_VALUE_IF_MISSING(_("E15: Unexpected assignment: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeAssignment); + cur_node->data.ass.type = cur_token.data.ass.type; + switch (cur_token.data.ass.type) { +#define HL_ASGN(asgn, hl) \ + case kExprAsgn##asgn: { HL_CUR_TOKEN(hl); break; } + HL_ASGN(Plain, PlainAssignment) + HL_ASGN(Add, AssignmentWithAddition) + HL_ASGN(Subtract, AssignmentWithSubtraction) + HL_ASGN(Concat, AssignmentWithConcatenation) +#undef HL_ASGN + } + ADD_OP_NODE(cur_node); + break; + } } viml_pexpr_parse_cycle_end: prev_token = cur_token; @@ -2862,6 +3032,7 @@ viml_pexpr_parse_end: // FIXME: Investigate whether above are OK to be present in the stack. break; } + case kExprNodeAssignment: case kExprNodeMod: case kExprNodeDivision: case kExprNodeMultiplication: diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 648f8cbc1f..23e172da75 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -51,6 +51,9 @@ typedef enum { kExprLexParenthesis, ///< Parenthesis, either opening or closing. kExprLexComma, ///< Comma. kExprLexArrow, ///< Arrow, like from lambda expressions. + kExprLexAssignment, ///< Assignment: `=` or `{op}=`. + // XXX When modifying this enum you need to also modify eltkn_type_tab in + // expressions.c and tests and, possibly, viml_pexpr_repr_token. } LexExprTokenType; typedef enum { @@ -68,6 +71,14 @@ typedef enum { kExprOptScopeLocal = 'l', } ExprOptScope; +/// All possible assignment types: `=` and `{op}=`. +typedef enum { + kExprAsgnPlain = 0, ///< Plain assignment: `=`. + kExprAsgnAdd, ///< Assignment augmented with addition: `+=`. + kExprAsgnSubtract, ///< Assignment augmented with subtraction: `-=`. + kExprAsgnConcat, ///< Assignment augmented with concatenation: `.=`. +} ExprAssignmentType; + #define EXPR_OPT_SCOPE_LIST \ ((char[]){ kExprOptScopeGlobal, kExprOptScopeLocal }) @@ -147,6 +158,10 @@ typedef struct { uint8_t base; ///< Base: 2, 8, 10 or 16. bool is_float; ///< True if number is a floating-point. } num; ///< For kExprLexNumber + + struct { + ExprAssignmentType type; + } ass; ///< For kExprLexAssignment } data; ///< Additional data, if needed. } LexExprToken; @@ -170,8 +185,8 @@ typedef enum { /// “EOC” is something like "|". It is fine with emitting EOC at the end of /// string still, with or without this flag set. kELFlagForbidEOC = (1 << 4), - // WARNING: whenever you add a new flag, alter klee_assume() statement in - // viml_expressions_lexer.c. + // XXX Whenever you add a new flag, alter klee_assume() statement in + // viml_expressions_lexer.c. } LexExprFlags; /// Expression AST node type @@ -233,6 +248,10 @@ typedef enum { kExprNodeMod, kExprNodeOption, kExprNodeEnvironment, + kExprNodeAssignment, + // XXX When modifying this list also modify east_node_type_tab both in parser + // and in tests, and you most likely will also have to alter list of + // highlight groups stored in highlight_init_cmdline variable. } ExprASTNodeType; typedef struct expr_ast_node ExprASTNode; @@ -301,6 +320,9 @@ struct expr_ast_node { const char *ident; ///< Environment variable name start. size_t ident_len; ///< Environment variable name length. } env; ///< For kExprNodeEnvironment. + struct { + ExprAssignmentType type; + } ass; ///< For kExprNodeAssignment } data; }; @@ -314,8 +336,15 @@ enum { /// When parsing expressions input by user bar is assumed to be a binary /// operator and other two are spacings. kExprFlagsDisallowEOC = (1 << 1), - // WARNING: whenever you add a new flag, alter klee_assume() statement in - // viml_expressions_parser.c. + /// Parse :let argument + /// + /// That mean that top level node must be an assignment and first nodes + /// belong to lvalues. + kExprFlagsParseLet = (1 << 2), + // XXX whenever you add a new flag, alter klee_assume() statement in + // viml_expressions_parser.c, nvim_parse_expression() flags parsing + // alongside with its documentation and flag sets in check_parsing() + // function in expressions parser functional and unit tests. } ExprParserFlags; /// AST error definition @@ -350,6 +379,9 @@ extern const char *const eltkn_cmp_type_tab[]; /// Array mapping ExprCaseCompareStrategy values to their stringified versions extern const char *const ccs_tab[]; +/// Array mapping ExprAssignmentType values to their stringified versions +extern const char *const expr_asgn_type_tab[]; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "viml/parser/expressions.h.generated.h" #endif -- cgit From 45445e2e03f1cbfa25dde76ccf3e24d0d297cabe Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 12 Nov 2017 03:52:26 +0300 Subject: unittests: Add some more edge test cases --- src/nvim/viml/parser/expressions.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 13f7131744..07bac89997 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -40,6 +40,13 @@ // E15: Invalid expression: [1, // // < , just exactly one E697 message. +// 6. Some expressions involving calling parenthesis which are treated +// separately by Vim even when not separated by spaces are treated as one +// expression by Neovim: e.g. ":echo (1)(1)" will yield runtime error after +// failing to call "1", while Vim will echo "1 1". Reasoning is the same: +// type of what is in the first expression is generally not known when +// parsing, so to have separate expressions like this separate them with +// spaces. #include #include -- cgit From 556451a7f2fd513db33b9d7ac1b653d356b7b915 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 12 Nov 2017 16:59:36 +0300 Subject: unittests,syntax: Check for sanity of highlight_init_cmdline Also fixes some errors found. --- src/nvim/syntax.c | 14 +++++++++----- src/nvim/syntax.h | 3 +++ 2 files changed, 12 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 9f98b26905..c9e99d82f8 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -6020,7 +6020,7 @@ static const char *highlight_init_dark[] = { NULL }; -static const char *highlight_init_cmdline[] = { +const char *const highlight_init_cmdline[] = { // XXX When modifying a list modify it in both valid and invalid halfs. // TODO(ZyX-I): merge valid and invalid groups via a macros. @@ -6047,6 +6047,7 @@ static const char *highlight_init_cmdline[] = { "default link NVimComparison NVimBinaryOperator", "default link NVimComparisonModifier NVimComparison", "default link NVimBinaryPlus NVimBinaryOperator", + "default link NVimBinaryMinus NVimBinaryOperator", "default link NVimConcat NVimBinaryOperator", "default link NVimConcatOrSubscript NVimConcat", "default link NVimOr NVimBinaryOperator", @@ -6108,9 +6109,6 @@ static const char *highlight_init_cmdline[] = { "default link NVimDoubleQuote NVimStringQuote", "default link NVimDoubleQuotedBody NVimStringBody", "default link NVimDoubleQuotedEscape NVimStringSpecial", - // Not actually invalid, but we highlight user that he is doing something - // wrong. - "default link NVimDoubleQuotedUnknownEscape NVimInvalidValue", "default link NVimFigureBrace NVimInternalError", "default link NVimSingleQuotedUnknownEscape NVimInternalError", @@ -6144,6 +6142,7 @@ static const char *highlight_init_cmdline[] = { "default link NVimInvalidComparison NVimInvalidBinaryOperator", "default link NVimInvalidComparisonModifier NVimInvalidComparison", "default link NVimInvalidBinaryPlus NVimInvalidBinaryOperator", + "default link NVimInvalidBinaryMinus NVimInvalidBinaryOperator", "default link NVimInvalidConcat NVimInvalidBinaryOperator", "default link NVimInvalidConcatOrSubscript NVimInvalidConcat", "default link NVimInvalidOr NVimInvalidBinaryOperator", @@ -6217,12 +6216,17 @@ static const char *highlight_init_cmdline[] = { "default link NVimInvalidFigureBrace NVimInvalidDelimiter", "default link NVimInvalidSpacing ErrorMsg", + + // Not actually invalid, but we highlight user that he is doing something + // wrong. + "default link NVimDoubleQuotedUnknownEscape NVimInvalidValue", + NULL, }; /// Create default links for NVim* highlight groups used for cmdline coloring void syn_init_cmdline_highlight(bool reset, bool init) { - for (size_t i = 0 ; i < ARRAY_SIZE(highlight_init_cmdline) ; i++) { + for (size_t i = 0 ; highlight_init_cmdline[i] != NULL ; i++) { do_highlight(highlight_init_cmdline[i], reset, init); } } diff --git a/src/nvim/syntax.h b/src/nvim/syntax.h index bb733ead30..c9b0665ec8 100644 --- a/src/nvim/syntax.h +++ b/src/nvim/syntax.h @@ -45,6 +45,9 @@ typedef struct { } color_name_table_T; extern color_name_table_T color_name_table[]; +/// Array of highlight definitions, used for unit testing +extern const char *const highlight_init_cmdline[]; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "syntax.h.generated.h" #endif -- cgit From 342239a9c53cf4857d18c0583d4cab1fdca534fa Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 13 Nov 2017 01:10:39 +0300 Subject: unittests,viml/parser/expressions: Start adding asgn parsing tests --- src/nvim/viml/parser/expressions.c | 50 ++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 07bac89997..4bd3652292 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -1363,7 +1363,6 @@ static inline ParserPosition recol_pos(const ParserPosition pos, } \ } while (0) -// TODO(ZyX-I): actual condition /// Check whether it is possible to have next expression after current /// /// For :echo: `:echo @a @a` is a valid expression. `:echo (@a @a)` is not. @@ -1901,6 +1900,7 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) bool highlighted_prev_spacing = false; // Lambda node, valid when parsing lambda arguments only. ExprASTNode *lambda_node = NULL; + size_t asgn_level = 0; do { const bool is_concat_or_subscript = ( want_node == kENodeValue @@ -2063,6 +2063,9 @@ viml_pexpr_parse_process_token: && tok_type != kExprLexDot && (tok_type != kExprLexComma || !is_single_assignment) && tok_type != kExprLexAssignment) { + if (flags & kExprFlagsMulti && MAY_HAVE_NEXT_EXPR) { + goto viml_pexpr_parse_end; + } ERROR_FROM_TOKEN_AND_MSG( cur_token, _("E15: Expected assignment operator or subscript: %.*s")); @@ -2429,6 +2432,10 @@ viml_pexpr_parse_valid_colon: ExprASTNode *new_top_node = *new_top_node_p; switch (new_top_node->type) { case kExprNodeListLiteral: { + if (pt_is_assignment(cur_pt) && new_top_node->children == NULL) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E475: Unable to assign to empty list: %.*s")); + } HL_CUR_TOKEN(List); break; } @@ -2447,14 +2454,18 @@ viml_pexpr_parse_bracket_closing_error: } kvi_push(ast_stack, new_top_node_p); want_node = kENodeOperator; - if (cur_pt == kEPTSingleAssignment) { - kv_drop(pt_stack, 1); - } else if (cur_pt == kEPTAssignment) { - assert(ast.err.msg); - } else if (cur_pt == kEPTExpr - && kv_size(pt_stack) > 1 - && pt_is_assignment(kv_Z(pt_stack, 1))) { - kv_drop(pt_stack, 1); + if (kv_size(ast_stack) <= asgn_level) { + assert(kv_size(ast_stack) == asgn_level); + asgn_level = 0; + if (cur_pt == kEPTSingleAssignment) { + kv_drop(pt_stack, 1); + } else if (cur_pt == kEPTAssignment) { + assert(ast.err.msg); + } else if (cur_pt == kEPTExpr + && kv_size(pt_stack) > 1 + && pt_is_assignment(kv_Z(pt_stack, 1))) { + kv_drop(pt_stack, 1); + } } } else { if (want_node == kENodeValue) { @@ -2480,6 +2491,7 @@ viml_pexpr_parse_bracket_closing_error: ADD_OP_NODE(cur_node); HL_CUR_TOKEN(SubscriptBracket); if (pt_is_assignment(cur_pt)) { + asgn_level = kv_size(ast_stack); kvi_push(pt_stack, kEPTExpr); } } @@ -2586,10 +2598,14 @@ viml_pexpr_parse_figure_brace_closing_error: } kvi_push(ast_stack, new_top_node_p); want_node = kENodeOperator; - if (cur_pt == kEPTExpr - && kv_size(pt_stack) > 1 - && pt_is_assignment(kv_Z(pt_stack, 1))) { - kv_drop(pt_stack, 1); + if (kv_size(ast_stack) <= asgn_level) { + assert(kv_size(ast_stack) == asgn_level); + if (cur_pt == kEPTExpr + && kv_size(pt_stack) > 1 + && pt_is_assignment(kv_Z(pt_stack, 1))) { + kv_drop(pt_stack, 1); + asgn_level = 0; + } } } else { if (want_node == kENodeValue) { @@ -2635,6 +2651,10 @@ viml_pexpr_parse_figure_brace_closing_error: } while (0), Curly); } + if (pt_is_assignment(cur_pt) + && !pt_is_assignment(kv_last(pt_stack))) { + asgn_level = kv_size(ast_stack); + } } break; } @@ -2755,6 +2775,10 @@ viml_pexpr_parse_figure_brace_closing_error: case kExprLexDot: { ADD_VALUE_IF_MISSING(_("E15: Unexpected dot: %.*s")); if (prev_token.type == kExprLexSpacing) { + if (cur_pt == kEPTAssignment) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Cannot concatenate in assignments: %.*s")); + } NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeConcat); HL_CUR_TOKEN(Concat); } else { -- cgit From c287893225bad586af486b37546f5982e5b1cd03 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 19 Nov 2017 19:22:54 +0300 Subject: viml/parser/expressions,unittests: Do better testing, fix found issues --- src/nvim/viml/parser/expressions.c | 40 ++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 4bd3652292..0824b3ca7d 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -2031,18 +2031,7 @@ viml_pexpr_parse_process_token: } break; } - case kEPTSingleAssignment: { - if (tok_type == kExprLexBracket && !cur_token.data.brc.closing) { - ERROR_FROM_TOKEN_AND_MSG( - cur_token, - _("E475: Nested lists not allowed when assigning: %.*s")); - kv_drop(pt_stack, 2); - assert(kv_size(pt_stack)); - assert(kv_last(pt_stack) == kEPTExpr); - break; - } - FALLTHROUGH; - } + case kEPTSingleAssignment: case kEPTAssignment: { if (want_node == kENodeValue && tok_type != kExprLexBracket @@ -2062,7 +2051,13 @@ viml_pexpr_parse_process_token: || cur_token.data.brc.closing) && tok_type != kExprLexDot && (tok_type != kExprLexComma || !is_single_assignment) - && tok_type != kExprLexAssignment) { + && tok_type != kExprLexAssignment + // Curly brace identifiers: will contain plain identifier or + // another curly brace in position where operator is wanted. + && !((tok_type == kExprLexPlainIdentifier + || (tok_type == kExprLexFigureBrace + && !cur_token.data.brc.closing)) + && prev_token.type != kExprLexSpacing)) { if (flags & kExprFlagsMulti && MAY_HAVE_NEXT_EXPR) { goto viml_pexpr_parse_end; } @@ -2457,9 +2452,7 @@ viml_pexpr_parse_bracket_closing_error: if (kv_size(ast_stack) <= asgn_level) { assert(kv_size(ast_stack) == asgn_level); asgn_level = 0; - if (cur_pt == kEPTSingleAssignment) { - kv_drop(pt_stack, 1); - } else if (cur_pt == kEPTAssignment) { + if (cur_pt == kEPTAssignment) { assert(ast.err.msg); } else if (cur_pt == kEPTExpr && kv_size(pt_stack) > 1 @@ -2467,10 +2460,12 @@ viml_pexpr_parse_bracket_closing_error: kv_drop(pt_stack, 1); } } + if (cur_pt == kEPTSingleAssignment && kv_size(ast_stack) == 1) { + kv_drop(pt_stack, 1); + } } else { if (want_node == kENodeValue) { // Value means list literal or list assignment. - HL_CUR_TOKEN(List); NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeListLiteral); *top_node_p = cur_node; kvi_push(ast_stack, &cur_node->children); @@ -2479,7 +2474,12 @@ viml_pexpr_parse_bracket_closing_error: // Additional assignment parse type allows to easily forbid nested // lists. kvi_push(pt_stack, kEPTSingleAssignment); + } else if (cur_pt == kEPTSingleAssignment) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E475: Nested lists not allowed when assigning: %.*s")); } + HL_CUR_TOKEN(List); } else { // Operator means subscript, also in assignment. But in assignment // subscript may be pretty much any expression, so need to push @@ -2491,7 +2491,8 @@ viml_pexpr_parse_bracket_closing_error: ADD_OP_NODE(cur_node); HL_CUR_TOKEN(SubscriptBracket); if (pt_is_assignment(cur_pt)) { - asgn_level = kv_size(ast_stack); + assert(want_node == kENodeValue); // Subtract 1 for NULL at top. + asgn_level = kv_size(ast_stack) - 1; kvi_push(pt_stack, kEPTExpr); } } @@ -2653,7 +2654,8 @@ viml_pexpr_parse_figure_brace_closing_error: } if (pt_is_assignment(cur_pt) && !pt_is_assignment(kv_last(pt_stack))) { - asgn_level = kv_size(ast_stack); + assert(want_node == kENodeValue); // Subtract 1 for NULL at top. + asgn_level = kv_size(ast_stack) - 1; } } break; -- cgit From 764cf3251de4f141fa451633dbdb48440ddf218a Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 19 Nov 2017 19:27:21 +0300 Subject: charset: Add missing include needed for vim_str2nr --- src/nvim/charset.h | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/charset.h b/src/nvim/charset.h index 7198c8d405..eb64b6128a 100644 --- a/src/nvim/charset.h +++ b/src/nvim/charset.h @@ -4,6 +4,7 @@ #include "nvim/types.h" #include "nvim/pos.h" #include "nvim/buffer_defs.h" +#include "nvim/eval/typval.h" /// Return the folded-case equivalent of the given character /// -- cgit From f20f97c936f1438589c8176f62ce69c26e255f85 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 19 Nov 2017 21:13:27 +0300 Subject: *: Fix linter errors --- src/nvim/viml/parser/expressions.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 0824b3ca7d..6c7c328b6d 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -2014,17 +2014,20 @@ viml_pexpr_parse_process_token: lambda_node->data.fig.type_guesses.allow_lambda = false; if (lambda_node->children != NULL && lambda_node->children->type == kExprNodeComma) { - // If lambda has comma child this means that parser has already seen at - // least "{arg1,", so node cannot possibly be anything, but lambda. - - // Vim may give E121 or E720 in this case, but it does not look right to - // have either because both are results of reevaluation possibly-lambda - // node as a dictionary and here this is not going to happen. + // If lambda has comma child this means that parser has already seen + // at least "{arg1,", so node cannot possibly be anything, but + // lambda. + + // Vim may give E121 or E720 in this case, but it does not look + // right to have either because both are results of reevaluation + // possibly-lambda node as a dictionary and here this is not going + // to happen. ERROR_FROM_TOKEN_AND_MSG( - cur_token, _("E15: Expected lambda arguments list or arrow: %.*s")); + cur_token, + _("E15: Expected lambda arguments list or arrow: %.*s")); } else { - // Else it may appear that possibly-lambda node is actually a dictionary - // or curly-braces-name identifier. + // Else it may appear that possibly-lambda node is actually + // a dictionary or curly-braces-name identifier. lambda_node = NULL; kv_drop(pt_stack, 1); } -- cgit From 731dc82f8c04e3019ecc3243b6b512535998635b Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 19 Nov 2017 21:21:45 +0300 Subject: ex_getln: Fix memory leak in color_expr_cmdline --- src/nvim/ex_getln.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index e6fb2571c4..cabdda28cf 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -2419,6 +2419,7 @@ static void color_expr_cmdline(const CmdlineInfo *const colored_ccline, .attr = 0, })); } + kvi_destroy(colors); } /// Color command-line -- cgit From 6ea3a08fdbb276fe64dda60c5fb934360327ed39 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 19 Nov 2017 21:55:36 +0300 Subject: syntax: Fix duplicate group definitions --- src/nvim/syntax.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'src') diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index c9e99d82f8..9a537130fd 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -6067,7 +6067,6 @@ const char *const highlight_init_cmdline[] = { "default link NVimSubscript NVimParenthesis", "default link NVimSubscriptBracket NVimSubscript", "default link NVimSubscriptColon NVimSubscript", - "default link NVimSubscriptColon NVimSubscript", "default link NVimCurly NVimSubscript", "default link NVimContainer NVimParenthesis", @@ -6164,7 +6163,6 @@ const char *const highlight_init_cmdline[] = { "default link NVimInvalidSubscript NVimInvalidParenthesis", "default link NVimInvalidSubscriptBracket NVimInvalidSubscript", "default link NVimInvalidSubscriptColon NVimInvalidSubscript", - "default link NVimInvalidSubscriptColon NVimInvalidSubscript", "default link NVimInvalidCurly NVimInvalidSubscript", "default link NVimInvalidContainer NVimInvalidParenthesis", -- cgit From 17077b68133a62d0dc1b84cb48779464c117e028 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 26 Nov 2017 16:08:53 +0300 Subject: viml/parser/expressions: Make $ENV not depend on &isident --- src/nvim/api/vim.c | 2 +- src/nvim/viml/parser/expressions.c | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 7838f9faa3..a0816dbc8e 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -983,7 +983,7 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; /// "DoubleQuotedString" nodes. Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, Error *err) - FUNC_API_SINCE(4) + FUNC_API_SINCE(4) FUNC_API_ASYNC { int pflags = 0; for (size_t i = 0 ; i < flags.size ; i++) { diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 6c7c328b6d..63ad6bab35 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -47,6 +47,8 @@ // type of what is in the first expression is generally not known when // parsing, so to have separate expressions like this separate them with // spaces. +// 7. 'isident' no longer applies to environment variables, they always include +// ASCII alphanumeric characters and underscore and nothing except this. #include #include @@ -383,10 +385,14 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) break; } +#define ISWORD_OR_AUTOLOAD(x) \ + (ASCII_ISALNUM(x) || (x) == AUTOLOAD_CHAR || (x) == '_') +#define ISWORD(x) \ + (ASCII_ISALNUM(x) || (x) == '_') + // Environment variable. case '$': { - // FIXME: Parser function can’t be thread-safe with vim_isIDc. - CHARREG(kExprLexEnv, vim_isIDc); + CHARREG(kExprLexEnv, ISWORD); break; } @@ -400,10 +406,6 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '_': { -#define ISWORD_OR_AUTOLOAD(x) \ - (ASCII_ISALNUM(x) || (x) == AUTOLOAD_CHAR || (x) == '_') -#define ISWORD(x) \ - (ASCII_ISALNUM(x) || (x) == '_') ret.data.var.scope = 0; ret.data.var.autoload = false; CHARREG(kExprLexPlainIdentifier, ISWORD); @@ -441,9 +443,10 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) CHARREG(kExprLexPlainIdentifier, ISWORD_OR_AUTOLOAD); } break; -#undef ISWORD_OR_AUTOLOAD -#undef ISWORD } + +#undef ISWORD +#undef ISWORD_OR_AUTOLOAD #undef CHARREG // Option. -- cgit From cddf84c3982b8225f1592b6a61b63f8d1883ca94 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 26 Nov 2017 16:45:29 +0300 Subject: functests: Add some more tests --- src/nvim/syntax.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 8e5a119b1f..55ff49d160 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -6170,18 +6170,18 @@ const char *const highlight_init_cmdline[] = { "default link NVimInvalidDict NVimInvalidContainer", "default link NVimInvalidList NVimInvalidContainer", - "default link NVimInvalidIdentifier Identifier", - "default link NVimInvalidIdentifierScope NVimIdentifier", - "default link NVimInvalidIdentifierScopeDelimiter NVimIdentifier", - "default link NVimInvalidIdentifierName NVimIdentifier", - "default link NVimInvalidIdentifierKey NVimIdentifier", + "default link NVimInvalidValue NVimInvalid", + + "default link NVimInvalidIdentifier NVimInvalidValue", + "default link NVimInvalidIdentifierScope NVimInvalidIdentifier", + "default link NVimInvalidIdentifierScopeDelimiter NVimInvalidIdentifier", + "default link NVimInvalidIdentifierName NVimInvalidIdentifier", + "default link NVimInvalidIdentifierKey NVimInvalidIdentifier", "default link NVimInvalidColon NVimInvalidDelimiter", "default link NVimInvalidComma NVimInvalidDelimiter", "default link NVimInvalidArrow NVimInvalidDelimiter", - "default link NVimInvalidValue NVimInvalid", - "default link NVimInvalidRegister NVimInvalidValue", "default link NVimInvalidNumber NVimInvalidValue", "default link NVimInvalidFloat NVimInvalidNumber", @@ -6199,7 +6199,7 @@ const char *const highlight_init_cmdline[] = { // Invalid string bodies and specials are still highlighted as valid ones to // minimize the red area. "default link NVimInvalidString NVimInvalidValue", - "default link NVimInvalidStringBody NVimString", + "default link NVimInvalidStringBody NVimStringBody", "default link NVimInvalidStringQuote NVimInvalidString", "default link NVimInvalidStringSpecial NVimStringSpecial", -- cgit From 36a4f3a259ffa282129b18358cce4130397077c5 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 26 Nov 2017 16:57:42 +0300 Subject: viml/parser/expressions: Make sure that listed nodes may be present With the new test leaving `assert(false);` for any of the cases makes tests crash. --- src/nvim/viml/parser/expressions.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 63ad6bab35..9773e60bbd 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -3065,12 +3065,9 @@ viml_pexpr_parse_end: // to be caught later. break; } + case kExprNodeSubscript: case kExprNodeConcatOrSubscript: case kExprNodeComplexIdentifier: - case kExprNodeSubscript: { - // FIXME: Investigate whether above are OK to be present in the stack. - break; - } case kExprNodeAssignment: case kExprNodeMod: case kExprNodeDivision: -- cgit From de45ec0146486c49719ff6f6dcceb4914b471c7a Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 30 Nov 2017 02:01:49 +0300 Subject: keymap: Do not use vim_isIDc in keymap.c Note: there are three changes to ascii_isident. Reverting first two (in find_special_key and first in get_special_key_code) normally fails the new test with empty &isident, but reverting the third does not. Hence adding `>` to &isident. Ref vim/vim#2389. --- src/nvim/ascii.h | 13 +++++++++++++ src/nvim/keymap.c | 6 +++--- src/nvim/viml/parser/expressions.c | 9 +++------ 3 files changed, 19 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/nvim/ascii.h b/src/nvim/ascii.h index adde91f9ec..9ccb70764d 100644 --- a/src/nvim/ascii.h +++ b/src/nvim/ascii.h @@ -3,6 +3,7 @@ #include +#include "nvim/macros.h" #include "nvim/func_attr.h" #include "nvim/os/os_defs.h" @@ -98,6 +99,10 @@ static inline bool ascii_isxdigit(int) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; +static inline bool ascii_isident(int) + REAL_FATTR_CONST + REAL_FATTR_ALWAYS_INLINE; + static inline bool ascii_isbdigit(int) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; @@ -138,6 +143,14 @@ static inline bool ascii_isxdigit(int c) || (c >= 'A' && c <= 'F'); } +/// Checks if `c` is an “identifier” character +/// +/// That is, whether it is alphanumeric character or underscore. +static inline bool ascii_isident(const int c) +{ + return ASCII_ISALNUM(c) || c == '_'; +} + /// Checks if `c` is a binary digit, that is, 0-1. /// /// @see {ascii_isdigit} diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index aca21c20a5..0eb5a41b1e 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -567,7 +567,7 @@ int find_special_key(const char_u **srcp, const size_t src_len, int *const modp, // Find end of modifier list last_dash = src; - for (bp = src + 1; bp <= end && (*bp == '-' || vim_isIDc(*bp)); bp++) { + for (bp = src + 1; bp <= end && (*bp == '-' || ascii_isident(*bp)); bp++) { if (*bp == '-') { last_dash = bp; if (bp + 1 <= end) { @@ -721,12 +721,12 @@ int get_special_key_code(const char_u *name) for (int i = 0; key_names_table[i].name != NULL; i++) { const char *const table_name = key_names_table[i].name; int j; - for (j = 0; vim_isIDc(name[j]) && table_name[j] != NUL; j++) { + for (j = 0; ascii_isident(name[j]) && table_name[j] != NUL; j++) { if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC(name[j])) { break; } } - if (!vim_isIDc(name[j]) && table_name[j] == NUL) { + if (!ascii_isident(name[j]) && table_name[j] == NUL) { return key_names_table[i].key; } } diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 9773e60bbd..cfcde6bb38 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -386,13 +386,11 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) } #define ISWORD_OR_AUTOLOAD(x) \ - (ASCII_ISALNUM(x) || (x) == AUTOLOAD_CHAR || (x) == '_') -#define ISWORD(x) \ - (ASCII_ISALNUM(x) || (x) == '_') + (ascii_isident(x) || (x) == AUTOLOAD_CHAR) // Environment variable. case '$': { - CHARREG(kExprLexEnv, ISWORD); + CHARREG(kExprLexEnv, ascii_isident); break; } @@ -408,7 +406,7 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) case '_': { ret.data.var.scope = 0; ret.data.var.autoload = false; - CHARREG(kExprLexPlainIdentifier, ISWORD); + CHARREG(kExprLexPlainIdentifier, ascii_isident); // "is" and "isnot" operators. if (!(flags & kELFlagIsNotCmp) && ((ret.len == 2 && memcmp(pline.data, "is", 2) == 0) @@ -445,7 +443,6 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) break; } -#undef ISWORD #undef ISWORD_OR_AUTOLOAD #undef CHARREG -- cgit From 5ab0f988caffad5e8c87a075cbd3f91f0f7e002c Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 30 Nov 2017 11:53:25 +0300 Subject: *: Replace all occurrences of NVim with Nvim --- src/nvim/syntax.c | 364 ++++++++++++++++++------------------- src/nvim/viml/parser/expressions.c | 4 +- 2 files changed, 184 insertions(+), 184 deletions(-) (limited to 'src') diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 55ff49d160..d1a5f0bd1c 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -6025,204 +6025,204 @@ const char *const highlight_init_cmdline[] = { // XXX When modifying a list modify it in both valid and invalid halfs. // TODO(ZyX-I): merge valid and invalid groups via a macros. - // NVimInternalError should appear only when highlighter has a bug. - "NVimInternalError ctermfg=Red ctermbg=Red guifg=Red guibg=Red", + // NvimInternalError should appear only when highlighter has a bug. + "NvimInternalError ctermfg=Red ctermbg=Red guifg=Red guibg=Red", // Highlight groups (links) used by parser: - "default link NVimAssignment Operator", - "default link NVimPlainAssignment NVimAssignment", - "default link NVimAugmentedAssignment NVimAssignment", - "default link NVimAssignmentWithAddition NVimAugmentedAssignment", - "default link NVimAssignmentWithSubtraction NVimAugmentedAssignment", - "default link NVimAssignmentWithConcatenation NVimAugmentedAssignment", - - "default link NVimOperator Operator", - - "default link NVimUnaryOperator NVimOperator", - "default link NVimUnaryPlus NVimUnaryOperator", - "default link NVimUnaryMinus NVimUnaryOperator", - "default link NVimNot NVimUnaryOperator", - - "default link NVimBinaryOperator NVimOperator", - "default link NVimComparison NVimBinaryOperator", - "default link NVimComparisonModifier NVimComparison", - "default link NVimBinaryPlus NVimBinaryOperator", - "default link NVimBinaryMinus NVimBinaryOperator", - "default link NVimConcat NVimBinaryOperator", - "default link NVimConcatOrSubscript NVimConcat", - "default link NVimOr NVimBinaryOperator", - "default link NVimAnd NVimBinaryOperator", - "default link NVimMultiplication NVimBinaryOperator", - "default link NVimDivision NVimBinaryOperator", - "default link NVimMod NVimBinaryOperator", - - "default link NVimTernary NVimOperator", - "default link NVimTernaryColon NVimTernary", - - "default link NVimParenthesis Delimiter", - "default link NVimLambda NVimParenthesis", - "default link NVimNestingParenthesis NVimParenthesis", - "default link NVimCallingParenthesis NVimParenthesis", - - "default link NVimSubscript NVimParenthesis", - "default link NVimSubscriptBracket NVimSubscript", - "default link NVimSubscriptColon NVimSubscript", - "default link NVimCurly NVimSubscript", - - "default link NVimContainer NVimParenthesis", - "default link NVimDict NVimContainer", - "default link NVimList NVimContainer", - - "default link NVimIdentifier Identifier", - "default link NVimIdentifierScope NVimIdentifier", - "default link NVimIdentifierScopeDelimiter NVimIdentifier", - "default link NVimIdentifierName NVimIdentifier", - "default link NVimIdentifierKey NVimIdentifier", - - "default link NVimColon Delimiter", - "default link NVimComma Delimiter", - "default link NVimArrow Delimiter", - - "default link NVimRegister SpecialChar", - "default link NVimNumber Number", - "default link NVimFloat NVimNumber", - "default link NVimNumberPrefix Type", - - "default link NVimOptionSigil Type", - "default link NVimOptionName NVimIdentifier", - "default link NVimOptionScope NVimIdentifierScope", - "default link NVimOptionScopeDelimiter NVimIdentifierScopeDelimiter", - - "default link NVimEnvironmentSigil NVimOptionSigil", - "default link NVimEnvironmentName NVimIdentifier", - - "default link NVimString String", - "default link NVimStringBody NVimString", - "default link NVimStringQuote NVimString", - "default link NVimStringSpecial SpecialChar", - - "default link NVimSingleQuote NVimStringQuote", - "default link NVimSingleQuotedBody NVimStringBody", - "default link NVimSingleQuotedQuote NVimStringSpecial", - - "default link NVimDoubleQuote NVimStringQuote", - "default link NVimDoubleQuotedBody NVimStringBody", - "default link NVimDoubleQuotedEscape NVimStringSpecial", - - "default link NVimFigureBrace NVimInternalError", - "default link NVimSingleQuotedUnknownEscape NVimInternalError", - - "default link NVimSpacing Normal", - - // NVimInvalid groups: - - "default link NVimInvalidSingleQuotedUnknownEscape NVimInternalError", - - "default link NVimInvalid Error", - - "default link NVimInvalidAssignment NVimInvalid", - "default link NVimInvalidPlainAssignment NVimInvalidAssignment", - "default link NVimInvalidAugmentedAssignment NVimInvalidAssignment", - "default link NVimInvalidAssignmentWithAddition " - "NVimInvalidAugmentedAssignment", - "default link NVimInvalidAssignmentWithSubtraction " - "NVimInvalidAugmentedAssignment", - "default link NVimInvalidAssignmentWithConcatenation " - "NVimInvalidAugmentedAssignment", - - "default link NVimInvalidOperator NVimInvalid", - - "default link NVimInvalidUnaryOperator NVimInvalidOperator", - "default link NVimInvalidUnaryPlus NVimInvalidUnaryOperator", - "default link NVimInvalidUnaryMinus NVimInvalidUnaryOperator", - "default link NVimInvalidNot NVimInvalidUnaryOperator", - - "default link NVimInvalidBinaryOperator NVimInvalidOperator", - "default link NVimInvalidComparison NVimInvalidBinaryOperator", - "default link NVimInvalidComparisonModifier NVimInvalidComparison", - "default link NVimInvalidBinaryPlus NVimInvalidBinaryOperator", - "default link NVimInvalidBinaryMinus NVimInvalidBinaryOperator", - "default link NVimInvalidConcat NVimInvalidBinaryOperator", - "default link NVimInvalidConcatOrSubscript NVimInvalidConcat", - "default link NVimInvalidOr NVimInvalidBinaryOperator", - "default link NVimInvalidAnd NVimInvalidBinaryOperator", - "default link NVimInvalidMultiplication NVimInvalidBinaryOperator", - "default link NVimInvalidDivision NVimInvalidBinaryOperator", - "default link NVimInvalidMod NVimInvalidBinaryOperator", - - "default link NVimInvalidTernary NVimInvalidOperator", - "default link NVimInvalidTernaryColon NVimInvalidTernary", - - "default link NVimInvalidDelimiter NVimInvalid", - - "default link NVimInvalidParenthesis NVimInvalidDelimiter", - "default link NVimInvalidLambda NVimInvalidParenthesis", - "default link NVimInvalidNestingParenthesis NVimInvalidParenthesis", - "default link NVimInvalidCallingParenthesis NVimInvalidParenthesis", - - "default link NVimInvalidSubscript NVimInvalidParenthesis", - "default link NVimInvalidSubscriptBracket NVimInvalidSubscript", - "default link NVimInvalidSubscriptColon NVimInvalidSubscript", - "default link NVimInvalidCurly NVimInvalidSubscript", - - "default link NVimInvalidContainer NVimInvalidParenthesis", - "default link NVimInvalidDict NVimInvalidContainer", - "default link NVimInvalidList NVimInvalidContainer", - - "default link NVimInvalidValue NVimInvalid", - - "default link NVimInvalidIdentifier NVimInvalidValue", - "default link NVimInvalidIdentifierScope NVimInvalidIdentifier", - "default link NVimInvalidIdentifierScopeDelimiter NVimInvalidIdentifier", - "default link NVimInvalidIdentifierName NVimInvalidIdentifier", - "default link NVimInvalidIdentifierKey NVimInvalidIdentifier", - - "default link NVimInvalidColon NVimInvalidDelimiter", - "default link NVimInvalidComma NVimInvalidDelimiter", - "default link NVimInvalidArrow NVimInvalidDelimiter", - - "default link NVimInvalidRegister NVimInvalidValue", - "default link NVimInvalidNumber NVimInvalidValue", - "default link NVimInvalidFloat NVimInvalidNumber", - "default link NVimInvalidNumberPrefix NVimInvalidNumber", - - "default link NVimInvalidOptionSigil NVimInvalidIdentifier", - "default link NVimInvalidOptionName NVimInvalidIdentifier", - "default link NVimInvalidOptionScope NVimInvalidIdentifierScope", - "default link NVimInvalidOptionScopeDelimiter " - "NVimInvalidIdentifierScopeDelimiter", - - "default link NVimInvalidEnvironmentSigil NVimInvalidOptionSigil", - "default link NVimInvalidEnvironmentName NVimInvalidIdentifier", + "default link NvimAssignment Operator", + "default link NvimPlainAssignment NvimAssignment", + "default link NvimAugmentedAssignment NvimAssignment", + "default link NvimAssignmentWithAddition NvimAugmentedAssignment", + "default link NvimAssignmentWithSubtraction NvimAugmentedAssignment", + "default link NvimAssignmentWithConcatenation NvimAugmentedAssignment", + + "default link NvimOperator Operator", + + "default link NvimUnaryOperator NvimOperator", + "default link NvimUnaryPlus NvimUnaryOperator", + "default link NvimUnaryMinus NvimUnaryOperator", + "default link NvimNot NvimUnaryOperator", + + "default link NvimBinaryOperator NvimOperator", + "default link NvimComparison NvimBinaryOperator", + "default link NvimComparisonModifier NvimComparison", + "default link NvimBinaryPlus NvimBinaryOperator", + "default link NvimBinaryMinus NvimBinaryOperator", + "default link NvimConcat NvimBinaryOperator", + "default link NvimConcatOrSubscript NvimConcat", + "default link NvimOr NvimBinaryOperator", + "default link NvimAnd NvimBinaryOperator", + "default link NvimMultiplication NvimBinaryOperator", + "default link NvimDivision NvimBinaryOperator", + "default link NvimMod NvimBinaryOperator", + + "default link NvimTernary NvimOperator", + "default link NvimTernaryColon NvimTernary", + + "default link NvimParenthesis Delimiter", + "default link NvimLambda NvimParenthesis", + "default link NvimNestingParenthesis NvimParenthesis", + "default link NvimCallingParenthesis NvimParenthesis", + + "default link NvimSubscript NvimParenthesis", + "default link NvimSubscriptBracket NvimSubscript", + "default link NvimSubscriptColon NvimSubscript", + "default link NvimCurly NvimSubscript", + + "default link NvimContainer NvimParenthesis", + "default link NvimDict NvimContainer", + "default link NvimList NvimContainer", + + "default link NvimIdentifier Identifier", + "default link NvimIdentifierScope NvimIdentifier", + "default link NvimIdentifierScopeDelimiter NvimIdentifier", + "default link NvimIdentifierName NvimIdentifier", + "default link NvimIdentifierKey NvimIdentifier", + + "default link NvimColon Delimiter", + "default link NvimComma Delimiter", + "default link NvimArrow Delimiter", + + "default link NvimRegister SpecialChar", + "default link NvimNumber Number", + "default link NvimFloat NvimNumber", + "default link NvimNumberPrefix Type", + + "default link NvimOptionSigil Type", + "default link NvimOptionName NvimIdentifier", + "default link NvimOptionScope NvimIdentifierScope", + "default link NvimOptionScopeDelimiter NvimIdentifierScopeDelimiter", + + "default link NvimEnvironmentSigil NvimOptionSigil", + "default link NvimEnvironmentName NvimIdentifier", + + "default link NvimString String", + "default link NvimStringBody NvimString", + "default link NvimStringQuote NvimString", + "default link NvimStringSpecial SpecialChar", + + "default link NvimSingleQuote NvimStringQuote", + "default link NvimSingleQuotedBody NvimStringBody", + "default link NvimSingleQuotedQuote NvimStringSpecial", + + "default link NvimDoubleQuote NvimStringQuote", + "default link NvimDoubleQuotedBody NvimStringBody", + "default link NvimDoubleQuotedEscape NvimStringSpecial", + + "default link NvimFigureBrace NvimInternalError", + "default link NvimSingleQuotedUnknownEscape NvimInternalError", + + "default link NvimSpacing Normal", + + // NvimInvalid groups: + + "default link NvimInvalidSingleQuotedUnknownEscape NvimInternalError", + + "default link NvimInvalid Error", + + "default link NvimInvalidAssignment NvimInvalid", + "default link NvimInvalidPlainAssignment NvimInvalidAssignment", + "default link NvimInvalidAugmentedAssignment NvimInvalidAssignment", + "default link NvimInvalidAssignmentWithAddition " + "NvimInvalidAugmentedAssignment", + "default link NvimInvalidAssignmentWithSubtraction " + "NvimInvalidAugmentedAssignment", + "default link NvimInvalidAssignmentWithConcatenation " + "NvimInvalidAugmentedAssignment", + + "default link NvimInvalidOperator NvimInvalid", + + "default link NvimInvalidUnaryOperator NvimInvalidOperator", + "default link NvimInvalidUnaryPlus NvimInvalidUnaryOperator", + "default link NvimInvalidUnaryMinus NvimInvalidUnaryOperator", + "default link NvimInvalidNot NvimInvalidUnaryOperator", + + "default link NvimInvalidBinaryOperator NvimInvalidOperator", + "default link NvimInvalidComparison NvimInvalidBinaryOperator", + "default link NvimInvalidComparisonModifier NvimInvalidComparison", + "default link NvimInvalidBinaryPlus NvimInvalidBinaryOperator", + "default link NvimInvalidBinaryMinus NvimInvalidBinaryOperator", + "default link NvimInvalidConcat NvimInvalidBinaryOperator", + "default link NvimInvalidConcatOrSubscript NvimInvalidConcat", + "default link NvimInvalidOr NvimInvalidBinaryOperator", + "default link NvimInvalidAnd NvimInvalidBinaryOperator", + "default link NvimInvalidMultiplication NvimInvalidBinaryOperator", + "default link NvimInvalidDivision NvimInvalidBinaryOperator", + "default link NvimInvalidMod NvimInvalidBinaryOperator", + + "default link NvimInvalidTernary NvimInvalidOperator", + "default link NvimInvalidTernaryColon NvimInvalidTernary", + + "default link NvimInvalidDelimiter NvimInvalid", + + "default link NvimInvalidParenthesis NvimInvalidDelimiter", + "default link NvimInvalidLambda NvimInvalidParenthesis", + "default link NvimInvalidNestingParenthesis NvimInvalidParenthesis", + "default link NvimInvalidCallingParenthesis NvimInvalidParenthesis", + + "default link NvimInvalidSubscript NvimInvalidParenthesis", + "default link NvimInvalidSubscriptBracket NvimInvalidSubscript", + "default link NvimInvalidSubscriptColon NvimInvalidSubscript", + "default link NvimInvalidCurly NvimInvalidSubscript", + + "default link NvimInvalidContainer NvimInvalidParenthesis", + "default link NvimInvalidDict NvimInvalidContainer", + "default link NvimInvalidList NvimInvalidContainer", + + "default link NvimInvalidValue NvimInvalid", + + "default link NvimInvalidIdentifier NvimInvalidValue", + "default link NvimInvalidIdentifierScope NvimInvalidIdentifier", + "default link NvimInvalidIdentifierScopeDelimiter NvimInvalidIdentifier", + "default link NvimInvalidIdentifierName NvimInvalidIdentifier", + "default link NvimInvalidIdentifierKey NvimInvalidIdentifier", + + "default link NvimInvalidColon NvimInvalidDelimiter", + "default link NvimInvalidComma NvimInvalidDelimiter", + "default link NvimInvalidArrow NvimInvalidDelimiter", + + "default link NvimInvalidRegister NvimInvalidValue", + "default link NvimInvalidNumber NvimInvalidValue", + "default link NvimInvalidFloat NvimInvalidNumber", + "default link NvimInvalidNumberPrefix NvimInvalidNumber", + + "default link NvimInvalidOptionSigil NvimInvalidIdentifier", + "default link NvimInvalidOptionName NvimInvalidIdentifier", + "default link NvimInvalidOptionScope NvimInvalidIdentifierScope", + "default link NvimInvalidOptionScopeDelimiter " + "NvimInvalidIdentifierScopeDelimiter", + + "default link NvimInvalidEnvironmentSigil NvimInvalidOptionSigil", + "default link NvimInvalidEnvironmentName NvimInvalidIdentifier", // Invalid string bodies and specials are still highlighted as valid ones to // minimize the red area. - "default link NVimInvalidString NVimInvalidValue", - "default link NVimInvalidStringBody NVimStringBody", - "default link NVimInvalidStringQuote NVimInvalidString", - "default link NVimInvalidStringSpecial NVimStringSpecial", + "default link NvimInvalidString NvimInvalidValue", + "default link NvimInvalidStringBody NvimStringBody", + "default link NvimInvalidStringQuote NvimInvalidString", + "default link NvimInvalidStringSpecial NvimStringSpecial", - "default link NVimInvalidSingleQuote NVimInvalidStringQuote", - "default link NVimInvalidSingleQuotedBody NVimInvalidStringBody", - "default link NVimInvalidSingleQuotedQuote NVimInvalidStringSpecial", + "default link NvimInvalidSingleQuote NvimInvalidStringQuote", + "default link NvimInvalidSingleQuotedBody NvimInvalidStringBody", + "default link NvimInvalidSingleQuotedQuote NvimInvalidStringSpecial", - "default link NVimInvalidDoubleQuote NVimInvalidStringQuote", - "default link NVimInvalidDoubleQuotedBody NVimInvalidStringBody", - "default link NVimInvalidDoubleQuotedEscape NVimInvalidStringSpecial", - "default link NVimInvalidDoubleQuotedUnknownEscape NVimInvalidValue", + "default link NvimInvalidDoubleQuote NvimInvalidStringQuote", + "default link NvimInvalidDoubleQuotedBody NvimInvalidStringBody", + "default link NvimInvalidDoubleQuotedEscape NvimInvalidStringSpecial", + "default link NvimInvalidDoubleQuotedUnknownEscape NvimInvalidValue", - "default link NVimInvalidFigureBrace NVimInvalidDelimiter", + "default link NvimInvalidFigureBrace NvimInvalidDelimiter", - "default link NVimInvalidSpacing ErrorMsg", + "default link NvimInvalidSpacing ErrorMsg", // Not actually invalid, but we highlight user that he is doing something // wrong. - "default link NVimDoubleQuotedUnknownEscape NVimInvalidValue", + "default link NvimDoubleQuotedUnknownEscape NvimInvalidValue", NULL, }; -/// Create default links for NVim* highlight groups used for cmdline coloring +/// Create default links for Nvim* highlight groups used for cmdline coloring void syn_init_cmdline_highlight(bool reset, bool init) { for (size_t i = 0 ; highlight_init_cmdline[i] != NULL ; i++) { diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index cfcde6bb38..4196ecb9d2 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -1326,7 +1326,7 @@ static inline ParserPosition recol_pos(const ParserPosition pos, } /// Get highlight group name -#define HL(g) (is_invalid ? "NVimInvalid" #g : "NVim" #g) +#define HL(g) (is_invalid ? "NvimInvalid" #g : "Nvim" #g) /// Highlight current token with the given group #define HL_CUR_TOKEN(g) \ @@ -2570,7 +2570,7 @@ viml_pexpr_parse_bracket_closing_error: new_top_node, _("E15: Don't know what figure brace means: %.*s")); if (pstate->colors) { - // Will reset to NVimInvalidFigureBrace. + // Will reset to NvimInvalidFigureBrace. kv_A(*pstate->colors, new_top_node->data.fig.opening_hl_idx).group = ( HL(FigureBrace)); -- cgit From 62993323494d846190590606d37aff1136421671 Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 1 Dec 2017 00:34:16 +0300 Subject: fix! set lsan options --- src/nvim/eval/encode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index ef647b3ee4..650f11a989 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -316,7 +316,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, #define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ do { \ const float_T flt_ = (flt); \ - switch (fpclassify(flt_)) { \ + switch (fpclassify((double)flt_)) { \ case FP_NAN: { \ ga_concat(gap, (char_u *) "str2float('nan')"); \ break; \ -- cgit From 6bc54832efcb8c269875dfa4eecd89e1984ead69 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 3 Dec 2017 16:53:29 +0300 Subject: Revert "fix! set lsan options" This reverts commit 62993323494d846190590606d37aff1136421671. --- src/nvim/eval/encode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 650f11a989..ef647b3ee4 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -316,7 +316,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, #define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ do { \ const float_T flt_ = (flt); \ - switch (fpclassify((double)flt_)) { \ + switch (fpclassify(flt_)) { \ case FP_NAN: { \ ga_concat(gap, (char_u *) "str2float('nan')"); \ break; \ -- cgit From d763d2fe7aae1c531c72c1d542cad2b19719929b Mon Sep 17 00:00:00 2001 From: FlorianGit Date: Thu, 9 Nov 2017 21:31:17 +0100 Subject: Viml: Make filter and map handle null list correct filter('v:_null_list, 'v:val') should return v:_null_list and a similar statement should hold for map. Changes after review * Test inserted in legacy test suite has been removed by reverting the commit adding it. * Change the fix to tv_copy the argument before returning. * Readd the two tests on crashes, and modified their expected return value. * Move the test from 'incorrect behaviour' section to 'correct behaviour section' * Add analogous tests for v:_null_dict Always copy list or dictionary to return variable If the type of input is correct (i.e. either a list or a dictionary), this should also be returned. --- src/nvim/eval.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 577aa67c60..56aedb1b4e 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -8457,11 +8457,13 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) int idx = 0; if (argvars[0].v_type == VAR_LIST) { + tv_copy(&argvars[0], rettv); if ((l = argvars[0].vval.v_list) == NULL || (!map && tv_check_lock(l->lv_lock, arg_errmsg, TV_TRANSLATE))) { return; } } else if (argvars[0].v_type == VAR_DICT) { + tv_copy(&argvars[0], rettv); if ((d = argvars[0].vval.v_dict) == NULL || (!map && tv_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) { return; @@ -8542,8 +8544,6 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) did_emsg |= save_did_emsg; } - - tv_copy(&argvars[0], rettv); } static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp) -- cgit From 3aa24042a8f7d591b8091b437fc9cdb03497bc7a Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 27 Nov 2017 00:35:09 +0100 Subject: tui: dump termcap info if -V3 ('verbose' >= 3) Get terminal debugging info by starting Nvim with 'verbose' level 3: nvim -V3log This is like Vim's `:set termcap`, which was removed in Nvim (and would be very awkward to restore because of the decoupled UI). --- src/nvim/README.md | 35 +++++++++++++++++++- src/nvim/option.c | 11 +++---- src/nvim/tui/terminfo.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++++ src/nvim/tui/tui.c | 21 ++++++++++-- src/nvim/ui_bridge.c | 3 +- 5 files changed, 147 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/nvim/README.md b/src/nvim/README.md index 0caf71e2c5..da87a0208e 100644 --- a/src/nvim/README.md +++ b/src/nvim/README.md @@ -32,6 +32,39 @@ The source files use extensions to hint about their purpose. - `*.h.generated.h` - exported functions’ declarations. - `*.c.generated.h` - static functions’ declarations. +TUI debugging +------------- + +### TUI troubleshoot + +Nvim logs its internal terminfo state at 'verbose' level 3. This makes it +possible to see exactly what terminfo values Nvim is using on any system. + + nvim -V3log + +### TUI trace + +The ancient `script` command is still the "state of the art" for tracing +terminal behavior. The libvterm `vterm-dump` utility formats the result for +human-readability. + +Record a Nvim terminal session and format it with `vterm-dump`: + + script foo + ./build/bin/nvim -u NONE + # Exit the script session with CTRL-d + + # Use `vterm-dump` utility to format the result. + ./.deps/usr/bin/vterm-dump foo > bar + +Then you can compare `bar` with another session, to debug TUI behavior. + +### Terminal reference + +- `man terminfo` +- http://bazaar.launchpad.net/~libvterm/libvterm/trunk/view/head:/doc/seqs.txt +- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + Nvim lifecycle -------------- @@ -39,7 +72,7 @@ Following describes how Nvim processes input. Consider a typical Vim-like editing session: -01. Vim dispays the welcome screen +01. Vim displays the welcome screen 02. User types: `:` 03. Vim enters command-line mode 04. User types: `edit README.txt` diff --git a/src/nvim/option.c b/src/nvim/option.c index 913d27d508..37c4233142 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4906,15 +4906,14 @@ showoptions ( vimoption_T **items = xmalloc(sizeof(vimoption_T *) * PARAM_COUNT); - /* Highlight title */ - if (all == 2) - MSG_PUTS_TITLE(_("\n--- Terminal codes ---")); - else if (opt_flags & OPT_GLOBAL) + // Highlight title + if (opt_flags & OPT_GLOBAL) { MSG_PUTS_TITLE(_("\n--- Global option values ---")); - else if (opt_flags & OPT_LOCAL) + } else if (opt_flags & OPT_LOCAL) { MSG_PUTS_TITLE(_("\n--- Local option values ---")); - else + } else { MSG_PUTS_TITLE(_("\n--- Options ---")); + } /* * do the loop two times: diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c index fdc33f0a77..492c1c5e9c 100644 --- a/src/nvim/tui/terminfo.c +++ b/src/nvim/tui/terminfo.c @@ -9,7 +9,10 @@ #include #include "nvim/log.h" +#include "nvim/globals.h" #include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/option.h" #include "nvim/tui/terminfo.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -166,3 +169,87 @@ unibi_term *terminfo_from_builtin(const char *term, char **termname) unibi_set_bool(ut, unibi_back_color_erase, false); return ut; } + +/// Dumps termcap info to the messages area. +/// Serves a similar purpose as Vim `:set termcap` (removed in Nvim). +/// +/// @note adapted from unibilium unibi-dump.c +void terminfo_info_msg(const unibi_term *const ut) +{ + if (exiting) { + return; + } + msg_puts_title("\n\n--- Terminal info --- {{{\n"); + + char *term; + get_tty_option("term", &term); + msg_printf_attr(0, "&term: %s\n", term); + msg_printf_attr(0, "Description: %s\n", unibi_get_name(ut)); + const char **a = unibi_get_aliases(ut); + if (*a) { + msg_puts("Aliases: "); + do { + msg_printf_attr(0, "%s%s\n", *a, a[1] ? " | " : ""); + a++; + } while (*a); + } + + msg_puts("Boolean capabilities:\n"); + for (enum unibi_boolean i = unibi_boolean_begin_ + 1; + i < unibi_boolean_end_; i++) { + msg_printf_attr(0, " %-25s %-10s = %s\n", unibi_name_bool(i), + unibi_short_name_bool(i), + unibi_get_bool(ut, i) ? "true" : "false"); + } + + msg_puts("Numeric capabilities:\n"); + for (enum unibi_numeric i = unibi_numeric_begin_ + 1; + i < unibi_numeric_end_; i++) { + int n = unibi_get_num(ut, i); // -1 means "empty" + msg_printf_attr(0, " %-25s %-10s = %hd\n", unibi_name_num(i), + unibi_short_name_num(i), n); + } + + msg_puts("String capabilities:\n"); + for (enum unibi_string i = unibi_string_begin_ + 1; + i < unibi_string_end_; i++) { + const char *s = unibi_get_str(ut, i); + if (s) { + msg_printf_attr(0, " %-25s %-10s = ", unibi_name_str(i), + unibi_short_name_str(i)); + // Most of these strings will contain escape sequences. + msg_outtrans_special((char_u *)s, false); + msg_putchar('\n'); + } + } + + if (unibi_count_ext_bool(ut)) { + msg_puts("Extended boolean capabilities:\n"); + for (size_t i = 0; i < unibi_count_ext_bool(ut); i++) { + msg_printf_attr(0, " %-25s = %s\n", + unibi_get_ext_bool_name(ut, i), + unibi_get_ext_bool(ut, i) ? "true" : "false"); + } + } + + if (unibi_count_ext_num(ut)) { + msg_puts("Extended numeric capabilities:\n"); + for (size_t i = 0; i < unibi_count_ext_num(ut); i++) { + msg_printf_attr(0, " %-25s = %hd\n", + unibi_get_ext_num_name(ut, i), + unibi_get_ext_num(ut, i)); + } + } + + if (unibi_count_ext_str(ut)) { + msg_puts("Extended string capabilities:\n"); + for (size_t i = 0; i < unibi_count_ext_str(ut); i++) { + msg_printf_attr(0, " %-25s = ", unibi_get_ext_str_name(ut, i)); + msg_outtrans_special((char_u *)unibi_get_ext_str(ut, i), false); + msg_putchar('\n'); + } + } + + msg_puts("}}}\n"); + xfree(term); +} diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 2436295ad4..739f3d18d2 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -354,10 +354,12 @@ static void tui_main(UIBridgeData *bridge, UI *ui) tui_terminal_start(ui); data->stop = false; - // allow the main thread to continue, we are ready to start handling UI - // callbacks + // Allow main thread to continue, we are ready to handle UI callbacks. CONTINUE(bridge); + loop_schedule_deferred(&main_loop, + event_create(show_termcap_event, 1, data->ut)); + while (!data->stop) { loop_poll_events(&tui_loop, -1); // tui_loop.events is never processed } @@ -1061,6 +1063,21 @@ static void tui_flush(UI *ui) flush_buf(ui, true); } +/// Dumps termcap info to the messages area, if 'verbose' >= 3. +static void show_termcap_event(void **argv) +{ + if (p_verbose < 3) { + return; + } + const unibi_term *const ut = argv[0]; + if (!ut) { + abort(); + } + // XXX: (future) if unibi_term is modified (e.g. after a terminal + // query-response) this is a race condition. + terminfo_info_msg(ut); +} + #ifdef UNIX static void suspend_event(void **argv) { diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c index 5585886612..7573fa1653 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -82,6 +82,7 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler) abort(); } + // Suspend the main thread until CONTINUE is called by the UI thread. while (!rv->ready) { uv_cond_wait(&rv->cond, &rv->mutex); } @@ -149,7 +150,7 @@ static void ui_bridge_suspend(UI *b) uv_mutex_lock(&data->mutex); UI_BRIDGE_CALL(b, suspend, 1, b); data->ready = false; - // suspend the main thread until CONTINUE is called by the UI thread + // Suspend the main thread until CONTINUE is called by the UI thread. while (!data->ready) { uv_cond_wait(&data->cond, &data->mutex); } -- cgit From 9d689d639d71ab3d60cb88544619b8cef9510217 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 29 Nov 2017 03:01:16 +0100 Subject: tui: set descriptions on termcap extensions --- src/nvim/tui/tui.c | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 739f3d18d2..2cdd1979af 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1594,11 +1594,13 @@ static void augment_terminfo(TUIData *data, const char *term, || konsole // per commentary in VT102Emulation.cpp || teraterm // per TeraTerm "Supported Control Functions" doco || rxvt) { // per command.C - data->unibi_ext.resize_screen = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.resize_screen = (int)unibi_add_ext_str(ut, + "ext.resize_screen", "\x1b[8;%p1%d;%p2%dt"); } if (putty || xterm || rxvt) { - data->unibi_ext.reset_scroll_region = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.reset_scroll_region = (int)unibi_add_ext_str(ut, + "ext.reset_scroll_region", "\x1b[r"); } @@ -1656,21 +1658,29 @@ static void augment_terminfo(TUIData *data, const char *term, /// Terminals usually ignore unrecognized private modes, and there is no /// known ambiguity with these. So we just set them unconditionally. - data->unibi_ext.enable_lr_margin = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.enable_lr_margin = (int)unibi_add_ext_str(ut, + "ext.enable_lr_margin", "\x1b[?69h"); - data->unibi_ext.disable_lr_margin = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.disable_lr_margin = (int)unibi_add_ext_str(ut, + "ext.disable_lr_margin", "\x1b[?69l"); - data->unibi_ext.enable_bracketed_paste = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.enable_bracketed_paste = (int)unibi_add_ext_str(ut, + "ext.enable_bpaste", "\x1b[?2004h"); - data->unibi_ext.disable_bracketed_paste = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.disable_bracketed_paste = (int)unibi_add_ext_str(ut, + "ext.disable_bpaste", "\x1b[?2004l"); - data->unibi_ext.enable_focus_reporting = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.enable_focus_reporting = (int)unibi_add_ext_str(ut, + "ext.enable_focus", rxvt ? "\x1b]777;focus;on\x7" : "\x1b[?1004h"); - data->unibi_ext.disable_focus_reporting = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.disable_focus_reporting = (int)unibi_add_ext_str(ut, + "ext.disable_focus", rxvt ? "\x1b]777;focus;off\x7" : "\x1b[?1004l"); - data->unibi_ext.enable_mouse = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.enable_mouse = (int)unibi_add_ext_str(ut, + "ext.enable_mouse", "\x1b[?1002h\x1b[?1006h"); - data->unibi_ext.disable_mouse = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.disable_mouse = (int)unibi_add_ext_str(ut, + "ext.disable_mouse", "\x1b[?1002l\x1b[?1006l"); } -- cgit From 586dafee2f4824b6cd4e0be80d597fbb810a647e Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 27 Nov 2017 10:20:06 +0100 Subject: str2specialbuf(): fix comparison regression by 832c158a663c --- src/nvim/message.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/message.c b/src/nvim/message.c index b90c475ede..806de5e116 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -1307,7 +1307,7 @@ char *str2special_save(const char *const str, const bool replace_spaces, return (char *)ga.ga_data; } -/// Convert character, replacing key one key code with printable representation +/// Convert character, replacing key with printable representation. /// /// @param[in,out] sp String to convert. Is advanced to the next key code. /// @param[in] replace_spaces Convert spaces into , normally used for @@ -1392,7 +1392,7 @@ void str2specialbuf(const char *sp, char *buf, size_t len) while (*sp) { const char *s = str2special(&sp, false, false); const size_t s_len = strlen(s); - if (s_len <= len) { + if (len <= s_len) { break; } memcpy(buf, s, s_len); -- cgit From 5de1eae4f2353de1dc4cdeb3e5bad0a908faf9c8 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 29 Nov 2017 02:54:19 +0100 Subject: msg_outtrans_special(): `const` some strings --- src/nvim/message.c | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/nvim/message.c b/src/nvim/message.c index 806de5e116..907a2472c8 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -1237,27 +1237,24 @@ void msg_make(char_u *arg) } } -/* - * Output the string 'str' upto a NUL character. - * Return the number of characters it takes on the screen. - * - * If K_SPECIAL is encountered, then it is taken in conjunction with the - * following character and shown as , etc. Any other character - * which is not printable shown in <> form. - * If 'from' is TRUE (lhs of a mapping), a space is shown as . - * If a character is displayed in one of these special ways, is also - * highlighted (its highlight name is '8' in the p_hl variable). - * Otherwise characters are not highlighted. - * This function is used to show mappings, where we want to see how to type - * the character/string -- webb - */ -int -msg_outtrans_special ( - char_u *strstart, - int from /* TRUE for lhs of a mapping */ +/// Output the string 'str' upto a NUL character. +/// Return the number of characters it takes on the screen. +/// +/// If K_SPECIAL is encountered, then it is taken in conjunction with the +/// following character and shown as , etc. Any other character +/// which is not printable shown in <> form. +/// If 'from' is TRUE (lhs of a mapping), a space is shown as . +/// If a character is displayed in one of these special ways, is also +/// highlighted (its highlight name is '8' in the p_hl variable). +/// Otherwise characters are not highlighted. +/// This function is used to show mappings, where we want to see how to type +/// the character/string -- webb +int msg_outtrans_special( + const char_u *strstart, + int from ///< true for LHS of a mapping ) { - char_u *str = strstart; + const char_u *str = strstart; int retval = 0; int attr; -- cgit From 1cae99b4bf6517d559ff222b968c314198923260 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 2 Dec 2017 18:55:54 +0100 Subject: msg_outtrans_special(): handle NULL This is convenient for terminfo_info_msg(). --- src/nvim/message.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/message.c b/src/nvim/message.c index 907a2472c8..5c8f0655bf 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -1254,11 +1254,13 @@ int msg_outtrans_special( int from ///< true for LHS of a mapping ) { + if (strstart == NULL) { + return 0; // Do nothing. + } const char_u *str = strstart; int retval = 0; - int attr; + int attr = hl_attr(HLF_8); - attr = hl_attr(HLF_8); while (*str != NUL) { const char *string; // Leading and trailing spaces need to be displayed in <> form. -- cgit From 2d4abc1caedf67487e100f5cef5eca78da68b3e7 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 4 Dec 2017 23:31:50 +0100 Subject: tui: flush -V3 ('verbose' >= 3) info ASAP --- src/nvim/tui/tui.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 2cdd1979af..9f2ae20fe3 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1073,9 +1073,12 @@ static void show_termcap_event(void **argv) if (!ut) { abort(); } + verbose_enter(); // XXX: (future) if unibi_term is modified (e.g. after a terminal // query-response) this is a race condition. terminfo_info_msg(ut); + verbose_leave(); + verbose_stop(); // flush now } #ifdef UNIX -- cgit From 90dd2b1473cf19a6467838dbae078eea4bdc3c61 Mon Sep 17 00:00:00 2001 From: Florian Larysch Date: Wed, 29 Nov 2017 18:43:02 +0100 Subject: tui: never flush buffers in midst of unibilium output e83845285 fixed an issue where long (true color) escape sequences got interrupted by the cursor visibility toggling caused by buffer flushes. cdfaecb25 introduces a new issue which causes similar problems: While the old buffer flushing code appended the cursor visibility escapes to the buffer before/after flushing, the new code effectively prepends the sequences. Assume the following sequence of events occurs: - A long escape code is issued using unibi_out when the buffer is almost full - out() gets called for a prefix of that escape code, causing the buffer to fill up - flush_buf(ui, false) is called and (correctly) does not insert any cursor toggling escapes - The rest of the escape code is written into the now empty buffer - At some later point, some other part of nvim calls flush_buf(ui, true), which then toggles the cursor, corrupting the escape code This could possibly also be fixed by tracking the state of the buffer (i.e. does it contain a partially output escape code?), but this seems fragile in the same way e83845285 turned out to be. The root cause for all these problems is the mismatch between nvim's (implicit) assumption that the buffer is flushable at any point in time and the non-atomicity of unibilium's character based callback interface. The proper fix (without modifying unibilium) is to ensure nvim's assumption about the buffer state holds at all times. To that end, add a "cork" flag which ensures one unibi_out-call never splits its output across a buffer flush; if an escape code does not fit into the current buffer, flush it without any part of the escape code in it and insert the whole escape code in the emptied buffer. This is a little more complex because it modifies the buffer in place rather than printing into another buffer, checking the remaining space in the terminal buffer and then memcpy'ing it. --- src/nvim/tui/tui.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 2436295ad4..d07eb9a923 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -96,6 +96,7 @@ typedef struct { bool immediate_wrap_after_last_column; bool mouse_enabled; bool busy, is_invisible; + bool cork, overflow; cursorentry_T cursor_shapes[SHAPE_IDX_COUNT]; HlAttrs print_attrs; bool default_attr; @@ -200,6 +201,8 @@ static void terminfo_start(UI *ui) data->default_attr = false; data->is_invisible = true; data->busy = false; + data->cork = false; + data->overflow = false; data->showing_mode = SHAPE_IDX_N; data->unibi_ext.enable_mouse = -1; data->unibi_ext.disable_mouse = -1; @@ -1219,8 +1222,18 @@ static void unibi_goto(UI *ui, int row, int col) } \ if (str) { \ unibi_var_t vars[26 + 26]; \ + size_t orig_pos = data->bufpos; \ + \ memset(&vars, 0, sizeof(vars)); \ + data->cork = true; \ +retry: \ unibi_format(vars, vars + 26, str, data->params, out, ui, NULL, NULL); \ + if (data->overflow) { \ + data->bufpos = orig_pos; \ + flush_buf(ui, true); \ + goto retry; \ + } \ + data->cork = false; \ } \ } while (0) static void unibi_out(UI *ui, int unibi_index) @@ -1239,8 +1252,17 @@ static void out(void *ctx, const char *str, size_t len) TUIData *data = ui->data; size_t available = sizeof(data->buf) - data->bufpos; + if (data->cork && data->overflow) { + return; + } + if (len > available) { - flush_buf(ui, false); + if (data->cork) { + data->overflow = true; + return; + } else { + flush_buf(ui, true); + } } memcpy(data->buf + data->bufpos, str, len); @@ -1696,6 +1718,7 @@ static void flush_buf(UI *ui, bool toggle_cursor) bufs, (unsigned)(bufp - bufs), NULL); uv_run(&data->write_loop, UV_RUN_DEFAULT); data->bufpos = 0; + data->overflow = false; } #if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18 -- cgit From 54087d80f3603165c0bdeab9f14c9aba2ddb6c67 Mon Sep 17 00:00:00 2001 From: Florian Larysch Date: Tue, 5 Dec 2017 21:18:30 +0100 Subject: tui: always hide cursor when flushing The previous commit ensures that we can never flush the buffer in a state where toggling cursor visibility can corrupt other escape codes. Thus, we can remove the workaround added as part of e838452, simplyfing the code and hiding the cursor on more occasions. --- src/nvim/tui/tui.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index d07eb9a923..61d3bde450 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -297,7 +297,7 @@ static void terminfo_stop(UI *ui) unibi_out_ext(ui, data->unibi_ext.disable_bracketed_paste); // Disable focus reporting unibi_out_ext(ui, data->unibi_ext.disable_focus_reporting); - flush_buf(ui, true); + flush_buf(ui); uv_tty_reset_mode(); uv_close((uv_handle_t *)&data->output_handle, NULL); uv_run(&data->write_loop, UV_RUN_DEFAULT); @@ -1061,7 +1061,7 @@ static void tui_flush(UI *ui) cursor_goto(ui, saved_row, saved_col); - flush_buf(ui, true); + flush_buf(ui); } #ifdef UNIX @@ -1230,7 +1230,7 @@ retry: \ unibi_format(vars, vars + 26, str, data->params, out, ui, NULL, NULL); \ if (data->overflow) { \ data->bufpos = orig_pos; \ - flush_buf(ui, true); \ + flush_buf(ui); \ goto retry; \ } \ data->cork = false; \ @@ -1261,7 +1261,7 @@ static void out(void *ctx, const char *str, size_t len) data->overflow = true; return; } else { - flush_buf(ui, true); + flush_buf(ui); } } @@ -1679,7 +1679,7 @@ static void augment_terminfo(TUIData *data, const char *term, "\x1b[?1002l\x1b[?1006l"); } -static void flush_buf(UI *ui, bool toggle_cursor) +static void flush_buf(UI *ui) { uv_write_t req; uv_buf_t bufs[3]; @@ -1690,7 +1690,7 @@ static void flush_buf(UI *ui, bool toggle_cursor) return; } - if (toggle_cursor && !data->is_invisible) { + if (!data->is_invisible) { // cursor is visible. Write a "cursor invisible" command before writing the // buffer. bufp->base = data->invis; @@ -1705,7 +1705,7 @@ static void flush_buf(UI *ui, bool toggle_cursor) bufp++; } - if (toggle_cursor && !data->busy && data->is_invisible) { + if (!data->busy && data->is_invisible) { // not busy and the cursor is invisible. Write a "cursor normal" command // after writing the buffer. bufp->base = data->norm; -- cgit From ba7d6a9e6b3c42f1996d05cec471b4842eeb8b66 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Tue, 5 Dec 2017 13:27:06 +0100 Subject: ui: fix glitch with both ext_cmdline and cmd_wildmenu --- src/nvim/ex_getln.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index c1500e3121..99330c177c 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -3455,8 +3455,10 @@ nextwild ( return FAIL; } - MSG_PUTS("..."); /* show that we are busy */ - ui_flush(); + if (!ui_is_external(kUIWildmenu)) { + MSG_PUTS("..."); // show that we are busy + ui_flush(); + } i = (int)(xp->xp_pattern - ccline.cmdbuff); xp->xp_pattern_len = ccline.cmdpos - i; -- cgit From 5cbd3b383c09539fed7a7c41f882996bf3d0f8ad Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 6 Dec 2017 10:42:26 +0100 Subject: tui: ignore st terminfo cursor shape (Se, Ss) entries closes #7641 --- src/nvim/tui/tui.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 9f2ae20fe3..99433924f6 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1480,19 +1480,18 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, } } - // Some terminals cannot be trusted to report DECSCUSR support. So we keep - // blacklist for when we should not trust the reported features. - if (!((vte_version != 0 && vte_version < 3900) || konsole)) { - // Dickey ncurses terminfo has included the Ss and Se capabilities, - // pioneered by tmux, since 2011-07-14. So adding them to terminal types, - // that do actually have such control sequences but lack the correct - // definitions in terminfo, is a fixup, not an augmentation. + // Blacklist of terminals that cannot be trusted to report DECSCUSR support. + if (!(st || (vte_version != 0 && vte_version < 3900) || konsole)) { data->unibi_ext.reset_cursor_style = unibi_find_ext_str(ut, "Se"); data->unibi_ext.set_cursor_style = unibi_find_ext_str(ut, "Ss"); } + + // Dickey ncurses terminfo includes Ss/Se capabilities since 2011-07-14. So + // adding them to terminal types, that have such control sequences but lack + // the correct terminfo entries, is a fixup, not an augmentation. if (-1 == data->unibi_ext.set_cursor_style) { - // The DECSCUSR sequence to change the cursor shape is widely supported by - // several terminal types. https://github.com/gnachman/iTerm2/pull/92 + // DECSCUSR (cursor shape) sequence is widely supported by several terminal + // types. https://github.com/gnachman/iTerm2/pull/92 // xterm extension: vertical bar if (!konsole && ((xterm && !vte_version) // anything claiming xterm compat // per MinTTY 0.4.3-1 release notes from 2009 @@ -1502,6 +1501,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, || tmux // per tmux manual page // https://lists.gnu.org/archive/html/screen-devel/2013-03/msg00000.html || screen + || st // #7641 || rxvt // per command.C // per analysis of VT100Terminal.m || iterm || iterm_pretending_xterm -- cgit From ddce5bca03067ce7adf70496a3493cf12c016364 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 00:08:44 +0300 Subject: eval/typval: Add functions useful for hiding list implementation --- src/nvim/eval/typval.h | 104 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) (limited to 'src') diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index c44b85644d..9caf3f7b60 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -284,6 +284,54 @@ typedef struct list_stack_S { #define TV_DICT_HI2DI(hi) \ ((dictitem_T *)((hi)->hi_key - offsetof(dictitem_T, di_key))) +static inline void tv_list_ref(list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Increase reference count for a given list +/// +/// Does nothing for NULL lists. +/// +/// @param[in] l List to modify. +static inline void tv_list_ref(list_T *const l) +{ + if (l == NULL) { + return; + } + l->lv_refcount++; +} + +static inline VarLockStatus tv_list_locked(const list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Get list lock status +/// +/// Returns VAR_FIXED for NULL lists. +/// +/// @param[in] l List to check. +static inline VarLockStatus tv_list_locked(const list_T *const l) +{ + if (l == NULL) { + return VAR_FIXED; + } + return l->lv_lock; +} + +/// Set list lock status +/// +/// May only “set” VAR_FIXED for NULL lists. +/// +/// @param[out] l List to modify. +/// @param[in] lock New lock status. +static inline void tv_list_set_lock(list_T *const l, + const VarLockStatus lock) +{ + if (l == NULL) { + assert(lock == VAR_FIXED); + return; + } + l->lv_lock = lock; +} + static inline long tv_list_len(const list_T *const l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; @@ -298,6 +346,22 @@ static inline long tv_list_len(const list_T *const l) return l->lv_len; } +static inline listitem_T *tv_list_first(const list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Get first list item +/// +/// @param[in] l List to get item from. +/// +/// @return List item or NULL in case of an empty list. +static inline listitem_T *tv_list_first(const list_T *const l) +{ + if (l == NULL) { + return NULL; + } + return l->lv_first; +} + static inline long tv_dict_len(const dict_T *const d) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; @@ -352,6 +416,46 @@ extern const char *const tv_empty_string; /// Specifies that free_unref_items() function has (not) been entered extern bool tv_in_free_unref_items; +/// Iterate over a list +/// +/// @param modifier Modifier: expected to be const or nothing, volatile should +/// also work if you have any uses for the volatile list. +/// @param[in] l List to iterate over. +/// @param li Name of the variable with current listitem_T entry. +/// @param code Cycle body. +#define _TV_LIST_ITER_MOD(modifier, l, li, code) \ + do { \ + modifier list_T *const l_ = (l); \ + if (l_ != NULL) { \ + for (modifier listitem_T *const li = l_->lv_first; \ + li != NULL; li = li->li_next) { \ + code \ + } \ + } \ + } while (0) + +/// Iterate over a list +/// +/// To be used when you need to modify list or values you iterate over, use +/// #TV_LIST_ITER_CONST if you don’t. +/// +/// @param[in] l List to iterate over. +/// @param li Name of the variable with current listitem_T entry. +/// @param code Cycle body. +#define TV_LIST_ITER(l, li, code) \ + _TV_LIST_ITER_MOD(, l, li, code) + +/// Iterate over a list +/// +/// To be used when you don’t need to modify list or values you iterate over, +/// use #TV_LIST_ITER if you do. +/// +/// @param[in] l List to iterate over. +/// @param li Name of the variable with current listitem_T entry. +/// @param code Cycle body. +#define TV_LIST_ITER_CONST(l, li, code) \ + _TV_LIST_ITER_MOD(const, l, li, code) + /// Iterate over a dictionary /// /// @param[in] d Dictionary to iterate over. -- cgit From 49dd615693c9f6d86a180371abcd09d32ac8aeca Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 00:20:05 +0300 Subject: eval/typval: Add macros useful for hiding list item implementation --- src/nvim/eval/typval.h | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'src') diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 9caf3f7b60..b00f43f625 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -456,6 +456,35 @@ extern bool tv_in_free_unref_items; #define TV_LIST_ITER_CONST(l, li, code) \ _TV_LIST_ITER_MOD(const, l, li, code) +// Below macros are macros to avoid duplicating code for functionally identical +// const and non-const function variants. + +/// Get typval_T out of list item +/// +/// @param[in] li List item to get typval_T from, must not be NULL. +/// +/// @return Pointer to typval_T. +#define TV_LIST_ITEM_TV(li) (&(li)->li_tv) + +/// Get next list item given the current one +/// +/// @param[in] l List to get item from. +/// @param[in] li List item to get typval_T from. +/// +/// @return Pointer to the next item or NULL. +#define TV_LIST_ITEM_NEXT(l, li) ((li)->li_next) + +/// Get previous list item given the current one +/// +/// @param[in] l List to get item from. +/// @param[in] li List item to get typval_T from. +/// +/// @return Pointer to the previous item or NULL. +#define TV_LIST_ITEM_PREV(l, li) ((li)->li_prev) +// List argument is not used currently, but it is a must for lists implemented +// as a pair (size(in list), array) without terminator - basically for lists on +// top of kvec. + /// Iterate over a dictionary /// /// @param[in] d Dictionary to iterate over. -- cgit From 274f32d42e61e6f6c76b9ca499f5b79f256a481a Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 03:39:51 +0300 Subject: *: Start hiding list implementation Most of files, except for eval.c and eval/* were only processed by perl. --- src/nvim/api/private/helpers.c | 2 +- src/nvim/channel.c | 2 +- src/nvim/eval.c | 845 +++++++++++++++++++++-------------------- src/nvim/eval/decode.c | 16 +- src/nvim/eval/typval.c | 131 +++---- src/nvim/eval/typval.h | 33 +- src/nvim/lua/converter.c | 2 +- src/nvim/ops.c | 2 +- 8 files changed, 541 insertions(+), 492 deletions(-) (limited to 'src') diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 629873998e..e5a1e150f9 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -798,7 +798,7 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) tv_list_append(list, li); } - list->lv_refcount++; + tv_list_ref(list); tv->v_type = VAR_LIST; tv->vval.v_list = list; diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 40af470bde..ff8dbd3d33 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -673,7 +673,7 @@ static void on_channel_event(void **args) argv[1].v_type = VAR_LIST; argv[1].v_lock = VAR_UNLOCKED; argv[1].vval.v_list = ev->received; - argv[1].vval.v_list->lv_refcount++; + tv_list_ref(argv[1].vval.v_list); } else { argv[1].v_type = VAR_NUMBER; argv[1].v_lock = VAR_UNLOCKED; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 56aedb1b4e..edf016fc41 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -564,8 +564,8 @@ void eval_init(void) dict_T *const msgpack_types_dict = tv_dict_alloc(); for (size_t i = 0; i < ARRAY_SIZE(msgpack_type_names); i++) { list_T *const type_list = tv_list_alloc(); - type_list->lv_lock = VAR_FIXED; - type_list->lv_refcount = 1; + tv_list_set_lock(type_list, VAR_FIXED); + tv_list_ref(type_list); dictitem_T *const di = tv_dict_item_alloc(msgpack_type_names[i]); di->di_flags |= DI_FLAGS_RO|DI_FLAGS_FIX; di->di_tv = (typval_T) { @@ -1014,7 +1014,7 @@ char_u *eval_to_string(char_u *arg, char_u **nextcmd, int convert) ga_init(&ga, (int)sizeof(char), 80); if (tv.vval.v_list != NULL) { tv_list_join(&ga, tv.vval.v_list, "\n"); - if (tv.vval.v_list->lv_len > 0) { + if (tv_list_len(tv.vval.v_list) > 0) { ga_append(&ga, NL); } } @@ -1143,27 +1143,31 @@ list_T *eval_spell_expr(char_u *badword, char_u *expr) return list; } -/* - * "list" is supposed to contain two items: a word and a number. Return the - * word in "pp" and the number as the return value. - * Return -1 if anything isn't right. - * Used to get the good word and score from the eval_spell_expr() result. - */ -int get_spellword(list_T *list, const char **pp) +/// Get spell word from an entry from spellsuggest=expr: +/// +/// Entry in question is supposed to be a list (to be checked by the caller) +/// with two items: a word and a score represented as an unsigned number +/// (whether it actually is unsigned is not checked). +/// +/// Used to get the good word and score from the eval_spell_expr() result. +/// +/// @param[in] list List to get values from. +/// @param[out] ret_word Suggested word. Not initialized if return value is +/// -1. +/// +/// @return -1 in case of error, score otherwise. +int get_spellword(list_T *const list, const char **ret_word) { - listitem_T *li; - - li = list->lv_first; - if (li == NULL) { + if (tv_list_len(list) != 2) { + EMSG(_("E5700: Expression from 'spellsuggest' must yield lists with " + "exactly two values")); return -1; } - *pp = tv_get_string(&li->li_tv); - - li = li->li_next; - if (li == NULL) { + *ret_word = tv_list_find_str(list, 0); + if (*ret_word == NULL) { return -1; } - return tv_get_number(&li->li_tv); + return tv_list_find_nr(list, -1, NULL); } @@ -1504,9 +1508,7 @@ ex_let_vars ( ) { char_u *arg = arg_start; - list_T *l; int i; - listitem_T *item; typval_T ltv; if (*arg != '[') { @@ -1518,13 +1520,12 @@ ex_let_vars ( return OK; } - /* - * ":let [v1, v2] = list" or ":for [v1, v2] in listlist" - */ - if (tv->v_type != VAR_LIST || (l = tv->vval.v_list) == NULL) { + // ":let [v1, v2] = list" or ":for [v1, v2] in listlist" + if (tv->v_type != VAR_LIST) { EMSG(_(e_listreq)); return FAIL; } + list_T *const l = tv->vval.v_list; i = tv_list_len(l); if (semicolon == 0 && var_count < i) { @@ -1535,29 +1536,33 @@ ex_let_vars ( EMSG(_("E688: More targets than List items")); return FAIL; } + // l may actually be NULL, but it should fail with E688 or even earlier if you + // try to do ":let [] = v:_null_list". + assert(l != NULL); - item = l->lv_first; + listitem_T *item = tv_list_first(l); while (*arg != ']') { arg = skipwhite(arg + 1); - arg = ex_let_one(arg, &item->li_tv, TRUE, (char_u *)",;]", nextchars); - item = item->li_next; - if (arg == NULL) + arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), TRUE, (char_u *)",;]", nextchars); + item = TV_LIST_ITEM_NEXT(l, item); + if (arg == NULL) { return FAIL; + } arg = skipwhite(arg); if (*arg == ';') { /* Put the rest of the list (may be empty) in the var after ';'. * Create a new list for this. */ - l = tv_list_alloc(); + list_T *const rest_list = tv_list_alloc(); while (item != NULL) { - tv_list_append_tv(l, &item->li_tv); - item = item->li_next; + tv_list_append_tv(rest_list, TV_LIST_ITEM_TV(item)); + item = TV_LIST_ITEM_NEXT(l, item); } ltv.v_type = VAR_LIST; ltv.v_lock = 0; - ltv.vval.v_list = l; - l->lv_refcount = 1; + ltv.vval.v_list = rest_list; + tv_list_ref(rest_list); arg = ex_let_one(skipwhite(arg + 1), <v, false, (char_u *)"]", nextchars); @@ -2310,7 +2315,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, } } - lp->ll_tv = &lp->ll_li->li_tv; + lp->ll_tv = TV_LIST_ITEM_TV(lp->ll_li); } } @@ -2375,45 +2380,49 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, int ll_n1 = lp->ll_n1; // Check whether any of the list items is locked - for (listitem_T *ri = rettv->vval.v_list->lv_first; + for (listitem_T *ri = tv_list_first(rettv->vval.v_list); ri != NULL && ll_li != NULL; ) { - if (tv_check_lock(ll_li->li_tv.v_lock, (const char *)lp->ll_name, + if (tv_check_lock(TV_LIST_ITEM_TV(ll_li)->v_lock, + (const char *)lp->ll_name, TV_CSTRING)) { return; } - ri = ri->li_next; + ri = TV_LIST_ITEM_NEXT(rettv->vval.v_list, ri); if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == ll_n1)) { break; } - ll_li = ll_li->li_next; + ll_li = TV_LIST_ITEM_NEXT(lp->ll_list, ll_li); ll_n1++; } /* * Assign the List values to the list items. */ - for (ri = rettv->vval.v_list->lv_first; ri != NULL; ) { + for (ri = tv_list_first(rettv->vval.v_list); ri != NULL; ) { if (op != NULL && *op != '=') { - eexe_mod_op(&lp->ll_li->li_tv, &ri->li_tv, (const char *)op); + eexe_mod_op(TV_LIST_ITEM_TV(lp->ll_li), TV_LIST_ITEM_TV(ri), + (const char *)op); } else { - tv_clear(&lp->ll_li->li_tv); - tv_copy(&ri->li_tv, &lp->ll_li->li_tv); + tv_clear(TV_LIST_ITEM_TV(lp->ll_li)); + tv_copy(TV_LIST_ITEM_TV(ri), TV_LIST_ITEM_TV(lp->ll_li)); } - ri = ri->li_next; - if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == lp->ll_n1)) + ri = TV_LIST_ITEM_NEXT(rettv->vval.v_list, ri); + if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == lp->ll_n1)) { break; - if (lp->ll_li->li_next == NULL) { + } + if (TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li) == NULL) { // Need to add an empty item. tv_list_append_number(lp->ll_list, 0); - assert(lp->ll_li->li_next); + assert(TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li)); } - lp->ll_li = lp->ll_li->li_next; - ++lp->ll_n1; + lp->ll_li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li); + lp->ll_n1++; } if (ri != NULL) EMSG(_("E710: List value has more items than target")); else if (lp->ll_empty2 - ? (lp->ll_li != NULL && lp->ll_li->li_next != NULL) + ? (lp->ll_li != NULL + && TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li) != NULL) : lp->ll_n1 != lp->ll_n2) EMSG(_("E711: List value has not enough items")); } else { @@ -2514,7 +2523,7 @@ void *eval_for_line(const char_u *arg, bool *errp, char_u **nextcmdp, int skip) * list being used in "tv". */ fi->fi_list = l; tv_list_watch_add(l, &fi->fi_lw); - fi->fi_lw.lw_item = l->lv_first; + fi->fi_lw.lw_item = tv_list_first(l); } } } @@ -2532,21 +2541,18 @@ void *eval_for_line(const char_u *arg, bool *errp, char_u **nextcmdp, int skip) * Return TRUE when a valid item was found, FALSE when at end of list or * something wrong. */ -int next_for_item(void *fi_void, char_u *arg) +bool next_for_item(void *fi_void, char_u *arg) { - forinfo_T *fi = (forinfo_T *)fi_void; - int result; - listitem_T *item; + forinfo_T *fi = (forinfo_T *)fi_void; - item = fi->fi_lw.lw_item; - if (item == NULL) - result = FALSE; - else { - fi->fi_lw.lw_item = item->li_next; - result = (ex_let_vars(arg, &item->li_tv, TRUE, - fi->fi_semicolon, fi->fi_varcount, NULL) == OK); + listitem_T *item = fi->fi_lw.lw_item; + if (item == NULL) { + return false; + } else { + fi->fi_lw.lw_item = TV_LIST_ITEM_NEXT(fi->fi_list, item); + return (ex_let_vars(arg, TV_LIST_ITEM_TV(item), true, + fi->fi_semicolon, fi->fi_varcount, NULL) == OK); } - return result; } // TODO(ZyX-I): move to eval/ex_cmds @@ -2869,9 +2875,9 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit) ret = FAIL; } *name_end = cc; - } else if ((lp->ll_list != NULL - && tv_check_lock(lp->ll_list->lv_lock, (const char *)lp->ll_name, - lp->ll_name_len)) + } else if (tv_check_lock(tv_list_locked(lp->ll_list), + (const char *)lp->ll_name, + lp->ll_name_len) || (lp->ll_dict != NULL && tv_check_lock(lp->ll_dict->dv_lock, (const char *)lp->ll_name, @@ -2883,8 +2889,9 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit) int ll_n1 = lp->ll_n1; while (ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= ll_n1)) { - li = ll_li->li_next; - if (tv_check_lock(ll_li->li_tv.v_lock, (const char *)lp->ll_name, + li = TV_LIST_ITEM_NEXT(lp->ll_list, ll_li); + if (tv_check_lock(TV_LIST_ITEM_TV(ll_li)->v_lock, + (const char *)lp->ll_name, lp->ll_name_len)) { return false; } @@ -2892,12 +2899,12 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit) ll_n1++; } - /* Delete a range of List items. */ + // Delete a range of List items. while (lp->ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) { - li = lp->ll_li->li_next; + li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li); tv_list_item_remove(lp->ll_list, lp->ll_li); lp->ll_li = li; - ++lp->ll_n1; + lp->ll_n1++; } } else { if (lp->ll_list != NULL) { @@ -3043,13 +3050,13 @@ static int do_lock_var(lval_T *lp, char_u *const name_end, const int deep, /* (un)lock a range of List items. */ while (li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) { - tv_item_lock(&li->li_tv, deep, lock); - li = li->li_next; - ++lp->ll_n1; + tv_item_lock(TV_LIST_ITEM_TV(li), deep, lock); + li = TV_LIST_ITEM_NEXT(lp->ll_list, li); + lp->ll_n1++; } } else if (lp->ll_list != NULL) { // (un)lock a List item. - tv_item_lock(&lp->ll_li->li_tv, deep, lock); + tv_item_lock(TV_LIST_ITEM_TV(lp->ll_li), deep, lock); } else { // (un)lock a Dictionary item. tv_item_lock(&lp->ll_di->di_tv, deep, lock); @@ -4511,15 +4518,15 @@ eval_index ( l = tv_list_alloc(); item = tv_list_find(rettv->vval.v_list, n1); while (n1++ <= n2) { - tv_list_append_tv(l, &item->li_tv); - item = item->li_next; + tv_list_append_tv(l, TV_LIST_ITEM_TV(item)); + item = TV_LIST_ITEM_NEXT(l, item); } tv_clear(rettv); rettv->v_type = VAR_LIST; rettv->vval.v_list = l; - l->lv_refcount++; + tv_list_ref(l); } else { - tv_copy(&tv_list_find(rettv->vval.v_list, n1)->li_tv, &var1); + tv_copy(TV_LIST_ITEM_TV(tv_list_find(rettv->vval.v_list, n1)), &var1); tv_clear(rettv); *rettv = var1; } @@ -4877,22 +4884,23 @@ static int get_list_tv(char_u **arg, typval_T *rettv, int evaluate) goto failret; if (evaluate) { item = tv_list_item_alloc(); - item->li_tv = tv; - item->li_tv.v_lock = 0; + *TV_LIST_ITEM_TV(item) = tv; + TV_LIST_ITEM_TV(item)->v_lock = VAR_UNLOCKED; tv_list_append(l, item); } - if (**arg == ']') + if (**arg == ']') { break; + } if (**arg != ',') { - EMSG2(_("E696: Missing comma in List: %s"), *arg); + emsgf(_("E696: Missing comma in List: %s"), *arg); goto failret; } *arg = skipwhite(*arg + 1); } if (**arg != ']') { - EMSG2(_("E697: Missing end of List ']': %s"), *arg); + emsgf(_("E697: Missing end of List ']': %s"), *arg); failret: if (evaluate) { tv_list_free(l); @@ -4904,7 +4912,7 @@ failret: if (evaluate) { rettv->v_type = VAR_LIST; rettv->vval.v_list = l; - ++l->lv_refcount; + tv_list_ref(l); } return OK; @@ -5260,6 +5268,7 @@ static int free_unref_items(int copyID) for (ll = gc_first_list; ll != NULL; ll = ll_next) { ll_next = ll->lv_used_next; if ((ll->lv_copyID & COPYID_MASK) != (copyID & COPYID_MASK) + // FIXME: Abstract away lv_watch. && ll->lv_watch == NULL) { // Free the List and ordinary items it contains, but don't recurse // into Lists and Dictionaries, they will be in the list of dicts @@ -5329,10 +5338,13 @@ bool set_ref_in_list(list_T *l, int copyID, ht_stack_T **ht_stack) // Mark each item in the list. If the item contains a hashtab // it is added to ht_stack, if it contains a list it is added to // list_stack. - for (listitem_T *li = cur_l->lv_first; !abort && li != NULL; - li = li->li_next) { - abort = set_ref_in_item(&li->li_tv, copyID, ht_stack, &list_stack); - } + TV_LIST_ITER(cur_l, li, { + abort = set_ref_in_item(TV_LIST_ITEM_TV(li), copyID, ht_stack, + &list_stack); + if (abort) { + break; + } + }); } if (list_stack == NULL) { @@ -6535,7 +6547,7 @@ static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = 1; /* Default: Failed */ if (argvars[0].v_type == VAR_LIST) { if ((l = argvars[0].vval.v_list) != NULL - && !tv_check_lock(l->lv_lock, "add() argument", TV_TRANSLATE)) { + && !tv_check_lock(tv_list_locked(l), "add() argument", TV_TRANSLATE)) { tv_list_append_tv(l, &argvars[1]); tv_copy(&argvars[0], rettv); } @@ -6586,9 +6598,10 @@ static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) && u_save(lnum, lnum + 1) == OK) { if (argvars[1].v_type == VAR_LIST) { l = argvars[1].vval.v_list; - if (l == NULL) + if (l == NULL) { return; - li = l->lv_first; + } + li = tv_list_first(l); } for (;; ) { if (l == NULL) { @@ -6596,7 +6609,7 @@ static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else if (li == NULL) { break; // End of list. } else { - tv = &li->li_tv; // Append item from list. + tv = TV_LIST_ITEM_TV(li); // Append item from list. } const char *const line = tv_get_string_chk(tv); if (line == NULL) { // Type error. @@ -6608,7 +6621,7 @@ static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (l == NULL) { break; } - li = li->li_next; + li = TV_LIST_ITEM_NEXT(l, li); } appended_lines_mark(lnum, added); @@ -7247,30 +7260,26 @@ static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, FunPtr fptr) int func_call(char_u *name, typval_T *args, partial_T *partial, dict_T *selfdict, typval_T *rettv) { - listitem_T *item; typval_T argv[MAX_FUNC_ARGS + 1]; int argc = 0; int dummy; int r = 0; - for (item = args->vval.v_list->lv_first; item != NULL; - item = item->li_next) { + TV_LIST_ITER(args->vval.v_list, item, { if (argc == MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) { EMSG(_("E699: Too many arguments")); - break; + goto func_call_skip_call; } - /* Make a copy of each argument. This is needed to be able to set - * v_lock to VAR_FIXED in the copy without changing the original list. - */ - tv_copy(&item->li_tv, &argv[argc++]); - } + // Make a copy of each argument. This is needed to be able to set + // v_lock to VAR_FIXED in the copy without changing the original list. + tv_copy(TV_LIST_ITEM_TV(item), &argv[argc++]); + }); - if (item == NULL) { - r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &dummy, true, partial, selfdict); - } + r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL, + curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &dummy, true, partial, selfdict); +func_call_skip_call: // Free the arguments. while (argc > 0) { tv_clear(&argv[--argc]); @@ -7595,7 +7604,7 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) long idx; if ((l = argvars[0].vval.v_list) != NULL) { - li = l->lv_first; + li = tv_list_first(l); if (argvars[2].v_type != VAR_UNKNOWN) { bool error = false; @@ -7613,9 +7622,11 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) li = NULL; } - for (; li != NULL; li = li->li_next) - if (tv_equal(&li->li_tv, &argvars[1], ic, FALSE)) - ++n; + for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { + if (tv_equal(TV_LIST_ITEM_TV(li), &argvars[1], ic, false)) { + n++; + } + } } } else if (argvars[0].v_type == VAR_DICT) { int todo; @@ -7933,34 +7944,51 @@ static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool n = true; switch (argvars[0].v_type) { - case VAR_STRING: - case VAR_FUNC: - n = argvars[0].vval.v_string == NULL - || *argvars[0].vval.v_string == NUL; - break; - case VAR_PARTIAL: - n = false; - break; - case VAR_NUMBER: - n = argvars[0].vval.v_number == 0; - break; - case VAR_FLOAT: - n = argvars[0].vval.v_float == 0.0; - break; - case VAR_LIST: - n = argvars[0].vval.v_list == NULL - || argvars[0].vval.v_list->lv_first == NULL; - break; - case VAR_DICT: - n = argvars[0].vval.v_dict == NULL - || argvars[0].vval.v_dict->dv_hashtab.ht_used == 0; - break; - case VAR_SPECIAL: - n = argvars[0].vval.v_special != kSpecialVarTrue; - break; - case VAR_UNKNOWN: - EMSG2(_(e_intern2), "f_empty(UNKNOWN)"); - break; + case VAR_STRING: + case VAR_FUNC: { + n = argvars[0].vval.v_string == NULL + || *argvars[0].vval.v_string == NUL; + break; + } + case VAR_PARTIAL: { + n = false; + break; + } + case VAR_NUMBER: { + n = argvars[0].vval.v_number == 0; + break; + } + case VAR_FLOAT: { + n = argvars[0].vval.v_float == 0.0; + break; + } + case VAR_LIST: { + n = (tv_list_len(argvars[0].vval.v_list) == 0); + break; + } + case VAR_DICT: { + n = (tv_dict_len(argvars[0].vval.v_dict) == 0); + break; + } + case VAR_SPECIAL: { + // Using switch to get warning if SpecialVarValue receives more values. + switch (argvars[0].vval.v_special) { + case kSpecialVarTrue: { + n = false; + break; + } + case kSpecialVarFalse: + case kSpecialVarNull: { + n = true; + break; + } + } + break; + } + case VAR_UNKNOWN: { + EMSG2(_(e_intern2), "f_empty(UNKNOWN)"); + break; + } } rettv->vval.v_number = n; @@ -8024,17 +8052,22 @@ static void f_executable(typval_T *argvars, typval_T *rettv, FunPtr fptr) && os_can_exe((const char_u *)name, NULL, false))); } +typedef struct { + const list_T *const l; + const listitem_T *li; +} GetListLineCookie; + static char_u *get_list_line(int c, void *cookie, int indent) { - const listitem_T **const p = (const listitem_T **)cookie; - const listitem_T *item = *p; + GetListLineCookie *const p = (GetListLineCookie *)cookie; + const listitem_T *const item = p->li; if (item == NULL) { return NULL; } char buf[NUMBUFLEN]; - const char *const s = tv_get_string_buf_chk(&item->li_tv, buf); - *p = item->li_next; + const char *const s = tv_get_string_buf_chk(TV_LIST_ITEM_TV(item), buf); + p->li = TV_LIST_ITEM_NEXT(p->l, item); return (char_u *)(s == NULL ? NULL : xstrdup(s)); } @@ -8076,11 +8109,14 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) do_cmdline_cmd(tv_get_string(&argvars[0])); } else if (argvars[0].vval.v_list != NULL) { list_T *const list = argvars[0].vval.v_list; - list->lv_refcount++; - listitem_T *const item = list->lv_first; - do_cmdline(NULL, get_list_line, (void *)&item, + tv_list_ref(list); + GetListLineCookie cookie = { + .l = list, + .li = tv_list_first(list), + }; + do_cmdline(NULL, get_list_line, (void *)&cookie, DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT|DOCMD_KEYTYPED); - list->lv_refcount--; + tv_list_unref(list); } msg_silent = save_msg_silent; emsg_silent = save_emsg_silent; @@ -8262,14 +8298,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) list_T *const l1 = argvars[0].vval.v_list; list_T *const l2 = argvars[1].vval.v_list; - if (l1 == NULL) { - const bool locked = tv_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE); - (void)locked; - assert(locked == true); - } else if (l2 == NULL) { - // Do nothing - tv_copy(&argvars[0], rettv); - } else if (!tv_check_lock(l1->lv_lock, arg_errmsg, TV_TRANSLATE)) { + if (!tv_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) { listitem_T *item; if (argvars[2].v_type != VAR_UNKNOWN) { before = (long)tv_get_number_chk(&argvars[2], &error); @@ -8277,7 +8306,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; // Type error; errmsg already given. } - if (before == l1->lv_len) { + if (before == tv_list_len(l1)) { item = NULL; } else { item = tv_list_find(l1, before); @@ -8286,8 +8315,9 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } } - } else + } else { item = NULL; + } tv_list_extend(l1, l2, item); tv_copy(&argvars[0], rettv); @@ -8459,7 +8489,8 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) if (argvars[0].v_type == VAR_LIST) { tv_copy(&argvars[0], rettv); if ((l = argvars[0].vval.v_list) == NULL - || (!map && tv_check_lock(l->lv_lock, arg_errmsg, TV_TRANSLATE))) { + || (!map && tv_check_lock(tv_list_locked(l), arg_errmsg, + TV_TRANSLATE))) { return; } } else if (argvars[0].v_type == VAR_DICT) { @@ -8522,14 +8553,15 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) } else { vimvars[VV_KEY].vv_type = VAR_NUMBER; - for (li = l->lv_first; li != NULL; li = nli) { + for (li = tv_list_first(l); li != NULL; li = nli) { if (map - && tv_check_lock(li->li_tv.v_lock, arg_errmsg, TV_TRANSLATE)) { + && tv_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, + TV_TRANSLATE)) { break; } - nli = li->li_next; + nli = TV_LIST_ITEM_NEXT(l, li); vimvars[VV_KEY].vv_nr = idx; - if (filter_map_one(&li->li_tv, expr, map, &rem) == FAIL + if (filter_map_one(TV_LIST_ITEM_TV(li), expr, map, &rem) == FAIL || did_emsg) break; if (!map && rem) { @@ -8935,7 +8967,7 @@ static void common_function(typval_T *argvars, typval_T *rettv, goto theend; } list = argvars[arg_idx].vval.v_list; - if (list == NULL || list->lv_len == 0) { + if (list == NULL || tv_list_len(list) == 0) { arg_idx = 0; } } @@ -8946,7 +8978,7 @@ static void common_function(typval_T *argvars, typval_T *rettv, // result is a VAR_PARTIAL if (arg_idx > 0 || (arg_pt != NULL && arg_pt->pt_argc > 0)) { const int arg_len = (arg_pt == NULL ? 0 : arg_pt->pt_argc); - const int lv_len = (list == NULL ? 0 : list->lv_len); + const int lv_len = tv_list_len(list); pt->pt_argc = arg_len + lv_len; pt->pt_argv = xmalloc(sizeof(pt->pt_argv[0]) * pt->pt_argc); @@ -8955,11 +8987,9 @@ static void common_function(typval_T *argvars, typval_T *rettv, tv_copy(&arg_pt->pt_argv[i], &pt->pt_argv[i]); } if (lv_len > 0) { - for (listitem_T *li = list->lv_first; - li != NULL; - li = li->li_next) { - tv_copy(&li->li_tv, &pt->pt_argv[i++]); - } + TV_LIST_ITER(list, li, { + tv_copy(TV_LIST_ITEM_TV(li), &pt->pt_argv[i++]); + }); } } @@ -9045,7 +9075,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) li = tv_list_find(l, tv_get_number_chk(&argvars[1], &error)); if (!error && li != NULL) { - tv = &li->li_tv; + tv = TV_LIST_ITEM_TV(li); } } } else if (argvars[0].v_type == VAR_DICT) { @@ -10067,7 +10097,7 @@ static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (rettv->vval.v_list == NULL) { rettv->vval.v_list = tv_list_alloc(); } - rettv->vval.v_list->lv_refcount++; + tv_list_ref(rettv->vval.v_list); } else { rettv->v_type = VAR_STRING; rettv->vval.v_string = get_reg_contents(regname, arg2 ? kGRegExprSrc : 0); @@ -11024,39 +11054,40 @@ static void f_indent(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - list_T *l; - listitem_T *item; long idx = 0; - int ic = FALSE; + bool ic = false; rettv->vval.v_number = -1; if (argvars[0].v_type != VAR_LIST) { EMSG(_(e_listreq)); return; } - l = argvars[0].vval.v_list; + list_T *const l = argvars[0].vval.v_list; if (l != NULL) { - item = l->lv_first; + listitem_T *item = tv_list_first(l); if (argvars[2].v_type != VAR_UNKNOWN) { bool error = false; // Start at specified item. Use the cached index that tv_list_find() // sets, so that a negative number also works. - item = tv_list_find(l, tv_get_number_chk(&argvars[2], &error)); - idx = l->lv_idx; - if (argvars[3].v_type != VAR_UNKNOWN) { - ic = tv_get_number_chk(&argvars[3], &error); - } - if (error) { + idx = tv_list_uidx(l, tv_get_number_chk(&argvars[2], &error)); + if (error || idx == -1) { item = NULL; + } else { + item = tv_list_find(l, idx); + assert(item != NULL); + } + if (argvars[3].v_type != VAR_UNKNOWN) { + ic = !!tv_get_number_chk(&argvars[3], &error); } } - for (; item != NULL; item = item->li_next, ++idx) - if (tv_equal(&item->li_tv, &argvars[1], ic, FALSE)) { + for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) { + if (tv_equal(TV_LIST_ITEM_TV(item), &argvars[1], ic, false)) { rettv->vval.v_number = idx; break; } + } } } @@ -11220,7 +11251,6 @@ static void f_inputdialog(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - listitem_T *li; int selected; int mouse_used; @@ -11235,15 +11265,16 @@ static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) msg_scroll = TRUE; msg_clr_eos(); - for (li = argvars[0].vval.v_list->lv_first; li != NULL; li = li->li_next) { - msg_puts(tv_get_string(&li->li_tv)); + TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { + msg_puts(tv_get_string(TV_LIST_ITEM_TV(li))); msg_putchar('\n'); - } + }); - /* Ask for choice. */ + // Ask for choice. selected = prompt_for_number(&mouse_used); - if (mouse_used) + if (mouse_used) { selected -= lines_left; + } rettv->vval.v_number = selected; } @@ -11299,9 +11330,8 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listarg), "insert()"); - } else if ((l = argvars[0].vval.v_list) != NULL - && !tv_check_lock(l->lv_lock, N_("insert() argument"), - TV_TRANSLATE)) { + } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + N_("insert() argument"), TV_TRANSLATE)) { long before = 0; if (argvars[2].v_type != VAR_UNKNOWN) { before = tv_get_number_chk(&argvars[2], &error); @@ -11312,7 +11342,7 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) } listitem_T *item = NULL; - if (before != l->lv_len) { + if (before != tv_list_len(l)) { item = tv_list_find(l, before); if (item == NULL) { EMSGN(_(e_listidx), before); @@ -11376,7 +11406,7 @@ static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG2(_(e_dictkey), lv.ll_newkey); } else if (lv.ll_list != NULL) { // List item. - rettv->vval.v_number = tv_islocked(&lv.ll_li->li_tv); + rettv->vval.v_number = tv_islocked(TV_LIST_ITEM_TV(lv.ll_li)); } else { // Dictionary item. rettv->vval.v_number = tv_islocked(&lv.ll_di->di_tv); @@ -11413,32 +11443,32 @@ static void dict_list(typval_T *const tv, typval_T *const rettv, switch (what) { case kDictListKeys: { - li->li_tv.v_type = VAR_STRING; - li->li_tv.v_lock = VAR_UNLOCKED; - li->li_tv.vval.v_string = vim_strsave(di->di_key); + TV_LIST_ITEM_TV(li)->v_type = VAR_STRING; + TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; + TV_LIST_ITEM_TV(li)->vval.v_string = vim_strsave(di->di_key); break; } case kDictListValues: { - tv_copy(&di->di_tv, &li->li_tv); + tv_copy(&di->di_tv, TV_LIST_ITEM_TV(li)); break; } case kDictListItems: { // items() list_T *const sub_l = tv_list_alloc(); - li->li_tv.v_type = VAR_LIST; - li->li_tv.v_lock = VAR_UNLOCKED; - li->li_tv.vval.v_list = sub_l; - sub_l->lv_refcount++; + TV_LIST_ITEM_TV(li)->v_type = VAR_LIST; + TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; + TV_LIST_ITEM_TV(li)->vval.v_list = sub_l; + tv_list_ref(sub_l); listitem_T *sub_li = tv_list_item_alloc(); tv_list_append(sub_l, sub_li); - sub_li->li_tv.v_type = VAR_STRING; - sub_li->li_tv.v_lock = VAR_UNLOCKED; - sub_li->li_tv.vval.v_string = vim_strsave(di->di_key); + TV_LIST_ITEM_TV(sub_li)->v_type = VAR_STRING; + TV_LIST_ITEM_TV(sub_li)->v_lock = VAR_UNLOCKED; + TV_LIST_ITEM_TV(sub_li)->vval.v_string = vim_strsave(di->di_key); sub_li = tv_list_item_alloc(); tv_list_append(sub_l, sub_li); - tv_copy(&di->di_tv, &sub_li->li_tv); + tv_copy(&di->di_tv, TV_LIST_ITEM_TV(sub_li)); break; } } @@ -11536,15 +11566,13 @@ static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable) } list_T *argl = cmd_tv->vval.v_list; - int argc = argl->lv_len; + int argc = tv_list_len(argl); if (!argc) { EMSG(_(e_invarg)); // List must have at least one item. return NULL; } - assert(argl->lv_first); - - const char *exe = tv_get_string_chk(&argl->lv_first->li_tv); + const char *exe = tv_get_string_chk(TV_LIST_ITEM_TV(tv_list_first(argl))); if (!exe || !os_can_exe((const char_u *)exe, NULL, true)) { if (exe && executable) { *executable = false; @@ -11559,15 +11587,15 @@ static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable) // Build the argument vector int i = 0; char **argv = xcalloc(argc + 1, sizeof(char *)); - for (listitem_T *arg = argl->lv_first; arg != NULL; arg = arg->li_next) { - const char *a = tv_get_string_chk(&arg->li_tv); + TV_LIST_ITER_CONST(argl, arg, { + const char *a = tv_get_string_chk(TV_LIST_ITEM_TV(arg)); if (!a) { // Did emsg in tv_get_string_chk; just deallocate argv. shell_free_argv(argv); return NULL; } argv[i++] = xstrdup(a); - } + }); return argv; } @@ -11695,7 +11723,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) list_T *args = argvars[0].vval.v_list; - Channel **jobs = xcalloc(args->lv_len, sizeof(*jobs)); + Channel **jobs = xcalloc(tv_list_len(args), sizeof(*jobs)); ui_busy_start(); MultiQueue *waiting_jobs = multiqueue_new_parent(loop_on_put, &main_loop); @@ -11704,10 +11732,10 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) // -1 for jobs that were skipped or timed out. int i = 0; - for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next, i++) { + TV_LIST_ITER_CONST(args, arg, { Channel *chan = NULL; - if (arg->li_tv.v_type != VAR_NUMBER - || !(chan = find_job(arg->li_tv.vval.v_number, false))) { + if (TV_LIST_ITEM_TV(arg)->v_type != VAR_NUMBER + || !(chan = find_job(TV_LIST_ITEM_TV(arg)->vval.v_number, false))) { jobs[i] = NULL; } else { jobs[i] = chan; @@ -11719,7 +11747,8 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) multiqueue_replace_parent(chan->events, waiting_jobs); } } - } + i++; + }); int remaining = -1; uint64_t before = 0; @@ -11728,7 +11757,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) before = os_hrtime(); } - for (i = 0; i < args->lv_len; i++) { + for (i = 0; i < tv_list_len(args); i++) { if (remaining == 0) { // timed out break; @@ -11758,7 +11787,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) list_T *rv = tv_list_alloc(); // restore the parent queue for any jobs still alive - for (i = 0; i < args->lv_len; i++) { + for (i = 0; i < tv_list_len(args); i++) { if (jobs[i] == NULL) { tv_list_append_number(rv, -3); continue; @@ -11774,7 +11803,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) multiqueue_free(waiting_jobs); xfree(jobs); ui_busy_stop(); - rv->lv_refcount++; + tv_list_ref(rv); rettv->v_type = VAR_LIST; rettv->vval.v_list = rv; } @@ -11788,9 +11817,6 @@ static void f_join(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG(_(e_listreq)); return; } - if (argvars[0].vval.v_list == NULL) { - return; - } const char *const sep = (argvars[1].v_type == VAR_UNKNOWN ? " " : tv_get_string_chk(&argvars[1])); @@ -12200,7 +12226,7 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) if (argvars[0].v_type == VAR_LIST) { if ((l = argvars[0].vval.v_list) == NULL) goto theend; - li = l->lv_first; + li = tv_list_first(l); } else { expr = str = (char_u *)tv_get_string(&argvars[0]); len = (long)STRLEN(str); @@ -12220,11 +12246,11 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) goto theend; } if (l != NULL) { - li = tv_list_find(l, start); + idx = tv_list_uidx(l, start); + li = tv_list_find(l, idx); if (li == NULL) { goto theend; } - idx = l->lv_idx; // Use the cached index. } else { if (start < 0) start = 0; @@ -12260,7 +12286,8 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) break; } xfree(tofree); - tofree = expr = str = (char_u *)encode_tv2echo(&li->li_tv, NULL); + tofree = expr = str = (char_u *)encode_tv2echo(TV_LIST_ITEM_TV(li), + NULL); if (str == NULL) { break; } @@ -12275,8 +12302,8 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) /* Advance to just after the match. */ if (l != NULL) { - li = li->li_next; - ++idx; + li = TV_LIST_ITEM_NEXT(l, li); + idx++; } else { startcol = (colnr_T)(regmatch.startp[0] + (*mb_ptr2len)(regmatch.startp[0]) - str); @@ -12289,18 +12316,22 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) if (match) { if (type == 4) { - listitem_T *li1 = rettv->vval.v_list->lv_first; - listitem_T *li2 = li1->li_next; - listitem_T *li3 = li2->li_next; - listitem_T *li4 = li3->li_next; - xfree(li1->li_tv.vval.v_string); - - int rd = (int)(regmatch.endp[0] - regmatch.startp[0]); - li1->li_tv.vval.v_string = vim_strnsave(regmatch.startp[0], rd); - li3->li_tv.vval.v_number = (varnumber_T)(regmatch.startp[0] - expr); - li4->li_tv.vval.v_number = (varnumber_T)(regmatch.endp[0] - expr); + list_T *const ret_l = rettv->vval.v_list; + listitem_T *li1 = tv_list_first(ret_l); + listitem_T *li2 = TV_LIST_ITEM_NEXT(ret_l, li1); + listitem_T *li3 = TV_LIST_ITEM_NEXT(ret_l, li2); + listitem_T *li4 = TV_LIST_ITEM_NEXT(ret_l, li3); + xfree(TV_LIST_ITEM_TV(li1)->vval.v_string); + + const size_t rd = (size_t)(regmatch.endp[0] - regmatch.startp[0]); + TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz( + (const char *)regmatch.startp[0], rd); + TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)( + regmatch.startp[0] - expr); + TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)( + regmatch.endp[0] - expr); if (l != NULL) { - li2->li_tv.vval.v_number = (varnumber_T)idx; + TV_LIST_ITEM_TV(li2)->vval.v_number = (varnumber_T)idx; } } else if (type == 3) { int i; @@ -12318,7 +12349,7 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) } else if (type == 2) { // Return matched string. if (l != NULL) { - tv_copy(&li->li_tv, rettv); + tv_copy(TV_LIST_ITEM_TV(li), rettv); } else { rettv->vval.v_string = (char_u *)xmemdupz( (const char *)regmatch.startp[0], @@ -12342,8 +12373,8 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) if (type == 4 && l == NULL) { // matchstrpos() without a list: drop the second item - tv_list_item_remove(rettv->vval.v_list, - rettv->vval.v_list->lv_first->li_next); + list_T *const ret_l = rettv->vval.v_list; + tv_list_item_remove(ret_l, TV_LIST_ITEM_NEXT(ret_l, tv_list_first(ret_l))); } theend: @@ -12539,41 +12570,41 @@ static void max_min(const typval_T *const tv, typval_T *const rettv, const bool domax) FUNC_ATTR_NONNULL_ALL { - varnumber_T n = 0; bool error = false; + rettv->vval.v_number = 0; + varnumber_T n = (domax ? VARNUMBER_MIN : VARNUMBER_MAX); if (tv->v_type == VAR_LIST) { - const list_T *const l = tv->vval.v_list; - if (tv_list_len(l) != 0) { - n = tv_get_number_chk(&l->lv_first->li_tv, &error); - for (const listitem_T *li = l->lv_first->li_next; li != NULL && !error; - li = li->li_next) { - const varnumber_T i = tv_get_number_chk(&li->li_tv, &error); - if (domax ? i > n : i < n) { - n = i; - } - } + if (tv_list_len(tv->vval.v_list) == 0) { + return; } + TV_LIST_ITER_CONST(tv->vval.v_list, li, { + const varnumber_T i = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error); + if (error) { + return; + } + if (domax ? i > n : i < n) { + n = i; + } + }); } else if (tv->v_type == VAR_DICT) { - if (tv->vval.v_dict != NULL) { - bool first = true; - TV_DICT_ITER(tv->vval.v_dict, di, { - const varnumber_T i = tv_get_number_chk(&di->di_tv, &error); - if (error) { - break; - } - if (first) { - n = i; - first = true; - } else if (domax ? i > n : i < n) { - n = i; - } - }); + if (tv_dict_len(tv->vval.v_dict) == 0) { + return; } + TV_DICT_ITER(tv->vval.v_dict, di, { + const varnumber_T i = tv_get_number_chk(&di->di_tv, &error); + if (error) { + return; + } + if (domax ? i > n : i < n) { + n = i; + } + }); } else { EMSG2(_(e_listdictarg), domax ? "max()" : "min()"); + return; } - rettv->vval.v_number = error ? 0 : n; + rettv->vval.v_number = n; } /* @@ -12659,22 +12690,19 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } list_T *ret_list = tv_list_alloc_ret(rettv); - const list_T *list = argvars[0].vval.v_list; - if (list == NULL) { - return; - } + list_T *list = argvars[0].vval.v_list; msgpack_packer *lpacker = msgpack_packer_new(ret_list, &encode_list_write); const char *const msg = _("msgpackdump() argument, index %i"); // Assume that translation will not take more then 4 times more space char msgbuf[sizeof("msgpackdump() argument, index ") * 4 + NUMBUFLEN]; int idx = 0; - for (listitem_T *li = list->lv_first; li != NULL; li = li->li_next) { - vim_snprintf(msgbuf, sizeof(msgbuf), (char *) msg, idx); + TV_LIST_ITER(list, li, { + vim_snprintf(msgbuf, sizeof(msgbuf), (char *)msg, idx); idx++; - if (encode_vim_to_msgpack(lpacker, &li->li_tv, msgbuf) == FAIL) { + if (encode_vim_to_msgpack(lpacker, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) { break; } - } + }); msgpack_packer_free(lpacker); } @@ -12688,10 +12716,10 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) } list_T *ret_list = tv_list_alloc_ret(rettv); const list_T *list = argvars[0].vval.v_list; - if (list == NULL || list->lv_first == NULL) { + if (tv_list_len(list) == 0) { return; } - if (list->lv_first->li_tv.v_type != VAR_STRING) { + if (TV_LIST_ITEM_TV(tv_list_first(list))->v_type != VAR_STRING) { EMSG2(_(e_invarg2), "List item is not a string"); return; } @@ -12732,9 +12760,9 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) } if (result == MSGPACK_UNPACK_SUCCESS) { listitem_T *li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_UNKNOWN; + TV_LIST_ITEM_TV(li)->v_type = VAR_UNKNOWN; tv_list_append(ret_list, li); - if (msgpack_to_vim(unpacked.data, &li->li_tv) == FAIL) { + if (msgpack_to_vim(unpacked.data, TV_LIST_ITEM_TV(li)) == FAIL) { EMSG2(_(e_invarg2), "Failed to convert msgpack string"); goto f_msgpackparse_exit; } @@ -13027,9 +13055,9 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_STRING; - li->li_tv.v_lock = 0; - li->li_tv.vval.v_string = s; + TV_LIST_ITEM_TV(li)->v_type = VAR_STRING; + TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; + TV_LIST_ITEM_TV(li)->vval.v_string = s; tv_list_append(rettv->vval.v_list, li); start = p + 1; /* step over newline */ @@ -13106,7 +13134,8 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ if (maxline < 0) while (cnt > -maxline) { - tv_list_item_remove(rettv->vval.v_list, rettv->vval.v_list->lv_first); + tv_list_item_remove(rettv->vval.v_list, + tv_list_first(rettv->vval.v_list)); cnt--; } @@ -13123,9 +13152,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// @return OK In case of success, FAIL in case of error static int list2proftime(typval_T *arg, proftime_T *tm) FUNC_ATTR_NONNULL_ALL { - if (arg->v_type != VAR_LIST - || arg->vval.v_list == NULL - || arg->vval.v_list->lv_len != 2) { + if (arg->v_type != VAR_LIST || tv_list_len(arg->vval.v_list) != 2) { return FAIL; } @@ -13249,8 +13276,8 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } else if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listdictarg), "remove()"); - } else if ((l = argvars[0].vval.v_list) != NULL - && !tv_check_lock(l->lv_lock, arg_errmsg, TV_TRANSLATE)) { + } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + arg_errmsg, TV_TRANSLATE)) { bool error = false; idx = tv_get_number_chk(&argvars[1], &error); @@ -13262,7 +13289,7 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[2].v_type == VAR_UNKNOWN) { // Remove one item, return its value. tv_list_remove_items(l, item, item); - *rettv = item->li_tv; + *rettv = *TV_LIST_ITEM_TV(item); xfree(item); } else { // Remove range of items, return list with values. @@ -13274,15 +13301,17 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else { int cnt = 0; - for (li = item; li != NULL; li = li->li_next) { - ++cnt; - if (li == item2) + for (li = item; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { + cnt++; + if (li == item2) { break; + } } if (li == NULL) { // Didn't find "item2" after "item". emsgf(_(e_invrange)); } else { tv_list_remove_items(l, item, item2); + // FIXME: Abstract the below away or move to eval/typval. l = tv_list_alloc_ret(rettv); l->lv_first = item; l->lv_last = item2; @@ -13524,9 +13553,9 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) list_T *l; if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listarg), "reverse()"); - } else if ((l = argvars[0].vval.v_list) != NULL - && !tv_check_lock(l->lv_lock, N_("reverse() argument"), - TV_TRANSLATE)) { + } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + N_("reverse() argument"), TV_TRANSLATE)) { + // FIXME: Abstract the below away or move to eval/typval. listitem_T *li = l->lv_last; l->lv_first = l->lv_last = NULL; l->lv_len = 0; @@ -13537,7 +13566,7 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) } rettv->vval.v_list = l; rettv->v_type = VAR_LIST; - l->lv_refcount++; + tv_list_ref(l); l->lv_idx = l->lv_len - l->lv_idx - 1; } } @@ -13836,14 +13865,17 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) int argsl = 0; if (argvars[1].v_type == VAR_LIST) { args = argvars[1].vval.v_list; - argsl = args->lv_len; + argsl = tv_list_len(args); // Assert that all list items are strings - for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { - if (arg->li_tv.v_type != VAR_STRING) { - EMSG(_(e_invarg)); + int i = 0; + TV_LIST_ITER_CONST(args, arg, { + if (TV_LIST_ITEM_TV(arg)->v_type != VAR_STRING) { + emsgf(_("E5010: List item %d of the second argument is not a string"), + i); return; } - } + i++; + }); } if (argvars[0].vval.v_string == NULL || argvars[0].vval.v_string[0] == NUL) { @@ -13861,9 +13893,9 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) int i = 1; // Copy arguments to the vector if (argsl > 0) { - for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { - argv[i++] = xstrdup(tv_get_string(&arg->li_tv)); - } + TV_LIST_ITER_CONST(args, arg, { + argv[i++] = xstrdup(tv_get_string(TV_LIST_ITEM_TV(arg))); + }); } // The last item of argv must be NULL @@ -14292,9 +14324,9 @@ static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) list_T *l = tv_list_alloc_ret(rettv); for (size_t i = 0; i < n; i++) { listitem_T *li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_STRING; - li->li_tv.v_lock = 0; - li->li_tv.vval.v_string = (char_u *)addrs[i]; + TV_LIST_ITEM_TV(li)->v_type = VAR_STRING; + TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; + TV_LIST_ITEM_TV(li)->vval.v_string = (char_u *)addrs[i]; tv_list_append(l, li); } xfree(addrs); @@ -14503,7 +14535,7 @@ static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *line = NULL; if (argvars[1].v_type == VAR_LIST) { l = argvars[1].vval.v_list; - li = l->lv_first; + li = tv_list_first(l); } else { line = tv_get_string_chk(&argvars[1]); } @@ -14515,8 +14547,8 @@ static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (li == NULL) { break; } - line = tv_get_string_chk(&li->li_tv); - li = li->li_next; + line = tv_get_string_chk(TV_LIST_ITEM_TV(li)); + li = TV_LIST_ITEM_NEXT(l, li); } rettv->vval.v_number = 1; /* FAIL */ @@ -14648,8 +14680,6 @@ static void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - list_T *l; - listitem_T *li; dict_T *d; list_T *s = NULL; @@ -14658,15 +14688,17 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG(_(e_listreq)); return; } + list_T *l; if ((l = argvars[0].vval.v_list) != NULL) { - /* To some extent make sure that we are dealing with a list from - * "getmatches()". */ - li = l->lv_first; - while (li != NULL) { - if (li->li_tv.v_type != VAR_DICT - || (d = li->li_tv.vval.v_dict) == NULL) { - EMSG(_(e_invarg)); + // To some extent make sure that we are dealing with a list from + // "getmatches()". + int i = 0; + TV_LIST_ITER_CONST(l, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT + || (d = TV_LIST_ITEM_TV(li)->vval.v_dict) == NULL) { + emsgf(_("E474: List item %d is either not a dictionary " + "or an empty one"), i); return; } if (!(tv_dict_find(d, S_LEN("group")) != NULL @@ -14674,19 +14706,18 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) || tv_dict_find(d, S_LEN("pos1")) != NULL) && tv_dict_find(d, S_LEN("priority")) != NULL && tv_dict_find(d, S_LEN("id")) != NULL)) { - EMSG(_(e_invarg)); + emsgf(_("E474: List item %d is missing one of the required keys"), i); return; } - li = li->li_next; - } + i++; + }); clear_matches(curwin); - li = l->lv_first; bool match_add_failed = false; - while (li != NULL) { + TV_LIST_ITER_CONST(l, li, { int i = 0; - d = li->li_tv.vval.v_dict; + d = TV_LIST_ITEM_TV(li)->vval.v_dict; dictitem_T *const di = tv_dict_find(d, S_LEN("pattern")); if (di == NULL) { if (s == NULL) { @@ -14704,7 +14735,7 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) } tv_list_append_tv(s, &pos_di->di_tv); - s->lv_refcount++; + tv_list_ref(s); } else { break; } @@ -14739,8 +14770,7 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_list_unref(s); s = NULL; } - li = li->li_next; - } + }); if (!match_add_failed) { rettv->vval.v_number = 0; } @@ -14860,7 +14890,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[1].v_type == VAR_LIST) { list_T *ll = argvars[1].vval.v_list; // If the list is NULL handle like an empty list. - int len = ll == NULL ? 0 : ll->lv_len; + const int len = tv_list_len(ll); // First half: use for pointers to result lines; second half: use for // pointers to allocated copies. @@ -14869,11 +14899,9 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) char **allocval = lstval + len + 2; char **curallocval = allocval; - for (listitem_T *li = ll == NULL ? NULL : ll->lv_first; - li != NULL; - li = li->li_next) { + TV_LIST_ITER_CONST(ll, li, { char buf[NUMBUFLEN]; - *curval = tv_get_string_buf_chk(&li->li_tv, buf); + *curval = tv_get_string_buf_chk(TV_LIST_ITEM_TV(li), buf); if (*curval == NULL) { goto free_lstval; } @@ -14885,7 +14913,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) curallocval++; } curval++; - } + }); *curval++ = NULL; write_reg_contents_lst(regname, (char_u **)lstval, append, yank_type, @@ -15135,8 +15163,8 @@ static int item_compare(const void *s1, const void *s2, bool keep_zero) sortItem_T *const si1 = (sortItem_T *)s1; sortItem_T *const si2 = (sortItem_T *)s2; - typval_T *const tv1 = &si1->item->li_tv; - typval_T *const tv2 = &si2->item->li_tv; + typval_T *const tv1 = TV_LIST_ITEM_TV(si1->item); + typval_T *const tv2 = TV_LIST_ITEM_TV(si2->item); int res; @@ -15249,8 +15277,8 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) // Copy the values. This is needed to be able to set v_lock to VAR_FIXED // in the copy without changing the original list items. - tv_copy(&si1->item->li_tv, &argv[0]); - tv_copy(&si2->item->li_tv, &argv[1]); + tv_copy(TV_LIST_ITEM_TV(si1->item), &argv[0]); + tv_copy(TV_LIST_ITEM_TV(si2->item), &argv[1]); rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this res = call_func((const char_u *)func_name, @@ -15294,8 +15322,6 @@ static int item_compare2_not_keeping_zero(const void *s1, const void *s2) */ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) { - list_T *l; - listitem_T *li; sortItem_T *ptrs; long len; long i; @@ -15313,13 +15339,13 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listarg), sort ? "sort()" : "uniq()"); } else { - l = argvars[0].vval.v_list; - if (l == NULL || tv_check_lock(l->lv_lock, arg_errmsg, TV_TRANSLATE)) { + list_T *const l = argvars[0].vval.v_list; + if (tv_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) { goto theend; } rettv->vval.v_list = l; rettv->v_type = VAR_LIST; - ++l->lv_refcount; + tv_list_ref(l); len = tv_list_len(l); if (len <= 1) { @@ -15391,11 +15417,11 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) i = 0; if (sort) { // sort(): ptrs will be the list to sort. - for (li = l->lv_first; li != NULL; li = li->li_next) { + TV_LIST_ITER(l, li, { ptrs[i].item = li; ptrs[i].idx = i; i++; - } + }); info.item_compare_func_err = false; // Test the compare function. @@ -15414,6 +15440,7 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) if (!info.item_compare_func_err) { // Clear the list and append the items in the sorted order. + // FIXME: Somehow abstract away or move to eval/typval. l->lv_first = NULL; l->lv_last = NULL; l->lv_idx_item = NULL; @@ -15431,25 +15458,30 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) info.item_compare_func_err = false; if (info.item_compare_func != NULL || info.item_compare_partial != NULL) { - item_compare_func_ptr = item_compare2_keeping_zero; + item_compare_func_ptr = item_compare2_keeping_zero; } else { - item_compare_func_ptr = item_compare_keeping_zero; + item_compare_func_ptr = item_compare_keeping_zero; } - for (li = l->lv_first; li != NULL && li->li_next != NULL; li = li->li_next) { - if (item_compare_func_ptr(&li, &li->li_next) == 0) { - ptrs[i++].item = li; - } - if (info.item_compare_func_err) { - EMSG(_("E882: Uniq compare function failed")); - break; + listitem_T *prev_li = NULL; + TV_LIST_ITER(l, li, { + if (prev_li != NULL) { + if (item_compare_func_ptr(prev_li, li) == 0) { + ptrs[i++].item = prev_li; + } + if (info.item_compare_func_err) { + EMSG(_("E882: Uniq compare function failed")); + break; + } } - } + prev_li = li; + }); if (!info.item_compare_func_err) { while (--i >= 0) { + // FIXME: Abstract away. assert(ptrs[i].item->li_next); - li = ptrs[i].item->li_next; + listitem_T *const li = ptrs[i].item->li_next; ptrs[i].item->li_next = li->li_next; if (li->li_next != NULL) { li->li_next->li_prev = ptrs[i].item; @@ -15592,7 +15624,6 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool typeerr = false; int maxcount; garray_T ga; - listitem_T *li; bool need_capital = false; tv_list_alloc_ret(rettv); @@ -15618,10 +15649,10 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) for (int i = 0; i < ga.ga_len; i++) { char *p = ((char **)ga.ga_data)[i]; - li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_STRING; - li->li_tv.v_lock = 0; - li->li_tv.vval.v_string = (char_u *)p; + listitem_T *const li = tv_list_item_alloc(); + TV_LIST_ITEM_TV(li)->v_type = VAR_STRING; + TV_LIST_ITEM_TV(li)->v_lock = VAR_LOCKED; + TV_LIST_ITEM_TV(li)->vval.v_string = (char_u *)p; tv_list_append(rettv->vval.v_list, li); } ga_clear(&ga); @@ -15677,7 +15708,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else { end = str + strlen(str); } - if (keepempty || end > str || (rettv->vval.v_list->lv_len > 0 + if (keepempty || end > str || (tv_list_len(rettv->vval.v_list) > 0 && *str != NUL && match && end < (const char *)regmatch.endp[0])) { @@ -16405,7 +16436,7 @@ static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, keepempty = tv_get_number(&argvars[2]); } rettv->vval.v_list = string_to_list(res, nread, (bool)keepempty); - rettv->vval.v_list->lv_refcount++; + tv_list_ref(rettv->vval.v_list); rettv->v_type = VAR_LIST; xfree(res); @@ -17451,8 +17482,8 @@ static bool write_list(FileDescriptor *const fp, const list_T *const list, const bool binary) { int error = 0; - for (const listitem_T *li = list->lv_first; li != NULL; li = li->li_next) { - const char *const s = tv_get_string_chk(&li->li_tv); + TV_LIST_ITER_CONST(list, li, { + const char *const s = tv_get_string_chk(TV_LIST_ITEM_TV(li)); if (s == NULL) { return false; } @@ -17479,14 +17510,14 @@ static bool write_list(FileDescriptor *const fp, const list_T *const list, } } } - if (!binary || li->li_next != NULL) { + if (!binary || TV_LIST_ITEM_NEXT(list, li) != NULL) { const ptrdiff_t written = file_write(fp, "\n", 1); if (written < 0) { error = (int)written; goto write_list_error; } } - } + }); if ((error = file_flush(fp)) != 0) { goto write_list_error; } @@ -17499,13 +17530,14 @@ write_list_error: /// Initializes a static list with 10 items. void init_static_list(staticList10_T *sl) { + // FIXME: Move to eval/typval. list_T *l = &sl->sl_list; memset(sl, 0, sizeof(staticList10_T)); l->lv_first = &sl->sl_items[0]; l->lv_last = &sl->sl_items[9]; l->lv_refcount = DO_NOT_FREE_CNT; - l->lv_lock = VAR_FIXED; + tv_list_set_lock(l, VAR_FIXED); sl->sl_list.lv_len = 10; for (int i = 0; i < 10; i++) { @@ -17556,9 +17588,9 @@ static char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) // Pre-calculate the resulting length. *len = 0; list_T *list = tv->vval.v_list; - for (listitem_T *li = list->lv_first; li != NULL; li = li->li_next) { - *len += strlen(tv_get_string(&li->li_tv)) + 1; - } + TV_LIST_ITER_CONST(list, li, { + *len += strlen(tv_get_string(TV_LIST_ITEM_TV(li))) + 1; + }); if (*len == 0) { return NULL; @@ -17566,14 +17598,14 @@ static char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) char *ret = xmalloc(*len + endnl); char *end = ret; - for (listitem_T *li = list->lv_first; li != NULL; li = li->li_next) { - for (const char *s = tv_get_string(&li->li_tv); *s != NUL; s++) { + TV_LIST_ITER_CONST(list, li, { + for (const char *s = tv_get_string(TV_LIST_ITEM_TV(li)); *s != NUL; s++) { *end++ = (*s == '\n') ? NUL : *s; } - if (endnl || li->li_next != NULL) { + if (endnl || TV_LIST_ITEM_NEXT(list, li) != NULL) { *end++ = '\n'; } - } + }); *end = NUL; *len = end - ret; return ret; @@ -17720,9 +17752,9 @@ pos_T *var2fpos(const typval_T *const tv, const int dollar_lnum, // We accept "$" for the column number: last column. li = tv_list_find(l, 1L); - if (li != NULL && li->li_tv.v_type == VAR_STRING - && li->li_tv.vval.v_string != NULL - && STRCMP(li->li_tv.vval.v_string, "$") == 0) { + if (li != NULL && TV_LIST_ITEM_TV(li)->v_type == VAR_STRING + && TV_LIST_ITEM_TV(li)->vval.v_string != NULL + && STRCMP(TV_LIST_ITEM_TV(li)->vval.v_string, "$") == 0) { pos.col = len + 1; } @@ -17799,17 +17831,18 @@ pos_T *var2fpos(const typval_T *const tv, const int dollar_lnum, */ static int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp) { - list_T *l = arg->vval.v_list; + list_T *l; long i = 0; long n; - /* List must be: [fnum, lnum, col, coladd, curswant], where "fnum" is only - * there when "fnump" isn't NULL; "coladd" and "curswant" are optional. */ + // List must be: [fnum, lnum, col, coladd, curswant], where "fnum" is only + // there when "fnump" isn't NULL; "coladd" and "curswant" are optional. if (arg->v_type != VAR_LIST - || l == NULL - || l->lv_len < (fnump == NULL ? 2 : 3) - || l->lv_len > (fnump == NULL ? 4 : 5)) + || (l = arg->vval.v_list) == NULL + || tv_list_len(l) < (fnump == NULL ? 2 : 3) + || tv_list_len(l) > (fnump == NULL ? 4 : 5)) { return FAIL; + } if (fnump != NULL) { n = tv_list_find_nr(l, i++, NULL); // fnum @@ -18234,7 +18267,7 @@ void set_vim_var_list(const VimVarIndex idx, list_T *const val) vimvars[idx].vv_type = VAR_LIST; vimvars[idx].vv_list = val; if (val != NULL) { - val->lv_refcount++; + tv_list_ref(val); } } @@ -19270,9 +19303,10 @@ int var_item_copy(const vimconv_T *const conv, if (from->vval.v_list == NULL) to->vval.v_list = NULL; else if (copyID != 0 && from->vval.v_list->lv_copyID == copyID) { - /* use the copy made earlier */ + // FIXME: Abstract away. + // Use the copy made earlier. to->vval.v_list = from->vval.v_list->lv_copylist; - ++to->vval.v_list->lv_refcount; + tv_list_ref(to->vval.v_list); } else { to->vval.v_list = tv_list_copy(conv, from->vval.v_list, deep, copyID); } @@ -21153,9 +21187,10 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, v->di_tv.v_type = VAR_LIST; v->di_tv.v_lock = VAR_FIXED; v->di_tv.vval.v_list = &fc->l_varlist; + // FIXME: Abstract away static list. memset(&fc->l_varlist, 0, sizeof(list_T)); fc->l_varlist.lv_refcount = DO_NOT_FREE_CNT; - fc->l_varlist.lv_lock = VAR_FIXED; + tv_list_set_lock(&fc->l_varlist, VAR_FIXED); // Set a:firstline to "firstline" and a:lastline to "lastline". // Set a:name to named arguments. @@ -21204,8 +21239,8 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, if (ai >= 0 && ai < MAX_FUNC_ARGS) { tv_list_append(&fc->l_varlist, &fc->l_listitems[ai]); - fc->l_listitems[ai].li_tv = argvars[i]; - fc->l_listitems[ai].li_tv.v_lock = VAR_FIXED; + *TV_LIST_ITEM_TV(&fc->l_listitems[ai]) = argvars[i]; + TV_LIST_ITEM_TV(&fc->l_listitems[ai])->v_lock = VAR_FIXED; } } @@ -21407,10 +21442,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, }); // Make a copy of the a:000 items, since we didn't do that above. - for (listitem_T *li = fc->l_varlist.lv_first; li != NULL; - li = li->li_next) { - tv_copy(&li->li_tv, &li->li_tv); - } + TV_LIST_ITER(&fc->l_varlist, li, { + tv_copy(TV_LIST_ITEM_TV(li), TV_LIST_ITEM_TV(li)); + }); } if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) { @@ -21437,6 +21471,7 @@ static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force) return; } + // FIXME: Abstract away static list implementation details. if (--fc->fc_refcount <= 0 && (force || ( fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT @@ -21475,8 +21510,6 @@ free_funccal ( int free_val /* a: vars were allocated */ ) { - listitem_T *li; - for (int i = 0; i < fc->fc_funcs.ga_len; i++) { ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i]; @@ -21494,14 +21527,14 @@ free_funccal ( // allocated variables. vars_clear_ext(&fc->l_avars.dv_hashtab, free_val); - /* free all l: variables */ + // Free all l: variables. vars_clear(&fc->l_vars.dv_hashtab); // Free the a:000 variables if they were allocated. if (free_val) { - for (li = fc->l_varlist.lv_first; li != NULL; li = li->li_next) { - tv_clear(&li->li_tv); - } + TV_LIST_ITER(&fc->l_varlist, li, { + tv_clear(TV_LIST_ITEM_TV(li)); + }); } func_ptr_unref(fc->func); @@ -22437,7 +22470,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments) {.v_type = VAR_UNKNOWN} }; typval_T rettv = {.v_type = VAR_UNKNOWN, .v_lock = 0}; - arguments->lv_refcount++; + tv_list_ref(arguments); int dummy; (void)call_func((const char_u *)func, diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index 0933b1bf9c..e3098683a4 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -61,7 +61,7 @@ static inline void create_special_dict(typval_T *const rettv, type_di->di_tv.v_type = VAR_LIST; type_di->di_tv.v_lock = VAR_UNLOCKED; type_di->di_tv.vval.v_list = (list_T *) eval_msgpack_type_lists[type]; - type_di->di_tv.vval.v_list->lv_refcount++; + tv_list_ref(type_di->di_tv.vval.v_list); tv_dict_add(dict, type_di); dictitem_T *const val_di = tv_dict_item_alloc_len(S_LEN("_VAL")); val_di->di_tv = val; @@ -234,7 +234,7 @@ list_T *decode_create_map_special_dict(typval_T *const ret_tv) FUNC_ATTR_NONNULL_ALL { list_T *const list = tv_list_alloc(); - list->lv_refcount++; + tv_list_ref(list); create_special_dict(ret_tv, kMPMap, ((typval_T) { .v_type = VAR_LIST, .v_lock = VAR_UNLOCKED, @@ -270,7 +270,7 @@ typval_T decode_string(const char *const s, const size_t len, : (bool)hasnul); if (really_hasnul) { list_T *const list = tv_list_alloc(); - list->lv_refcount++; + tv_list_ref(list); typval_T tv; create_special_dict(&tv, binary ? kMPBinary : kMPString, ((typval_T) { .v_type = VAR_LIST, @@ -849,7 +849,7 @@ json_decode_string_cycle_start: } case '[': { list_T *list = tv_list_alloc(); - list->lv_refcount++; + tv_list_ref(list); typval_T tv = { .v_type = VAR_LIST, .v_lock = VAR_UNLOCKED, @@ -970,7 +970,7 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) }; } else { list_T *const list = tv_list_alloc(); - list->lv_refcount++; + tv_list_ref(list); create_special_dict(rettv, kMPInteger, ((typval_T) { .v_type = VAR_LIST, .v_lock = VAR_UNLOCKED, @@ -993,7 +993,7 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) }; } else { list_T *const list = tv_list_alloc(); - list->lv_refcount++; + tv_list_ref(list); create_special_dict(rettv, kMPInteger, ((typval_T) { .v_type = VAR_LIST, .v_lock = VAR_UNLOCKED, @@ -1039,7 +1039,7 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) } case MSGPACK_OBJECT_ARRAY: { list_T *const list = tv_list_alloc(); - list->lv_refcount++; + tv_list_ref(list); *rettv = (typval_T) { .v_type = VAR_LIST, .v_lock = VAR_UNLOCKED, @@ -1110,7 +1110,7 @@ msgpack_to_vim_generic_map: {} } case MSGPACK_OBJECT_EXT: { list_T *const list = tv_list_alloc(); - list->lv_refcount++; + tv_list_ref(list); tv_list_append_number(list, mobj.via.ext.type); list_T *const ext_val_list = tv_list_alloc(); tv_list_append_list(list, ext_val_list); diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 4bc3a85efb..eaf70b41fd 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -66,7 +66,7 @@ listitem_T *tv_list_item_alloc(void) void tv_list_item_free(listitem_T *const item) FUNC_ATTR_NONNULL_ALL { - tv_clear(&item->li_tv); + tv_clear(TV_LIST_ITEM_TV(item)); xfree(item); } @@ -326,7 +326,7 @@ void tv_list_append_tv(list_T *const l, typval_T *const tv) FUNC_ATTR_NONNULL_ALL { listitem_T *const li = tv_list_item_alloc(); - tv_copy(tv, &li->li_tv); + tv_copy(tv, TV_LIST_ITEM_TV(li)); tv_list_append(l, li); } @@ -339,12 +339,12 @@ void tv_list_append_list(list_T *const list, list_T *const itemlist) { listitem_T *const li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_LIST; - li->li_tv.v_lock = VAR_UNLOCKED; - li->li_tv.vval.v_list = itemlist; + TV_LIST_ITEM_TV(li)->v_type = VAR_LIST; + TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; + TV_LIST_ITEM_TV(li)->vval.v_list = itemlist; tv_list_append(list, li); if (itemlist != NULL) { - itemlist->lv_refcount++; + tv_list_ref(itemlist); } } @@ -357,9 +357,9 @@ void tv_list_append_dict(list_T *const list, dict_T *const dict) { listitem_T *const li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_DICT; - li->li_tv.v_lock = VAR_UNLOCKED; - li->li_tv.vval.v_dict = dict; + TV_LIST_ITEM_TV(li)->v_type = VAR_DICT; + TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; + TV_LIST_ITEM_TV(li)->vval.v_dict = dict; tv_list_append(list, li); if (dict != NULL) { dict->dv_refcount++; @@ -399,9 +399,9 @@ void tv_list_append_allocated_string(list_T *const l, char *const str) listitem_T *const li = tv_list_item_alloc(); tv_list_append(l, li); - li->li_tv.v_type = VAR_STRING; - li->li_tv.v_lock = VAR_UNLOCKED; - li->li_tv.vval.v_string = (char_u *)str; + TV_LIST_ITEM_TV(li)->v_type = VAR_STRING; + TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; + TV_LIST_ITEM_TV(li)->vval.v_string = (char_u *)str; } /// Append number to the list @@ -412,9 +412,9 @@ void tv_list_append_allocated_string(list_T *const l, char *const str) void tv_list_append_number(list_T *const l, const varnumber_T n) { listitem_T *const li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_NUMBER; - li->li_tv.v_lock = VAR_UNLOCKED; - li->li_tv.vval.v_number = n; + TV_LIST_ITEM_TV(li)->v_type = VAR_NUMBER; + TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; + TV_LIST_ITEM_TV(li)->vval.v_number = n; tv_list_append(l, li); } @@ -439,33 +439,32 @@ list_T *tv_list_copy(const vimconv_T *const conv, list_T *const orig, } list_T *copy = tv_list_alloc(); + tv_list_ref(copy); if (copyID != 0) { // Do this before adding the items, because one of the items may // refer back to this list. orig->lv_copyID = copyID; orig->lv_copylist = copy; } - listitem_T *item; - for (item = orig->lv_first; item != NULL && !got_int; - item = item->li_next) { + TV_LIST_ITER(orig, item, { listitem_T *const ni = tv_list_item_alloc(); if (deep) { - if (var_item_copy(conv, &item->li_tv, &ni->li_tv, deep, copyID) == FAIL) { + if (var_item_copy(conv, TV_LIST_ITEM_TV(item), TV_LIST_ITEM_TV(ni), + deep, copyID) == FAIL) { xfree(ni); - break; + goto tv_list_copy_error; } } else { - tv_copy(&item->li_tv, &ni->li_tv); + tv_copy(TV_LIST_ITEM_TV(item), TV_LIST_ITEM_TV(ni)); } tv_list_append(copy, ni); - } - copy->lv_refcount++; - if (item != NULL) { - tv_list_unref(copy); - copy = NULL; - } + }); return copy; + +tv_list_copy_error: + tv_list_unref(copy); + return NULL; } /// Extend first list with the second @@ -475,17 +474,17 @@ list_T *tv_list_copy(const vimconv_T *const conv, list_T *const orig, /// @param[in] bef If not NULL, extends before this item. void tv_list_extend(list_T *const l1, list_T *const l2, listitem_T *const bef) - FUNC_ATTR_NONNULL_ARG(1, 2) + FUNC_ATTR_NONNULL_ARG(1) { - int todo = l2->lv_len; + int todo = tv_list_len(l2); listitem_T *const befbef = (bef == NULL ? NULL : bef->li_prev); listitem_T *const saved_next = (befbef == NULL ? NULL : befbef->li_next); // We also quit the loop when we have inserted the original item count of // the list, avoid a hang when we extend a list with itself. - for (listitem_T *item = l2->lv_first - ; item != NULL && --todo >= 0 + for (listitem_T *item = tv_list_first(l2) + ; item != NULL && todo-- ; item = (item == befbef ? saved_next : item->li_next)) { - tv_list_insert_tv(l1, &item->li_tv, bef); + tv_list_insert_tv(l1, TV_LIST_ITEM_TV(item), bef); } } @@ -540,13 +539,12 @@ static int list_join_inner(garray_T *const gap, list_T *const l, { size_t sumlen = 0; bool first = true; - listitem_T *item; // Stringify each item in the list. - for (item = l->lv_first; item != NULL && !got_int; item = item->li_next) { + TV_LIST_ITER(l, item, { char *s; size_t len; - s = encode_tv2echo(&item->li_tv, &len); + s = encode_tv2echo(TV_LIST_ITEM_TV(item), &len); if (s == NULL) { return FAIL; } @@ -557,7 +555,7 @@ static int list_join_inner(garray_T *const gap, list_T *const l, p->tofree = p->s = (char_u *)s; line_breakcheck(); - } + }); // Allocate result buffer with its total size, avoid re-allocation and // multiple copy operations. Add 2 for a tailing ']' and NUL. @@ -591,16 +589,16 @@ static int list_join_inner(garray_T *const gap, list_T *const l, /// /// @return OK in case of success, FAIL otherwise. int tv_list_join(garray_T *const gap, list_T *const l, const char *const sep) - FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_NONNULL_ARG(1) { - if (l->lv_len < 1) { + if (!tv_list_len(l)) { return OK; } garray_T join_ga; int retval; - ga_init(&join_ga, (int)sizeof(Join), l->lv_len); + ga_init(&join_ga, (int)sizeof(Join), tv_list_len(l)); retval = list_join_inner(gap, l, sep, &join_ga); #define FREE_JOIN_TOFREE(join) xfree((join)->tofree) @@ -632,11 +630,13 @@ bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic, return false; } - listitem_T *item1 = l1->lv_first; - listitem_T *item2 = l2->lv_first; + listitem_T *item1 = tv_list_first(l1); + listitem_T *item2 = tv_list_first(l2); for (; item1 != NULL && item2 != NULL - ; item1 = item1->li_next, item2 = item2->li_next) { - if (!tv_equal(&item1->li_tv, &item2->li_tv, ic, recursive)) { + ; item1 = TV_LIST_ITEM_NEXT(l1, item1), + item2 = TV_LIST_ITEM_NEXT(n2, item2)) { + if (!tv_equal(TV_LIST_ITEM_TV(item1), TV_LIST_ITEM_TV(item2), ic, + recursive)) { return false; } } @@ -662,13 +662,8 @@ listitem_T *tv_list_find(list_T *const l, int n) return NULL; } - // Negative index is relative to the end. - if (n < 0) { - n = l->lv_len + n; - } - - // Check for index out of range. - if (n < 0 || n >= l->lv_len) { + n = tv_list_uidx(l, n); + if (n == -1) { return NULL; } @@ -740,7 +735,7 @@ varnumber_T tv_list_find_nr(list_T *const l, const int n, bool *const ret_error) } return -1; } - return tv_get_number_chk(&li->li_tv, ret_error); + return tv_get_number_chk(TV_LIST_ITEM_TV(li), ret_error); } /// Get list item l[n] as a string @@ -757,7 +752,7 @@ const char *tv_list_find_str(list_T *const l, const int n) emsgf(_(e_listidx), (int64_t)n); return NULL; } - return tv_get_string(&li->li_tv); + return tv_get_string(TV_LIST_ITEM_TV(li)); } /// Locate item in a list and return its index @@ -772,15 +767,14 @@ long tv_list_idx_of_item(const list_T *const l, const listitem_T *const item) if (l == NULL) { return -1; } - long idx = 0; - const listitem_T *li; - for (li = l->lv_first; li != NULL && li != item; li = li->li_next) { + int idx = 0; + TV_LIST_ITER_CONST(l, li, { + if (li == item) { + return idx; + } idx++; - } - if (li == NULL) { - return -1; - } - return idx; + }); + return -1; } //{{{1 Dictionaries @@ -1339,7 +1333,7 @@ int tv_dict_add_list(dict_T *const d, const char *const key, item->di_tv.v_lock = VAR_UNLOCKED; item->di_tv.v_type = VAR_LIST; item->di_tv.vval.v_list = list; - list->lv_refcount++; + tv_list_ref(list); if (tv_dict_add(d, item) == FAIL) { tv_dict_item_free(item); return FAIL; @@ -1677,7 +1671,7 @@ list_T *tv_list_alloc_ret(typval_T *const ret_tv) ret_tv->vval.v_list = l; ret_tv->v_type = VAR_LIST; ret_tv->v_lock = VAR_UNLOCKED; - l->lv_refcount++; + tv_list_ref(l); return l; } @@ -2027,7 +2021,7 @@ void tv_copy(typval_T *const from, typval_T *const to) } case VAR_LIST: { if (from->vval.v_list != NULL) { - to->vval.v_list->lv_refcount++; + tv_list_ref(to->vval.v_list); } break; } @@ -2084,9 +2078,9 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock) CHANGE_LOCK(lock, l->lv_lock); if (deep < 0 || deep > 1) { // Recursive: lock/unlock the items the List contains. - for (listitem_T *li = l->lv_first; li != NULL; li = li->li_next) { - tv_item_lock(&li->li_tv, deep - 1, lock); - } + TV_LIST_ITER(l, li, { + tv_item_lock(TV_LIST_ITEM_TV(li), deep - 1, lock); + }); } } break; @@ -2122,6 +2116,8 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock) /// Check whether VimL value is locked itself or refers to a locked container /// +/// @warning Fixed container is not the same as locked. +/// /// @param[in] tv Value to check. /// /// @return True if value is locked, false otherwise. @@ -2130,8 +2126,7 @@ bool tv_islocked(const typval_T *const tv) { return ((tv->v_lock == VAR_LOCKED) || (tv->v_type == VAR_LIST - && tv->vval.v_list != NULL - && (tv->vval.v_list->lv_lock == VAR_LOCKED)) + && (tv_list_locked(tv->vval.v_list) == VAR_LOCKED)) || (tv->v_type == VAR_DICT && tv->vval.v_dict != NULL && (tv->vval.v_dict->dv_lock == VAR_LOCKED))); diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index b00f43f625..2a0c52b4be 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "nvim/types.h" #include "nvim/hashtab.h" @@ -284,9 +285,6 @@ typedef struct list_stack_S { #define TV_DICT_HI2DI(hi) \ ((dictitem_T *)((hi)->hi_key - offsetof(dictitem_T, di_key))) -static inline void tv_list_ref(list_T *const l) - REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; - /// Increase reference count for a given list /// /// Does nothing for NULL lists. @@ -332,13 +330,13 @@ static inline void tv_list_set_lock(list_T *const l, l->lv_lock = lock; } -static inline long tv_list_len(const list_T *const l) +static inline int tv_list_len(const list_T *const l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Get the number of items in a list /// /// @param[in] l List to check. -static inline long tv_list_len(const list_T *const l) +static inline int tv_list_len(const list_T *const l) { if (l == NULL) { return 0; @@ -346,6 +344,29 @@ static inline long tv_list_len(const list_T *const l) return l->lv_len; } +static inline int tv_list_uidx(const list_T *const l, int n) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Normalize index: that is, return either -1 or non-negative index +/// +/// @param[in] l List to intex. Used to get length. +/// @param[in] n List index, possibly negative. +/// +/// @return -1 or list index in range [0, tv_list_len(l)). +static inline int tv_list_uidx(const list_T *const l, int n) +{ + // Negative index is relative to the end. + if (n < 0) { + n += tv_list_len(l); + } + + // Check for index out of range. + if (n < 0 || n >= tv_list_len(l)) { + return -1; + } + return n; +} + static inline listitem_T *tv_list_first(const list_T *const l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; @@ -427,7 +448,7 @@ extern bool tv_in_free_unref_items; do { \ modifier list_T *const l_ = (l); \ if (l_ != NULL) { \ - for (modifier listitem_T *const li = l_->lv_first; \ + for (modifier listitem_T *li = l_->lv_first; \ li != NULL; li = li->li_next) { \ code \ } \ diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index cacba3ce87..18134c56e3 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -306,7 +306,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(); - cur.tv->vval.v_list->lv_refcount++; + tv_list_ref(cur.tv->vval.v_list); if (table_props.maxidx != 0) { cur.container = true; cur.idx = lua_gettop(lstate); diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 432c9a8b47..9f445204d0 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -2568,7 +2568,7 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) for (size_t i = 0; i < reg->y_size; i++) { tv_list_append_string(list, (const char *)reg->y_array[i], -1); } - list->lv_lock = VAR_FIXED; + tv_list_set_lock(list, VAR_FIXED); tv_dict_add_list(dict, S_LEN("regcontents"), list); // the register type -- cgit From 21745d72b8c24c7f19dea5d53384da4875c43e74 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 03:50:52 +0300 Subject: eval: Fix inputlist() --- src/nvim/eval.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index edf016fc41..da953999cc 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -11254,7 +11254,7 @@ static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) int selected; int mouse_used; - if (argvars[0].v_type != VAR_LIST || argvars[0].vval.v_list == NULL) { + if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listarg), "inputlist()"); return; } -- cgit From 5c1ddb5078c90f69c7225a7b2e74ccb914dcdd6a Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 03:54:21 +0300 Subject: eval: Fix writefile() --- src/nvim/eval.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index da953999cc..2f908f70a9 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -17480,6 +17480,7 @@ static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// @return true in case of success, false otherwise. static bool write_list(FileDescriptor *const fp, const list_T *const list, const bool binary) + FUNC_ATTR_NONNULL_ARG(1) { int error = 0; TV_LIST_ITER_CONST(list, li, { @@ -17645,9 +17646,6 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG2(_(e_listarg), "writefile()"); return; } - if (argvars[0].vval.v_list == NULL) { - return; - } bool binary = false; bool append = false; -- cgit From 4a5bc6275d09056ff0ccf5a29a878d48bbe58655 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 8 Dec 2017 10:42:30 -0500 Subject: ci: run oldtests in Appveyor #7705 --- src/nvim/testdir/Makefile | 20 +++++++++++++++----- src/nvim/testdir/unix.vim | 6 ++++++ 2 files changed, 21 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 111bd172ef..e1faaccb84 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -2,7 +2,11 @@ # Makefile to run all tests for Vim # -NVIM_PRG ?= ../../../build/bin/nvim +ifeq ($(OS),Windows_NT) + NVIM_PRG ?= ../../../build/bin/nvim.exe +else + NVIM_PRG ?= ../../../build/bin/nvim +endif TMPDIR ?= Xtest-tmpdir SCRIPTSOURCE := ../../../runtime @@ -10,12 +14,9 @@ export SHELL := sh export NVIM_PRG := $(NVIM_PRG) export TMPDIR -SCRIPTS ?= \ - test13.out \ +SCRIPTS_DEFAULT = \ test14.out \ - test17.out \ test24.out \ - test32.out \ test37.out \ test40.out \ test42.out \ @@ -27,6 +28,15 @@ SCRIPTS ?= \ test73.out \ test79.out \ +ifneq ($(OS),Windows_NT) + SCRIPTS_DEFAULTS := $(SCRIPTS_DEFAULT) \ + test17.out \ + test32.out \ + +endif + +SCRIPTS ?= $(SCRIPTS_DEFAULT) + # Tests using runtest.vim. # Keep test_alot*.res as the last one, sort the others. NEW_TESTS ?= \ diff --git a/src/nvim/testdir/unix.vim b/src/nvim/testdir/unix.vim index a7daacf8cf..ce2beff7fe 100644 --- a/src/nvim/testdir/unix.vim +++ b/src/nvim/testdir/unix.vim @@ -2,6 +2,12 @@ " Always use "sh", don't use the value of "$SHELL". set shell=sh +if has('win32') + set shellcmdflag=-c shellxquote= shellxescape= shellquote= + let &shellredir = '>%s 2>&1' + set shellslash +endif + " Don't depend on system locale, always use utf-8 set encoding=utf-8 -- cgit From 1f2b35860f755513a58c1d010f082d946c889b47 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Thu, 7 Dec 2017 20:53:02 -0500 Subject: mac: Set $LANG based on the system locale Unix's typical locale-related environment variables aren't always set appropriately on a Mac. Instead of relying on them, query the locale information using Mac specific APIs and then set $LANG appropriately for the rest of nvim. Closes #5873 --- src/nvim/CMakeLists.txt | 3 +++ src/nvim/option.c | 3 +++ src/nvim/os/lang.c | 40 ++++++++++++++++++++++++++++++++++++++++ src/nvim/os/lang.h | 7 +++++++ 4 files changed, 53 insertions(+) create mode 100644 src/nvim/os/lang.c create mode 100644 src/nvim/os/lang.h (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index d3e07326bf..7eb1afa135 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -13,6 +13,9 @@ endif() if(WIN32) # tell MinGW compiler to enable wmain set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -municode") +elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework CoreFoundation") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -framework CoreFoundation") endif() set(TOUCHES_DIR ${PROJECT_BINARY_DIR}/touches) diff --git a/src/nvim/option.c b/src/nvim/option.c index 37c4233142..f8a05f133d 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -75,6 +75,7 @@ #include "nvim/window.h" #include "nvim/os/os.h" #include "nvim/os/input.h" +#include "nvim/os/lang.h" /* * The options that are local to a window or buffer have "indir" set to one of @@ -784,6 +785,8 @@ void set_init_1(void) didset_options2(); + lang_init(); + // enc_locale() will try to find the encoding of the current locale. // This will be used when 'default' is used as encoding specifier // in 'fileencodings' diff --git a/src/nvim/os/lang.c b/src/nvim/os/lang.c new file mode 100644 index 0000000000..f0bbf4b1cb --- /dev/null +++ b/src/nvim/os/lang.c @@ -0,0 +1,40 @@ +#ifdef __APPLE__ +# define Boolean CFBoolean // Avoid conflict with API's Boolean +# include +# include +# undef Boolean +#endif + +#ifdef HAVE_LOCALE_H +# include +#endif +#include "nvim/os/os.h" + +void lang_init(void) +{ +#ifdef __APPLE__ + if (os_getenv("LANG") == NULL) { + CFLocaleRef cf_locale = CFLocaleCopyCurrent(); + CFTypeRef cf_lang_region = CFLocaleGetValue(cf_locale, + kCFLocaleIdentifier); + CFRetain(cf_lang_region); + CFRelease(cf_locale); + + const char *lang_region = CFStringGetCStringPtr(cf_lang_region, + kCFStringEncodingUTF8); + if (lang_region) { + os_setenv("LANG", lang_region, true); + } else { + char buf[20] = { 0 }; + if (CFStringGetCString(cf_lang_region, buf, 20, + kCFStringEncodingUTF8)) { + os_setenv("LANG", lang_region, true); + } + } + CFRelease(cf_lang_region); +# ifdef HAVE_LOCALE_H + setlocale(LC_ALL, ""); +# endif + } +#endif +} diff --git a/src/nvim/os/lang.h b/src/nvim/os/lang.h new file mode 100644 index 0000000000..f60e064f57 --- /dev/null +++ b/src/nvim/os/lang.h @@ -0,0 +1,7 @@ +#ifndef NVIM_OS_LANG_H +#define NVIM_OS_LANG_H + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/lang.h.generated.h" +#endif +#endif // NVIM_OS_LANG_H -- cgit From abe38f7d26d68d7032ea391c039c56c8b87675a5 Mon Sep 17 00:00:00 2001 From: glacambre Date: Sun, 26 Nov 2017 13:29:32 +0100 Subject: window.c: do BufEnter in correct window after closing help #7431 closes #7429 Problem: after a help window was closed, a window was selected and its autocommands triggered. After that, restore_snapshot was called and the focused window changed, confusing the user. Solution: Add function get_snapshot_focus() that returns the window that holds the cursor in a snapshot. Use this function in win_close to make sure the right window is selected before any autocommand is triggered. --- src/nvim/window.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'src') diff --git a/src/nvim/window.c b/src/nvim/window.c index 4e4eb297aa..e39569321e 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1991,6 +1991,14 @@ int win_close(win_T *win, int free_buf) * the screen space. */ wp = win_free_mem(win, &dir, NULL); + if (help_window) { + // Closing the help window moves the cursor back to the original window. + win_T *tmpwp = get_snapshot_focus(SNAP_HELP_IDX); + if (tmpwp != NULL) { + wp = tmpwp; + } + } + /* Make sure curwin isn't invalid. It can cause severe trouble when * printing an error message. For win_equal() curbuf needs to be valid * too. */ @@ -5421,6 +5429,27 @@ static win_T *restore_snapshot_rec(frame_T *sn, frame_T *fr) return wp; } +/// Gets the focused window (the one holding the cursor) of the snapshot. +static win_T *get_snapshot_focus(int idx) +{ + if (curtab->tp_snapshot[idx] == NULL) { + return NULL; + } + + frame_T *sn = curtab->tp_snapshot[idx]; + // This should be equivalent to the recursive algorithm found in + // restore_snapshot as far as traveling nodes go. + while (sn->fr_child != NULL || sn->fr_next != NULL) { + while (sn->fr_child != NULL) { + sn = sn->fr_child; + } + if (sn->fr_next != NULL) { + sn = sn->fr_next; + } + } + + return sn->fr_win; +} /* * Set "win" to be the curwin and "tp" to be the current tab page. -- cgit From dc232b74fb1a7927d6c13bd8c80bb42fc18a859f Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 10 Dec 2017 01:24:54 +0100 Subject: doc: hack to avoid doxygen bug Use `@cond ` to obscure a section from doxygen. doxygen thinks kvec_withinit_t() is a function. That adds noise to the generated API documentation, and also prevents the following function from being noticed. --- src/nvim/api/vim.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 416e7d22d2..f0db391abe 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -789,6 +789,10 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode) return keymap_array(mode, NULL); } +/// Returns a 2-tuple (Array), where item 0 is the current channel id and item +/// 1 is the |api-metadata| map (Dictionary). +/// +/// @returns 2-tuple [{channel-id}, {api-metadata}] Array nvim_get_api_info(uint64_t channel_id) FUNC_API_SINCE(1) FUNC_API_ASYNC FUNC_API_REMOTE_ONLY { @@ -896,7 +900,9 @@ typedef struct { Object *ret_node_p; } ExprASTConvStackItem; +///@cond DOXYGEN_NOT_A_FUNCTION typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; +///@endcond /// Parse a VimL expression /// -- cgit From ad9c2d3cb97bbc4ba83ce7d23c9df23f9d3d11db Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 14 Nov 2017 10:47:49 +0100 Subject: doc closes #7622 --- src/nvim/api/buffer.c | 4 ++-- src/nvim/tui/tui.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 4b6a88e5fa..fdde28f2bb 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -763,8 +763,8 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) /// or -1 for ungrouped highlight /// @param hl_group Name of the highlight group to use /// @param line Line to highlight (zero-indexed) -/// @param col_start Start of range of columns to highlight -/// @param col_end End of range of columns to highlight, +/// @param col_start Start of (byte-indexed) column range to highlight +/// @param col_end End of (byte-indexed) column range to highlight, /// or -1 to highlight to end of line /// @param[out] err Error details, if any /// @return The src_id that was used diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 6e2a5cbe67..9ff1acf64a 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1529,7 +1529,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, || iterm || iterm_pretending_xterm || teraterm // per TeraTerm "Supported Control Functions" doco // Some linux-type terminals (such as console-terminal-emulator - // from the nosh toolset) implement implement the xterm extension. + // from the nosh toolset) implement the xterm extension. || (linuxvt && (xterm_version || (vte_version > 0) || colorterm)))) { data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss", "\x1b[%p1%d q"); -- cgit From e9504b7fd8cbf781aa9e68c48a4f3d59c633f269 Mon Sep 17 00:00:00 2001 From: ckelsel Date: Fri, 10 Nov 2017 22:51:03 +0800 Subject: vim-patch: NA vim-patch:8.0.0299 NA vim-patch:8.0.0309 NA vim-patch:8.0.0310 NA vim-patch:8.0.0215 NA Problem: When a Cscope line contains CTRL-L a NULL pointer may be used. (Coverity) Solution: Don't check for an emacs tag in a cscope line. https://github.com/vim/vim/commit/e362c3d2c34f2b7ff38b4c3d2a7ff127d2290e09 vim-patch:8.0.0244 NA Problem: When the user sets t_BE empty after startup to disable bracketed paste, this has no direct effect. Solution: When t_BE is made empty write t_BD. When t_BE is made non-empty write the new value. https://github.com/vim/vim/commit/d9c60648e50a82dcb85b8dffb47f6416c3d56972 Some patches were not properly marked in the commit logs. So they are marked here: vim-patch:8.0.1230 vim-patch:8.0.1229 vim-patch:8.0.0618 vim-patch:8.0.0104 vim-patch:8.0.0405 vim-patch:8.0.0400 vim-patch:8.0.0302 vim-patch:8.0.0288 vim-patch:8.0.0285 vim-patch:8.0.0284 vim-patch:8.0.0281 vim-patch:8.0.0279 vim-patch:8.0.0278 vim-patch:8.0.0277 vim-patch:8.0.0276 vim-patch:8.0.0273 vim-patch:8.0.0272 vim-patch:8.0.0271 vim-patch:8.0.0270 vim-patch:8.0.0269 vim-patch:8.0.0268 vim-patch:8.0.0267 vim-patch:8.0.0260 vim-patch:8.0.0257 vim-patch:8.0.0249 vim-patch:8.0.0248 vim-patch:8.0.0246 vim-patch:8.0.0244 vim-patch:8.0.0241 vim-patch:8.0.0240 vim-patch:8.0.0239 vim-patch:8.0.0232 vim-patch:8.0.0221 vim-patch:8.0.0217 vim-patch:8.0.0215 vim-patch:8.0.0213 vim-patch:8.0.0211 vim-patch:8.0.0203 vim-patch:8.0.0199 vim-patch:8.0.0193 vim-patch:8.0.0192 vim-patch:8.0.0191 vim-patch:8.0.0187 vim-patch:8.0.0183 vim-patch:8.0.0180 vim-patch:8.0.0173 vim-patch:8.0.0171 vim-patch:8.0.0170 vim-patch:8.0.0169 vim-patch:8.0.0166 vim-patch:8.0.0163 vim-patch:8.0.0162 vim-patch:8.0.0161 vim-patch:8.0.0152 vim-patch:8.0.0145 vim-patch:8.0.0144 vim-patch:8.0.0141 vim-patch:8.0.0139 vim-patch:8.0.0138 vim-patch:8.0.0130 vim-patch:8.0.0129 vim-patch:8.0.0123 vim-patch:8.0.0122 vim-patch:8.0.0120 vim-patch:8.0.0117 vim-patch:8.0.0115 vim-patch:8.0.0114 vim-patch:8.0.0113 vim-patch:8.0.0109 vim-patch:8.0.0108 vim-patch:8.0.0107 vim-patch:8.0.0105 vim-patch:8.0.0103 vim-patch:8.0.0098 vim-patch:8.0.0097 vim-patch:8.0.0095 vim-patch:8.0.0094 vim-patch:8.0.0093 vim-patch:8.0.0089 vim-patch:8.0.0087 vim-patch:8.0.0082 vim-patch:8.0.0080 vim-patch:8.0.0077 vim-patch:8.0.0076 vim-patch:8.0.0072 vim-patch:8.0.0071 vim-patch:8.0.0070 vim-patch:8.0.0067 vim-patch:8.0.0065 vim-patch:8.0.0063 vim-patch:8.0.0061 vim-patch:8.0.0059 vim-patch:8.0.0055 vim-patch:8.0.0054 vim-patch:8.0.0051 vim-patch:8.0.0050 vim-patch:8.0.0048 vim-patch:8.0.0045 vim-patch:8.0.0039 vim-patch:8.0.0036 vim-patch:8.0.0030 vim-patch:8.0.0029 vim-patch:8.0.0028 vim-patch:8.0.0027 vim-patch:8.0.0024 vim-patch:8.0.0022 vim-patch:8.0.0021 vim-patch:8.0.0018 vim-patch:8.0.0016 vim-patch:8.0.0015 vim-patch:8.0.0014 vim-patch:8.0.0013 vim-patch:8.0.0011 vim-patch:8.0.0010 vim-patch:8.0.0009 vim-patch:8.0.0007 vim-patch:8.0.0005 --- src/nvim/version.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/version.c b/src/nvim/version.c index 1a9c9d73e5..e8cf1d0f0b 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -1012,7 +1012,7 @@ static const int included_patches[] = { 247, // 246 NA 245, - // 244, + // 244 NA 243, 242, // 241 NA @@ -1041,7 +1041,7 @@ static const int included_patches[] = { 218, // 217 NA // 216, - // 215, + // 215 NA // 214, // 213 NA // 212, -- cgit From ac4bbf55f6d6b9b252dd90fe800626850022b690 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 22:04:43 +0300 Subject: *: Hide list implementation in other files as well --- src/nvim/api/private/helpers.c | 2 +- src/nvim/edit.c | 16 ++--- src/nvim/eval/decode.c | 28 +++++---- src/nvim/eval/encode.c | 76 ++++++++++++------------ src/nvim/eval/encode.h | 13 +++-- src/nvim/eval/typval.h | 16 +++++ src/nvim/eval/typval_encode.c.h | 125 ++++++++++++++++++++++++---------------- src/nvim/ex_cmds.c | 14 +++-- src/nvim/ex_getln.c | 43 +++++++------- src/nvim/lua/converter.c | 10 ++-- src/nvim/ops.c | 27 ++++----- src/nvim/quickfix.c | 36 +++++++----- src/nvim/regexp.c | 25 ++++---- src/nvim/shada.c | 21 +++---- src/nvim/spell.c | 11 ++-- src/nvim/window.c | 50 ++++++++-------- 16 files changed, 277 insertions(+), 236 deletions(-) (limited to 'src') diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index e5a1e150f9..4492f8bb93 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -789,7 +789,7 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) Object item = obj.data.array.items[i]; listitem_T *li = tv_list_item_alloc(); - if (!object_to_vim(item, &li->li_tv, err)) { + if (!object_to_vim(item, TV_LIST_ITEM_TV(li), err)) { // cleanup tv_list_item_free(li); tv_list_free(list); diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 859f98d2ad..2fa4a52387 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -3536,19 +3536,19 @@ theend: /* * Add completions from a list. */ -static void ins_compl_add_list(list_T *list) +static void ins_compl_add_list(list_T *const list) { - listitem_T *li; int dir = compl_direction; - /* Go through the List with matches and add each of them. */ - for (li = list->lv_first; li != NULL; li = li->li_next) { - if (ins_compl_add_tv(&li->li_tv, dir) == OK) - /* if dir was BACKWARD then honor it just once */ + // Go through the List with matches and add each of them. + TV_LIST_ITER(list, li, { + if (ins_compl_add_tv(TV_LIST_ITEM_TV(li), dir) == OK) { + // If dir was BACKWARD then honor it just once. dir = FORWARD; - else if (did_emsg) + } else if (did_emsg) { break; - } + } + }); } /* diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index e3098683a4..b2d901462a 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -120,7 +120,7 @@ static inline int json_decoder_pop(ValuesStackItem obj, last_container = kv_last(*container_stack); } if (last_container.container.v_type == VAR_LIST) { - if (last_container.container.vval.v_list->lv_len != 0 + if (tv_list_len(last_container.container.vval.v_list) != 0 && !obj.didcomma) { EMSG2(_("E474: Expected comma before list item: %s"), val_location); tv_clear(&obj.val); @@ -128,7 +128,7 @@ static inline int json_decoder_pop(ValuesStackItem obj, } assert(last_container.special_val == NULL); listitem_T *obj_li = tv_list_item_alloc(); - obj_li->li_tv = obj.val; + *TV_LIST_ITEM_TV(obj_li) = obj.val; tv_list_append(last_container.container.vval.v_list, obj_li); } else if (last_container.stack_index == kv_size(*stack) - 2) { if (!obj.didcolon) { @@ -155,10 +155,10 @@ static inline int json_decoder_pop(ValuesStackItem obj, list_T *const kv_pair = tv_list_alloc(); tv_list_append_list(last_container.special_val, kv_pair); listitem_T *const key_li = tv_list_item_alloc(); - key_li->li_tv = key.val; + *TV_LIST_ITEM_TV(key_li) = key.val; tv_list_append(kv_pair, key_li); listitem_T *const val_li = tv_list_item_alloc(); - val_li->li_tv = obj.val; + *TV_LIST_ITEM_TV(val_li) = obj.val; tv_list_append(kv_pair, val_li); } } else { @@ -738,8 +738,9 @@ json_decode_string_cycle_start: } else if (last_container.special_val == NULL ? (last_container.container.v_type == VAR_DICT ? (DICT_LEN(last_container.container.vval.v_dict) == 0) - : (last_container.container.vval.v_list->lv_len == 0)) - : (last_container.special_val->lv_len == 0)) { + : (tv_list_len(last_container.container.vval.v_list) + == 0)) + : (tv_list_len(last_container.special_val) == 0)) { emsgf(_("E474: Leading comma: %.*s"), LENP(p, e)); goto json_decode_string_fail; } @@ -1047,9 +1048,10 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) }; for (size_t i = 0; i < mobj.via.array.size; i++) { listitem_T *const li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_UNKNOWN; + TV_LIST_ITEM_TV(li)->v_type = VAR_UNKNOWN; tv_list_append(list, li); - if (msgpack_to_vim(mobj.via.array.ptr[i], &li->li_tv) == FAIL) { + if (msgpack_to_vim(mobj.via.array.ptr[i], TV_LIST_ITEM_TV(li)) + == FAIL) { return FAIL; } } @@ -1094,15 +1096,17 @@ msgpack_to_vim_generic_map: {} list_T *const kv_pair = tv_list_alloc(); tv_list_append_list(list, kv_pair); listitem_T *const key_li = tv_list_item_alloc(); - key_li->li_tv.v_type = VAR_UNKNOWN; + TV_LIST_ITEM_TV(key_li)->v_type = VAR_UNKNOWN; tv_list_append(kv_pair, key_li); listitem_T *const val_li = tv_list_item_alloc(); - val_li->li_tv.v_type = VAR_UNKNOWN; + TV_LIST_ITEM_TV(val_li)->v_type = VAR_UNKNOWN; tv_list_append(kv_pair, val_li); - if (msgpack_to_vim(mobj.via.map.ptr[i].key, &key_li->li_tv) == FAIL) { + if (msgpack_to_vim(mobj.via.map.ptr[i].key, TV_LIST_ITEM_TV(key_li)) + == FAIL) { return FAIL; } - if (msgpack_to_vim(mobj.via.map.ptr[i].val, &val_li->li_tv) == FAIL) { + if (msgpack_to_vim(mobj.via.map.ptr[i].val, TV_LIST_ITEM_TV(val_li)) + == FAIL) { return FAIL; } } diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index ef647b3ee4..0e5db84ad5 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -53,17 +53,18 @@ int encode_list_write(void *const data, const char *const buf, const size_t len) list_T *const list = (list_T *) data; const char *const end = buf + len; const char *line_end = buf; - listitem_T *li = list->lv_last; + listitem_T *li = tv_list_last(list); // Continue the last list element if (li != NULL) { line_end = xmemscan(buf, NL, len); if (line_end != buf) { const size_t line_length = (size_t)(line_end - buf); - char *str = (char *)li->li_tv.vval.v_string; + char *str = (char *)TV_LIST_ITEM_TV(li)->vval.v_string; const size_t li_len = (str == NULL ? 0 : strlen(str)); - li->li_tv.vval.v_string = xrealloc(str, li_len + line_length + 1); - str = (char *)li->li_tv.vval.v_string + li_len; + TV_LIST_ITEM_TV(li)->vval.v_string = xrealloc( + str, li_len + line_length + 1); + str = (char *)TV_LIST_ITEM_TV(li)->vval.v_string + li_len; memcpy(str, buf, line_length); str[line_length] = 0; memchrsub(str, NUL, NL, line_length); @@ -135,21 +136,18 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, } case kMPConvPairs: case kMPConvList: { - int idx = 0; - const listitem_T *li; - for (li = v.data.l.list->lv_first; - li != NULL && li->li_next != v.data.l.li; - li = li->li_next) { - idx++; - } + const listitem_T *const li = TV_LIST_ITEM_PREV(v.data.l.list, + v.data.l.li); + int idx = (int)tv_list_idx_of_item(v.data.l.list, li); if (v.type == kMPConvList || li == NULL - || (li->li_tv.v_type != VAR_LIST - && li->li_tv.vval.v_list->lv_len <= 0)) { - vim_snprintf((char *) IObuff, IOSIZE, idx_msg, idx); + || (TV_LIST_ITEM_TV(li)->v_type != VAR_LIST + && tv_list_len(TV_LIST_ITEM_TV(li)->vval.v_list) <= 0)) { + vim_snprintf((char *)IObuff, IOSIZE, idx_msg, idx); ga_concat(&msg_ga, IObuff); } else { - typval_T key_tv = li->li_tv.vval.v_list->lv_first->li_tv; + typval_T key_tv = *TV_LIST_ITEM_TV( + tv_list_first(TV_LIST_ITEM_TV(li)->vval.v_list)); char *const key = encode_tv2echo(&key_tv, NULL); vim_snprintf((char *) IObuff, IOSIZE, key_pair_msg, key, idx); xfree(key); @@ -202,21 +200,17 @@ bool encode_vim_list_to_buf(const list_T *const list, size_t *const ret_len, FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT { size_t len = 0; - if (list != NULL) { - for (const listitem_T *li = list->lv_first; - li != NULL; - li = li->li_next) { - if (li->li_tv.v_type != VAR_STRING) { - return false; - } - len++; - if (li->li_tv.vval.v_string != 0) { - len += STRLEN(li->li_tv.vval.v_string); - } + TV_LIST_ITER_CONST(list, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING) { + return false; } - if (len) { - len--; + len++; + if (TV_LIST_ITEM_TV(li)->vval.v_string != 0) { + len += STRLEN(TV_LIST_ITEM_TV(li)->vval.v_string); } + }); + if (len) { + len--; } *ret_len = len; if (len == 0) { @@ -253,31 +247,34 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, char *const buf_end = buf + nbuf; char *p = buf; while (p < buf_end) { - assert(state->li_length == 0 || state->li->li_tv.vval.v_string != NULL); + assert(state->li_length == 0 + || TV_LIST_ITEM_TV(state->li)->vval.v_string != NULL); for (size_t i = state->offset; i < state->li_length && p < buf_end; i++) { - assert(state->li->li_tv.vval.v_string != NULL); - const char ch = (char)state->li->li_tv.vval.v_string[state->offset++]; + assert(TV_LIST_ITEM_TV(state->li)->vval.v_string != NULL); + const char ch = (char)( + TV_LIST_ITEM_TV(state->li)->vval.v_string[state->offset++]); *p++ = (char)((char)ch == (char)NL ? (char)NUL : (char)ch); } if (p < buf_end) { - state->li = state->li->li_next; + state->li = TV_LIST_ITEM_NEXT(state->list, state->li); if (state->li == NULL) { *read_bytes = (size_t) (p - buf); return OK; } *p++ = NL; - if (state->li->li_tv.v_type != VAR_STRING) { + if (TV_LIST_ITEM_TV(state->li)->v_type != VAR_STRING) { *read_bytes = (size_t) (p - buf); return FAIL; } state->offset = 0; - state->li_length = (state->li->li_tv.vval.v_string == NULL + state->li_length = (TV_LIST_ITEM_TV(state->li)->vval.v_string == NULL ? 0 - : STRLEN(state->li->li_tv.vval.v_string)); + : STRLEN(TV_LIST_ITEM_TV(state->li)->vval.v_string)); } } *read_bytes = nbuf; - return (state->offset < state->li_length || state->li->li_next != NULL + return ((state->offset < state->li_length + || TV_LIST_ITEM_NEXT(state->list, state->li) != NULL) ? NOTDONE : OK); } @@ -727,12 +724,11 @@ bool encode_check_json_key(const typval_T *const tv) if (val_di->di_tv.vval.v_list == NULL) { return true; } - for (const listitem_T *li = val_di->di_tv.vval.v_list->lv_first; - li != NULL; li = li->li_next) { - if (li->li_tv.v_type != VAR_STRING) { + TV_LIST_ITER_CONST(val_di->di_tv.vval.v_list, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING) { return false; } - } + }); return true; } diff --git a/src/nvim/eval/encode.h b/src/nvim/eval/encode.h index 9bc665253b..ccea245ab3 100644 --- a/src/nvim/eval/encode.h +++ b/src/nvim/eval/encode.h @@ -33,9 +33,10 @@ int encode_vim_to_echo(garray_T *const packer, /// Structure defining state for read_from_list() typedef struct { + const list_T *const list; ///< List being currently read. const listitem_T *li; ///< Item currently read. - size_t offset; ///< Byte offset inside the read item. - size_t li_length; ///< Length of the string inside the read item. + size_t offset; ///< Byte offset inside the read item. + size_t li_length; ///< Length of the string inside the read item. } ListReaderState; /// Initialize ListReaderState structure @@ -43,11 +44,13 @@ static inline ListReaderState encode_init_lrstate(const list_T *const list) FUNC_ATTR_NONNULL_ALL { return (ListReaderState) { - .li = list->lv_first, + .list = list, + .li = tv_list_first(list), .offset = 0, - .li_length = (list->lv_first->li_tv.vval.v_string == NULL + .li_length = (TV_LIST_ITEM_TV(tv_list_first(list))->vval.v_string == NULL ? 0 - : STRLEN(list->lv_first->li_tv.vval.v_string)), + : STRLEN(TV_LIST_ITEM_TV( + tv_list_first(list))->vval.v_string)), }; } diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 2a0c52b4be..a05abd8a53 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -383,6 +383,22 @@ static inline listitem_T *tv_list_first(const list_T *const l) return l->lv_first; } +static inline listitem_T *tv_list_last(const list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Get last list item +/// +/// @param[in] l List to get item from. +/// +/// @return List item or NULL in case of an empty list. +static inline listitem_T *tv_list_last(const list_T *const l) +{ + if (l == NULL) { + return NULL; + } + return l->lv_last; +} + static inline long tv_dict_len(const dict_T *const d) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index a93ad2dbba..800760cf7b 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -355,14 +355,14 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( break; } case VAR_LIST: { - if (tv->vval.v_list == NULL || tv->vval.v_list->lv_len == 0) { + if (tv->vval.v_list == NULL || tv_list_len(tv->vval.v_list) == 0) { TYPVAL_ENCODE_CONV_EMPTY_LIST(tv); break; } const int saved_copyID = tv->vval.v_list->lv_copyID; _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, copyID, kMPConvList); - TYPVAL_ENCODE_CONV_LIST_START(tv, tv->vval.v_list->lv_len); + TYPVAL_ENCODE_CONV_LIST_START(tv, tv_list_len(tv->vval.v_list)); assert(saved_copyID != copyID && saved_copyID != copyID - 1); _mp_push(*mpstack, ((MPConvStackVal) { .type = kMPConvList, @@ -371,7 +371,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( .data = { .l = { .list = tv->vval.v_list, - .li = tv->vval.v_list->lv_first, + .li = tv_list_first(tv->vval.v_list), }, }, })); @@ -440,23 +440,43 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( // bits is not checked), other unsigned and have at most 31 // non-zero bits (number of bits is not checked). if (val_di->di_tv.v_type != VAR_LIST - || (val_list = val_di->di_tv.vval.v_list) == NULL - || val_list->lv_len != 4 - || val_list->lv_first->li_tv.v_type != VAR_NUMBER - || (sign = val_list->lv_first->li_tv.vval.v_number) == 0 - || val_list->lv_first->li_next->li_tv.v_type != VAR_NUMBER - || (highest_bits = - val_list->lv_first->li_next->li_tv.vval.v_number) < 0 - || val_list->lv_last->li_prev->li_tv.v_type != VAR_NUMBER - || (high_bits = - val_list->lv_last->li_prev->li_tv.vval.v_number) < 0 - || val_list->lv_last->li_tv.v_type != VAR_NUMBER - || (low_bits = val_list->lv_last->li_tv.vval.v_number) < 0) { + || tv_list_len(val_list = val_di->di_tv.vval.v_list) != 4) { goto _convert_one_value_regular_dict; } - uint64_t number = ((uint64_t)(((uint64_t)highest_bits) << 62) - | (uint64_t)(((uint64_t)high_bits) << 31) - | (uint64_t)low_bits); + + const listitem_T *const sign_li = tv_list_first(val_list); + if (TV_LIST_ITEM_TV(sign_li)->v_type != VAR_NUMBER + || (sign = TV_LIST_ITEM_TV(sign_li)->vval.v_number) == 0) { + goto _convert_one_value_regular_dict; + } + + const listitem_T *const highest_bits_li = ( + TV_LIST_ITEM_NEXT(val_list, sign_li)); + if (TV_LIST_ITEM_TV(highest_bits_li)->v_type != VAR_NUMBER + || ((highest_bits + = TV_LIST_ITEM_TV(highest_bits_li)->vval.v_number) + < 0)) { + goto _convert_one_value_regular_dict; + } + + const listitem_T *const high_bits_li = ( + TV_LIST_ITEM_NEXT(val_list, highest_bits_li)); + if (TV_LIST_ITEM_TV(high_bits_li)->v_type != VAR_NUMBER + || ((high_bits = TV_LIST_ITEM_TV(high_bits_li)->vval.v_number) + < 0)) { + goto _convert_one_value_regular_dict; + } + + const listitem_T *const low_bits_li = tv_list_last(val_list); + if (TV_LIST_ITEM_TV(low_bits_li)->v_type != VAR_NUMBER + || ((low_bits = TV_LIST_ITEM_TV(low_bits_li)->vval.v_number) + < 0)) { + goto _convert_one_value_regular_dict; + } + + const uint64_t number = ((uint64_t)(((uint64_t)highest_bits) << 62) + | (uint64_t)(((uint64_t)high_bits) << 31) + | (uint64_t)low_bits); if (sign > 0) { TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, number); } else { @@ -499,8 +519,8 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list, lv_copyID, copyID, kMPConvList); - TYPVAL_ENCODE_CONV_LIST_START(tv, - val_di->di_tv.vval.v_list->lv_len); + TYPVAL_ENCODE_CONV_LIST_START( + tv, tv_list_len(val_di->di_tv.vval.v_list)); assert(saved_copyID != copyID && saved_copyID != copyID - 1); _mp_push(*mpstack, ((MPConvStackVal) { .tv = tv, @@ -509,7 +529,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( .data = { .l = { .list = val_di->di_tv.vval.v_list, - .li = val_di->di_tv.vval.v_list->lv_first, + .li = tv_list_first(val_di->di_tv.vval.v_list), }, }, })); @@ -520,22 +540,21 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( goto _convert_one_value_regular_dict; } list_T *const val_list = val_di->di_tv.vval.v_list; - if (val_list == NULL || val_list->lv_len == 0) { + if (val_list == NULL || tv_list_len(val_list) == 0) { TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, TYPVAL_ENCODE_NODICT_VAR); break; } - for (const listitem_T *li = val_list->lv_first; li != NULL; - li = li->li_next) { - if (li->li_tv.v_type != VAR_LIST - || li->li_tv.vval.v_list->lv_len != 2) { + TV_LIST_ITER_CONST(val_list, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_LIST + || tv_list_len(TV_LIST_ITEM_TV(li)->vval.v_list) != 2) { goto _convert_one_value_regular_dict; } - } + }); const int saved_copyID = val_di->di_tv.vval.v_list->lv_copyID; _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_list, lv_copyID, copyID, kMPConvPairs); TYPVAL_ENCODE_CONV_DICT_START(tv, TYPVAL_ENCODE_NODICT_VAR, - val_list->lv_len); + tv_list_len(val_list)); assert(saved_copyID != copyID && saved_copyID != copyID - 1); _mp_push(*mpstack, ((MPConvStackVal) { .tv = tv, @@ -544,7 +563,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( .data = { .l = { .list = val_list, - .li = val_list->lv_first, + .li = tv_list_first(val_list), }, }, })); @@ -554,18 +573,22 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( const list_T *val_list; varnumber_T type; if (val_di->di_tv.v_type != VAR_LIST - || (val_list = val_di->di_tv.vval.v_list) == NULL - || val_list->lv_len != 2 - || (val_list->lv_first->li_tv.v_type != VAR_NUMBER) - || (type = val_list->lv_first->li_tv.vval.v_number) > INT8_MAX + || tv_list_len((val_list = val_di->di_tv.vval.v_list)) != 2 + || (TV_LIST_ITEM_TV(tv_list_first(val_list))->v_type + != VAR_NUMBER) + || ((type + = TV_LIST_ITEM_TV(tv_list_first(val_list))->vval.v_number) + > INT8_MAX) || type < INT8_MIN - || (val_list->lv_last->li_tv.v_type != VAR_LIST)) { + || (TV_LIST_ITEM_TV(tv_list_last(val_list))->v_type + != VAR_LIST)) { goto _convert_one_value_regular_dict; } size_t len; char *buf; - if (!encode_vim_list_to_buf(val_list->lv_last->li_tv.vval.v_list, - &len, &buf)) { + if (!encode_vim_list_to_buf( + TV_LIST_ITEM_TV(tv_list_last(val_list))->vval.v_list, &len, + &buf)) { goto _convert_one_value_regular_dict; } TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type); @@ -674,11 +697,13 @@ typval_encode_stop_converting_one_item: cur_mpsv->data.l.list->lv_copyID = cur_mpsv->saved_copyID; TYPVAL_ENCODE_CONV_LIST_END(cur_mpsv->tv); continue; - } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { + } else if (cur_mpsv->data.l.li + != tv_list_first(cur_mpsv->data.l.list)) { TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(cur_mpsv->tv); } - tv = &cur_mpsv->data.l.li->li_tv; - cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; + tv = TV_LIST_ITEM_TV(cur_mpsv->data.l.li); + cur_mpsv->data.l.li = TV_LIST_ITEM_NEXT(cur_mpsv->data.l.list, + cur_mpsv->data.l.li); break; } case kMPConvPairs: { @@ -687,24 +712,26 @@ typval_encode_stop_converting_one_item: cur_mpsv->data.l.list->lv_copyID = cur_mpsv->saved_copyID; TYPVAL_ENCODE_CONV_DICT_END(cur_mpsv->tv, TYPVAL_ENCODE_NODICT_VAR); continue; - } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { + } else if (cur_mpsv->data.l.li + != tv_list_first(cur_mpsv->data.l.list)) { TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS( cur_mpsv->tv, TYPVAL_ENCODE_NODICT_VAR); } - const list_T *const kv_pair = cur_mpsv->data.l.li->li_tv.vval.v_list; + const list_T *const kv_pair = ( + TV_LIST_ITEM_TV(cur_mpsv->data.l.li)->vval.v_list); TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK( - encode_vim_to__error_ret, kv_pair->lv_first->li_tv); - if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, - &mpstack, cur_mpsv, - &kv_pair->lv_first->li_tv, - copyID, - objname) == FAIL) { + encode_vim_to__error_ret, *TV_LIST_ITEM_TV(tv_list_first(kv_pair))); + if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE( + TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, cur_mpsv, + TV_LIST_ITEM_TV(tv_list_first(kv_pair)), copyID, objname) + == FAIL) { goto encode_vim_to__error_ret; } TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(cur_mpsv->tv, TYPVAL_ENCODE_NODICT_VAR); - tv = &kv_pair->lv_last->li_tv; - cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; + tv = TV_LIST_ITEM_TV(tv_list_last(kv_pair)); + cur_mpsv->data.l.li = TV_LIST_ITEM_NEXT(cur_mpsv->data.l.list, + cur_mpsv->data.l.li); break; } case kMPConvPartial: { diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 8616508d88..4f54d4c88b 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -6334,7 +6334,6 @@ char_u *skip_vimgrep_pat(char_u *p, char_u **s, int *flags) void ex_oldfiles(exarg_T *eap) { list_T *l = get_vim_var_list(VV_OLDFILES); - listitem_T *li; long nr = 0; if (l == NULL) { @@ -6342,19 +6341,22 @@ void ex_oldfiles(exarg_T *eap) } else { msg_start(); msg_scroll = true; - for (li = l->lv_first; li != NULL && !got_int; li = li->li_next) { + TV_LIST_ITER(l, li, { + if (got_int) { + break; + } nr++; - const char *fname = tv_get_string(&li->li_tv); + const char *fname = tv_get_string(TV_LIST_ITEM_TV(li)); if (!message_filtered((char_u *)fname)) { msg_outnum(nr); MSG_PUTS(": "); - msg_outtrans((char_u *)tv_get_string(&li->li_tv)); + msg_outtrans((char_u *)tv_get_string(TV_LIST_ITEM_TV(li))); msg_clr_eos(); msg_putchar('\n'); ui_flush(); // output one line at a time os_breakcheck(); } - } + }); // Assume "got_int" was set to truncate the listing. got_int = false; @@ -6364,7 +6366,7 @@ void ex_oldfiles(exarg_T *eap) quit_more = false; nr = prompt_for_number(false); msg_starthere(); - if (nr > 0 && nr <= l->lv_len) { + if (nr > 0 && nr <= tv_list_len(l)) { const char *const p = tv_list_find_str(l, nr - 1); if (p == NULL) { return; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 1adc8325f8..fcd230d535 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -2618,20 +2618,20 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) } varnumber_T prev_end = 0; int i = 0; - for (const listitem_T *li = tv.vval.v_list->lv_first; - li != NULL; li = li->li_next, i++) { - if (li->li_tv.v_type != VAR_LIST) { + TV_LIST_ITER_CONST(tv.vval.v_list, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_LIST) { PRINT_ERRMSG(_("E5401: List item %i is not a List"), i); goto color_cmdline_error; } - const list_T *const l = li->li_tv.vval.v_list; + const list_T *const l = TV_LIST_ITEM_TV(li)->vval.v_list; if (tv_list_len(l) != 3) { PRINT_ERRMSG(_("E5402: List item %i has incorrect length: %li /= 3"), i, tv_list_len(l)); goto color_cmdline_error; } bool error = false; - const varnumber_T start = tv_get_number_chk(&l->lv_first->li_tv, &error); + const varnumber_T start = ( + tv_get_number_chk(TV_LIST_ITEM_TV(tv_list_first(l)), &error)); if (error) { goto color_cmdline_error; } else if (!(prev_end <= start && start < colored_ccline->cmdlen)) { @@ -2651,8 +2651,8 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) .attr = 0, })); } - const varnumber_T end = tv_get_number_chk(&l->lv_first->li_next->li_tv, - &error); + const varnumber_T end = tv_get_number_chk( + TV_LIST_ITEM_TV(TV_LIST_ITEM_NEXT(l, tv_list_first(l))), &error); if (error) { goto color_cmdline_error; } else if (!(start < end && end <= colored_ccline->cmdlen)) { @@ -2668,7 +2668,8 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) goto color_cmdline_error; } prev_end = end; - const char *const group = tv_get_string_chk(&l->lv_last->li_tv); + const char *const group = tv_get_string_chk( + TV_LIST_ITEM_TV(tv_list_last(l))); if (group == NULL) { goto color_cmdline_error; } @@ -2679,7 +2680,7 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) .end = end, .attr = attr, })); - } + }); if (prev_end < colored_ccline->cmdlen) { kv_push(ccline_colors->colors, ((CmdlineColorChunk) { .start = prev_end, @@ -5021,24 +5022,24 @@ static int ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, int *num_file, */ static int ExpandUserList(expand_T *xp, int *num_file, char_u ***file) { - list_T *retlist; - listitem_T *li; - garray_T ga; - - retlist = call_user_expand_func((user_expand_func_T)call_func_retlist, xp, - num_file, file); + list_T *const retlist = call_user_expand_func( + (user_expand_func_T)call_func_retlist, xp, num_file, file); if (retlist == NULL) { return FAIL; } + garray_T ga; ga_init(&ga, (int)sizeof(char *), 3); - /* Loop over the items in the list. */ - for (li = retlist->lv_first; li != NULL; li = li->li_next) { - if (li->li_tv.v_type != VAR_STRING || li->li_tv.vval.v_string == NULL) - continue; /* Skip non-string items and empty strings */ + // Loop over the items in the list. + TV_LIST_ITER_CONST(retlist, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING + || TV_LIST_ITEM_TV(li)->vval.v_string == NULL) { + continue; // Skip non-string items and empty strings. + } - GA_APPEND(char_u *, &ga, vim_strsave(li->li_tv.vval.v_string)); - } + GA_APPEND(char *, &ga, xstrdup( + (const char *)TV_LIST_ITEM_TV(li)->vval.v_string)); + }); tv_list_unref(retlist); *file = ga.ga_data; diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 18134c56e3..8303ecdd37 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -214,9 +214,9 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) list_T *const kv_pair = tv_list_alloc(); tv_list_append_list(cur.tv->vval.v_list, kv_pair); listitem_T *const key = tv_list_item_alloc(); - key->li_tv = decode_string(s, len, kTrue, false, false); + *TV_LIST_ITEM_TV(key) = decode_string(s, len, kTrue, false, false); tv_list_append(kv_pair, key); - if (key->li_tv.v_type == VAR_UNKNOWN) { + if (TV_LIST_ITEM_TV(key)->v_type == VAR_UNKNOWN) { ret = false; tv_list_unref(kv_pair); continue; @@ -224,7 +224,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) listitem_T *const val = tv_list_item_alloc(); tv_list_append(kv_pair, val); kv_push(stack, cur); - cur = (TVPopStackItem) { &val->li_tv, false, false, 0 }; + cur = (TVPopStackItem) { TV_LIST_ITEM_TV(val), false, false, 0 }; } else { dictitem_T *const di = tv_dict_item_alloc_len(s, len); if (tv_dict_add(cur.tv->vval.v_dict, di) == FAIL) { @@ -239,7 +239,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) } } else { assert(cur.tv->v_type == VAR_LIST); - lua_rawgeti(lstate, -1, cur.tv->vval.v_list->lv_len + 1); + lua_rawgeti(lstate, -1, tv_list_len(cur.tv->vval.v_list) + 1); if (lua_isnil(lstate, -1)) { lua_pop(lstate, 2); continue; @@ -247,7 +247,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) listitem_T *const li = tv_list_item_alloc(); tv_list_append(cur.tv->vval.v_list, li); kv_push(stack, cur); - cur = (TVPopStackItem) { &li->li_tv, false, false, 0 }; + cur = (TVPopStackItem) { TV_LIST_ITEM_TV(li), false, false, 0 }; } } assert(!cur.container); diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 9f445204d0..3cc73b2e40 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -4854,9 +4854,8 @@ static void *get_reg_wrap_one_line(char_u *s, int flags) if (!(flags & kGRegList)) { return s; } - list_T *list = tv_list_alloc(); - tv_list_append_string(list, NULL, 0); - list->lv_first->li_tv.vval.v_string = s; + list_T *const list = tv_list_alloc(); + tv_list_append_allocated_string(list, (char *)s); return list; } @@ -5610,12 +5609,13 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet) list_T *res = result.vval.v_list; list_T *lines = NULL; - if (res->lv_len == 2 && res->lv_first->li_tv.v_type == VAR_LIST) { - lines = res->lv_first->li_tv.vval.v_list; - if (res->lv_last->li_tv.v_type != VAR_STRING) { + if (tv_list_len(res) == 2 + && TV_LIST_ITEM_TV(tv_list_first(res))->v_type == VAR_LIST) { + lines = TV_LIST_ITEM_TV(tv_list_first(res))->vval.v_list; + if (TV_LIST_ITEM_TV(tv_list_last(res))->v_type != VAR_STRING) { goto err; } - char_u *regtype = res->lv_last->li_tv.vval.v_string; + char_u *regtype = TV_LIST_ITEM_TV(tv_list_last(res))->vval.v_string; if (regtype == NULL || strlen((char*)regtype) > 1) { goto err; } @@ -5641,20 +5641,21 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet) reg->y_type = kMTUnknown; } - reg->y_array = xcalloc((size_t)lines->lv_len, sizeof(uint8_t *)); - reg->y_size = (size_t)lines->lv_len; + reg->y_array = xcalloc((size_t)tv_list_len(lines), sizeof(char_u *)); + reg->y_size = (size_t)tv_list_len(lines); reg->additional_data = NULL; reg->timestamp = 0; // Timestamp is not saved for clipboard registers because clipboard registers // are not saved in the ShaDa file. int i = 0; - for (listitem_T *li = lines->lv_first; li != NULL; li = li->li_next) { - if (li->li_tv.v_type != VAR_STRING) { + TV_LIST_ITER_CONST(lines, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING) { goto err; } - reg->y_array[i++] = (char_u *)xstrdupnul((char *)li->li_tv.vval.v_string); - } + reg->y_array[i++] = (char_u *)xstrdupnul( + (const char *)TV_LIST_ITEM_TV(li)->vval.v_string); + }); if (reg->y_size > 0 && strlen((char*)reg->y_array[reg->y_size-1]) == 0) { // a known-to-be charwise yank might have a final linebreak diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 1fc585f0c9..56d11c21ab 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -156,6 +156,7 @@ typedef struct { FILE *fd; typval_T *tv; char_u *p_str; + list_T *p_list; listitem_T *p_li; buf_T *buf; linenr_T buflnum; @@ -516,17 +517,17 @@ static int qf_get_next_list_line(qfstate_T *state) // Get the next line from the supplied list while (p_li != NULL - && (p_li->li_tv.v_type != VAR_STRING - || p_li->li_tv.vval.v_string == NULL)) { - p_li = p_li->li_next; // Skip non-string items + && (TV_LIST_ITEM_TV(p_li)->v_type != VAR_STRING + || TV_LIST_ITEM_TV(p_li)->vval.v_string == NULL)) { + p_li = TV_LIST_ITEM_NEXT(state->p_list, p_li); // Skip non-string items. } - if (p_li == NULL) { // End of the list + if (p_li == NULL) { // End of the list. state->p_li = NULL; return QF_END_OF_INPUT; } - len = STRLEN(p_li->li_tv.vval.v_string); + len = STRLEN(TV_LIST_ITEM_TV(p_li)->vval.v_string); if (len > IOSIZE - 2) { state->linebuf = qf_grow_linebuf(state, len); } else { @@ -534,9 +535,10 @@ static int qf_get_next_list_line(qfstate_T *state) state->linelen = len; } - STRLCPY(state->linebuf, p_li->li_tv.vval.v_string, state->linelen + 1); + STRLCPY(state->linebuf, TV_LIST_ITEM_TV(p_li)->vval.v_string, + state->linelen + 1); - state->p_li = p_li->li_next; // next item + state->p_li = TV_LIST_ITEM_NEXT(state->p_list, p_li); return QF_OK; } @@ -983,7 +985,7 @@ qf_init_ext( ) { qfstate_T state = { NULL, 0, NULL, 0, NULL, NULL, NULL, NULL, - NULL, 0, 0 }; + NULL, NULL, 0, 0 }; qffields_T fields = { NULL, NULL, 0, 0L, 0, false, NULL, 0, 0, 0 }; qfline_T *old_last = NULL; bool adding = false; @@ -1064,7 +1066,8 @@ qf_init_ext( if (tv->v_type == VAR_STRING) { state.p_str = tv->vval.v_string; } else if (tv->v_type == VAR_LIST) { - state.p_li = tv->vval.v_list->lv_first; + state.p_list = tv->vval.v_list; + state.p_li = tv_list_first(tv->vval.v_list); } state.tv = tv; } @@ -4100,7 +4103,6 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) static int qf_add_entries(qf_info_T *qi, list_T *list, char_u *title, int action) { - listitem_T *li; dict_T *d; qfline_T *old_last = NULL; int retval = OK; @@ -4117,13 +4119,15 @@ static int qf_add_entries(qf_info_T *qi, list_T *list, char_u *title, qf_store_title(qi, title); } - for (li = list->lv_first; li != NULL; li = li->li_next) { - if (li->li_tv.v_type != VAR_DICT) - continue; /* Skip non-dict items */ + TV_LIST_ITER_CONST(list, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT) { + continue; // Skip non-dict items. + } - d = li->li_tv.vval.v_dict; - if (d == NULL) + d = TV_LIST_ITEM_TV(li)->vval.v_dict; + if (d == NULL) { continue; + } char *const filename = tv_dict_get_string(d, "filename", true); int bufnum = (int)tv_dict_get_number(d, "bufnr"); @@ -4175,7 +4179,7 @@ static int qf_add_entries(qf_info_T *qi, list_T *list, char_u *title, retval = FAIL; break; } - } + }); if (qi->qf_lists[qi->qf_curlist].qf_index == 0) { // no valid entry diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index ae611a0005..ffe393f1b0 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -6477,10 +6477,6 @@ static regsubmatch_T rsm; // can only be used when can_f_submatch is true /// vim_regsub_both(). static int fill_submatch_list(int argc, typval_T *argv, int argcount) { - listitem_T *li; - int i; - char_u *s; - if (argcount == 0) { // called function doesn't take an argument return 0; @@ -6490,28 +6486,26 @@ static int fill_submatch_list(int argc, typval_T *argv, int argcount) init_static_list((staticList10_T *)(argv->vval.v_list)); // There are always 10 list items in staticList10_T. - li = argv->vval.v_list->lv_first; - for (i = 0; i < 10; i++) { - s = rsm.sm_match->startp[i]; + listitem_T *li = tv_list_first(argv->vval.v_list); + for (int i = 0; i < 10; i++) { + char_u *s = rsm.sm_match->startp[i]; if (s == NULL || rsm.sm_match->endp[i] == NULL) { s = NULL; } else { s = vim_strnsave(s, (int)(rsm.sm_match->endp[i] - s)); } - li->li_tv.v_type = VAR_STRING; - li->li_tv.vval.v_string = s; - li = li->li_next; + TV_LIST_ITEM_TV(li)->v_type = VAR_STRING; + TV_LIST_ITEM_TV(li)->vval.v_string = s; + li = TV_LIST_ITEM_NEXT(argv->vval.v_list, li); } return 1; } static void clear_submatch_list(staticList10_T *sl) { - int i; - - for (i = 0; i < 10; i++) { - xfree(sl->sl_items[i].li_tv.vval.v_string); - } + TV_LIST_ITER(&sl->sl_list, li, { + xfree(TV_LIST_ITEM_TV(li)->vval.v_string); + }); } /// vim_regsub() - perform substitutions after a vim_regexec() or @@ -6651,6 +6645,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, rettv.vval.v_string = NULL; argv[0].v_type = VAR_LIST; argv[0].vval.v_list = &matchList.sl_list; + // FIXME: Abstract away matchList.sl_list.lv_len = 0; if (expr->v_type == VAR_FUNC) { s = expr->vval.v_string; diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 736d6bf162..75f91ce6d8 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1180,8 +1180,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) list_T *oldfiles_list = get_vim_var_list(VV_OLDFILES); const bool force = flags & kShaDaForceit; const bool get_old_files = (flags & (kShaDaGetOldfiles | kShaDaForceit) - && (force || oldfiles_list == NULL - || oldfiles_list->lv_len == 0)); + && (force || tv_list_len(oldfiles_list) == 0)); const bool want_marks = flags & kShaDaWantMarks; const unsigned srni_flags = (unsigned) ( (flags & kShaDaWantInfo @@ -1599,13 +1598,13 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, #define DUMP_ADDITIONAL_ELEMENTS(src, what) \ do { \ if ((src) != NULL) { \ - for (listitem_T *li = (src)->lv_first; li != NULL; li = li->li_next) { \ - if (encode_vim_to_msgpack(spacker, &li->li_tv, \ + TV_LIST_ITER((src), li, { \ + if (encode_vim_to_msgpack(spacker, TV_LIST_ITEM_TV(li), \ _("additional elements of ShaDa " what)) \ == FAIL) { \ goto shada_pack_entry_error; \ } \ - } \ + }); \ } \ } while (0) #define DUMP_ADDITIONAL_DATA(src, what) \ @@ -1648,9 +1647,7 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, const bool is_hist_search = entry.data.history_item.histtype == HIST_SEARCH; const size_t arr_size = 2 + (size_t) is_hist_search + (size_t) ( - entry.data.history_item.additional_elements == NULL - ? 0 - : entry.data.history_item.additional_elements->lv_len); + tv_list_len(entry.data.history_item.additional_elements)); msgpack_pack_array(spacker, arr_size); msgpack_pack_uint8(spacker, entry.data.history_item.histtype); PACK_BIN(cstr_as_string(entry.data.history_item.string)); @@ -1663,9 +1660,7 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, } case kSDItemVariable: { const size_t arr_size = 2 + (size_t) ( - entry.data.global_var.additional_elements == NULL - ? 0 - : entry.data.global_var.additional_elements->lv_len); + tv_list_len(entry.data.global_var.additional_elements)); msgpack_pack_array(spacker, arr_size); const String varname = cstr_as_string(entry.data.global_var.name); PACK_BIN(varname); @@ -1685,9 +1680,7 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, } case kSDItemSubString: { const size_t arr_size = 1 + (size_t) ( - entry.data.sub_string.additional_elements == NULL - ? 0 - : entry.data.sub_string.additional_elements->lv_len); + tv_list_len(entry.data.sub_string.additional_elements)); msgpack_pack_array(spacker, arr_size); PACK_BIN(cstr_as_string(entry.data.sub_string.sub)); DUMP_ADDITIONAL_ELEMENTS(entry.data.sub_string.additional_elements, diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 42c9bcc0ee..6a1b46fb5b 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -3213,26 +3213,25 @@ spell_find_suggest ( // Find suggestions by evaluating expression "expr". static void spell_suggest_expr(suginfo_T *su, char_u *expr) { - list_T *list; - listitem_T *li; int score; const char *p; // The work is split up in a few parts to avoid having to export // suginfo_T. // First evaluate the expression and get the resulting list. - list = eval_spell_expr(su->su_badword, expr); + list_T *const list = eval_spell_expr(su->su_badword, expr); if (list != NULL) { // Loop over the items in the list. - for (li = list->lv_first; li != NULL; li = li->li_next) - if (li->li_tv.v_type == VAR_LIST) { + TV_LIST_ITER(list, li, { + if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) { // Get the word and the score from the items. - score = get_spellword(li->li_tv.vval.v_list, &p); + score = get_spellword(TV_LIST_ITEM_TV(li)->vval.v_list, &p); if (score >= 0 && score <= su->su_maxscore) { add_suggestion(su, &su->su_ga, (const char_u *)p, su->su_badlen, score, 0, true, su->su_sallang, false); } } + }); tv_list_unref(list); } diff --git a/src/nvim/window.c b/src/nvim/window.c index 4e4eb297aa..21d668a2bd 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -5579,30 +5579,26 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, // Set up position matches if (pos_list != NULL) { - linenr_T toplnum = 0; - linenr_T botlnum = 0; - listitem_T *li; - int i; - - for (i = 0, li = pos_list->lv_first; li != NULL && i < MAXPOSMATCH; - i++, li = li->li_next) { - linenr_T lnum = 0; - colnr_T col = 0; - int len = 1; - list_T *subl; - listitem_T *subli; + linenr_T toplnum = 0; + linenr_T botlnum = 0; + + int i = 0; + TV_LIST_ITER(pos_list, li, { + linenr_T lnum = 0; + colnr_T col = 0; + int len = 1; bool error = false; - if (li->li_tv.v_type == VAR_LIST) { - subl = li->li_tv.vval.v_list; + if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) { + const list_T *const subl = TV_LIST_ITEM_TV(li)->vval.v_list; if (subl == NULL) { goto fail; } - subli = subl->lv_first; + const listitem_T *subli = tv_list_first(subl); if (subli == NULL) { goto fail; } - lnum = tv_get_number_chk(&subli->li_tv, &error); + lnum = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); if (error) { goto fail; } @@ -5611,15 +5607,15 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, continue; } m->pos.pos[i].lnum = lnum; - subli = subli->li_next; + subli = TV_LIST_ITEM_NEXT(subl, subli); if (subli != NULL) { - col = tv_get_number_chk(&subli->li_tv, &error); + col = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); if (error) { goto fail; } - subli = subli->li_next; + subli = TV_LIST_ITEM_NEXT(subl, subli); if (subli != NULL) { - len = tv_get_number_chk(&subli->li_tv, &error); + len = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); if (error) { goto fail; } @@ -5627,12 +5623,12 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, } m->pos.pos[i].col = col; m->pos.pos[i].len = len; - } else if (li->li_tv.v_type == VAR_NUMBER) { - if (li->li_tv.vval.v_number == 0) { - --i; + } else if (TV_LIST_ITEM_TV(li)->v_type == VAR_NUMBER) { + if (TV_LIST_ITEM_TV(li)->vval.v_number == 0) { + i--; continue; } - m->pos.pos[i].lnum = li->li_tv.vval.v_number; + m->pos.pos[i].lnum = TV_LIST_ITEM_TV(li)->vval.v_number; m->pos.pos[i].col = 0; m->pos.pos[i].len = 0; } else { @@ -5645,7 +5641,11 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, if (botlnum == 0 || lnum >= botlnum) { botlnum = lnum + 1; } - } + i++; + if (i >= MAXPOSMATCH) { + break; + } + }); // Calculate top and bottom lines for redrawing area if (toplnum != 0){ -- cgit From f572bd7e4e15a99cc19244a4411c6a596309f489 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 22:24:11 +0300 Subject: eval,functests: Fix tests and complete() and setline() behaviour --- src/nvim/eval.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 2f908f70a9..81dd6434a1 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -7496,7 +7496,7 @@ static void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (!undo_allowed()) return; - if (argvars[1].v_type != VAR_LIST || argvars[1].vval.v_list == NULL) { + if (argvars[1].v_type != VAR_LIST) { EMSG(_(e_invarg)); return; } @@ -14540,20 +14540,19 @@ static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr) line = tv_get_string_chk(&argvars[1]); } - /* default result is zero == OK */ + // Default result is zero == OK. for (;; ) { - if (l != NULL) { // List argument, get next string. - if (li == NULL) { - break; - } - line = tv_get_string_chk(TV_LIST_ITEM_TV(li)); - li = TV_LIST_ITEM_NEXT(l, li); + if (li == NULL) { + break; } + line = tv_get_string_chk(TV_LIST_ITEM_TV(li)); + li = TV_LIST_ITEM_NEXT(l, li); - rettv->vval.v_number = 1; /* FAIL */ - if (line == NULL || lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) + rettv->vval.v_number = 1; // FAIL + if (line == NULL || lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) { break; + } /* When coming here from Insert mode, sync undo, so that this can be * undone separately from what was previously inserted. */ -- cgit From 5008205a3e1eef61396e457e5091eb1341b98278 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 22:28:18 +0300 Subject: eval: Fix setmatches(), setqflist() and setloclist() --- src/nvim/eval.c | 153 +++++++++++++++++++++++++++----------------------------- 1 file changed, 75 insertions(+), 78 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 81dd6434a1..c654fe691e 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -14653,8 +14653,8 @@ skip_args: title = (wp ? "setloclist()" : "setqflist()"); } - list_T *l = list_arg->vval.v_list; - if (l && set_errorlist(wp, l, action, (char_u *)title, d) == OK) { + list_T *const l = list_arg->vval.v_list; + if (set_errorlist(wp, l, action, (char_u *)title, d) == OK) { rettv->vval.v_number = 0; } } @@ -14687,92 +14687,89 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG(_(e_listreq)); return; } - list_T *l; - if ((l = argvars[0].vval.v_list) != NULL) { + list_T *const l = argvars[0].vval.v_list; + // To some extent make sure that we are dealing with a list from + // "getmatches()". + int i = 0; + TV_LIST_ITER_CONST(l, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT + || (d = TV_LIST_ITEM_TV(li)->vval.v_dict) == NULL) { + emsgf(_("E474: List item %d is either not a dictionary " + "or an empty one"), i); + return; + } + if (!(tv_dict_find(d, S_LEN("group")) != NULL + && (tv_dict_find(d, S_LEN("pattern")) != NULL + || tv_dict_find(d, S_LEN("pos1")) != NULL) + && tv_dict_find(d, S_LEN("priority")) != NULL + && tv_dict_find(d, S_LEN("id")) != NULL)) { + emsgf(_("E474: List item %d is missing one of the required keys"), i); + return; + } + i++; + }); - // To some extent make sure that we are dealing with a list from - // "getmatches()". + clear_matches(curwin); + bool match_add_failed = false; + TV_LIST_ITER_CONST(l, li, { int i = 0; - TV_LIST_ITER_CONST(l, li, { - if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT - || (d = TV_LIST_ITEM_TV(li)->vval.v_dict) == NULL) { - emsgf(_("E474: List item %d is either not a dictionary " - "or an empty one"), i); - return; - } - if (!(tv_dict_find(d, S_LEN("group")) != NULL - && (tv_dict_find(d, S_LEN("pattern")) != NULL - || tv_dict_find(d, S_LEN("pos1")) != NULL) - && tv_dict_find(d, S_LEN("priority")) != NULL - && tv_dict_find(d, S_LEN("id")) != NULL)) { - emsgf(_("E474: List item %d is missing one of the required keys"), i); - return; - } - i++; - }); - clear_matches(curwin); - bool match_add_failed = false; - TV_LIST_ITER_CONST(l, li, { - int i = 0; - - d = TV_LIST_ITEM_TV(li)->vval.v_dict; - dictitem_T *const di = tv_dict_find(d, S_LEN("pattern")); - if (di == NULL) { - if (s == NULL) { - s = tv_list_alloc(); - } - - // match from matchaddpos() - for (i = 1; i < 9; i++) { - char buf[5]; - snprintf(buf, sizeof(buf), "pos%d", i); - dictitem_T *const pos_di = tv_dict_find(d, buf, -1); - if (pos_di != NULL) { - if (pos_di->di_tv.v_type != VAR_LIST) { - return; - } + d = TV_LIST_ITEM_TV(li)->vval.v_dict; + dictitem_T *const di = tv_dict_find(d, S_LEN("pattern")); + if (di == NULL) { + if (s == NULL) { + s = tv_list_alloc(); + } - tv_list_append_tv(s, &pos_di->di_tv); - tv_list_ref(s); - } else { - break; + // match from matchaddpos() + for (i = 1; i < 9; i++) { + char buf[5]; + snprintf(buf, sizeof(buf), "pos%d", i); + dictitem_T *const pos_di = tv_dict_find(d, buf, -1); + if (pos_di != NULL) { + if (pos_di->di_tv.v_type != VAR_LIST) { + return; } + + tv_list_append_tv(s, &pos_di->di_tv); + tv_list_ref(s); + } else { + break; } } + } - // Note: there are three number buffers involved: - // - group_buf below. - // - numbuf in tv_dict_get_string(). - // - mybuf in tv_get_string(). - // - // If you change this code make sure that buffers will not get - // accidentally reused. - char group_buf[NUMBUFLEN]; - const char *const group = tv_dict_get_string_buf(d, "group", group_buf); - const int priority = (int)tv_dict_get_number(d, "priority"); - const int id = (int)tv_dict_get_number(d, "id"); - dictitem_T *const conceal_di = tv_dict_find(d, S_LEN("conceal")); - const char *const conceal = (conceal_di != NULL - ? tv_get_string(&conceal_di->di_tv) - : NULL); - if (i == 0) { - if (match_add(curwin, group, - tv_dict_get_string(d, "pattern", false), - priority, id, NULL, conceal) != id) { - match_add_failed = true; - } - } else { - if (match_add(curwin, group, NULL, priority, id, s, conceal) != id) { - match_add_failed = true; - } - tv_list_unref(s); - s = NULL; + // Note: there are three number buffers involved: + // - group_buf below. + // - numbuf in tv_dict_get_string(). + // - mybuf in tv_get_string(). + // + // If you change this code make sure that buffers will not get + // accidentally reused. + char group_buf[NUMBUFLEN]; + const char *const group = tv_dict_get_string_buf(d, "group", group_buf); + const int priority = (int)tv_dict_get_number(d, "priority"); + const int id = (int)tv_dict_get_number(d, "id"); + dictitem_T *const conceal_di = tv_dict_find(d, S_LEN("conceal")); + const char *const conceal = (conceal_di != NULL + ? tv_get_string(&conceal_di->di_tv) + : NULL); + if (i == 0) { + if (match_add(curwin, group, + tv_dict_get_string(d, "pattern", false), + priority, id, NULL, conceal) != id) { + match_add_failed = true; } - }); - if (!match_add_failed) { - rettv->vval.v_number = 0; + } else { + if (match_add(curwin, group, NULL, priority, id, s, conceal) != id) { + match_add_failed = true; + } + tv_list_unref(s); + s = NULL; } + }); + if (!match_add_failed) { + rettv->vval.v_number = 0; } } -- cgit From 83f77c80c084a0390b5a6efd364b33620c9f3d10 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 22:33:05 +0300 Subject: quickfix: Fix :cexpr and :lexpr --- src/nvim/quickfix.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 56d11c21ab..a4dc9c68ce 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4403,7 +4403,7 @@ void ex_cexpr(exarg_T *eap) typval_T tv; if (eval0(eap->arg, &tv, NULL, true) != FAIL) { if ((tv.v_type == VAR_STRING && tv.vval.v_string != NULL) - || (tv.v_type == VAR_LIST && tv.vval.v_list != NULL)) { + || tv.v_type == VAR_LIST) { if (qf_init_ext(qi, NULL, NULL, &tv, p_efm, (eap->cmdidx != CMD_caddexpr && eap->cmdidx != CMD_laddexpr), -- cgit From 7572d5ac5a09c18651843c026ebc92bffafe348f Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 22:41:00 +0300 Subject: eval/encode: Fix crash in json_encode test suite --- src/nvim/eval/encode.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 0e5db84ad5..50ddb1f38c 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -136,8 +136,10 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, } case kMPConvPairs: case kMPConvList: { - const listitem_T *const li = TV_LIST_ITEM_PREV(v.data.l.list, - v.data.l.li); + const listitem_T *const li = (v.data.l.li == NULL + ? tv_list_last(v.data.l.list) + : TV_LIST_ITEM_PREV(v.data.l.list, + v.data.l.li)); int idx = (int)tv_list_idx_of_item(v.data.l.list, li); if (v.type == kMPConvList || li == NULL -- cgit From d11884db497114bc8ac5e33964ed81165f8a50fe Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 23:02:19 +0300 Subject: eval: Fix uniq() crash in legacy test 055 --- src/nvim/eval.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index c654fe691e..064f2c072c 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -15232,6 +15232,8 @@ item_compare_end: // When the result would be zero, compare the item indexes. Makes the // sort stable. if (res == 0 && !keep_zero) { + // WARNING: When using uniq si1 and si2 are actually listitem_T **, no + // indexes are there. res = si1->idx > si2->idx ? 1 : -1; } return res; @@ -15297,6 +15299,8 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) // When the result would be zero, compare the pointers themselves. Makes // the sort stable. if (res == 0 && !keep_zero) { + // WARNING: When using uniq si1 and si2 are actually listitem_T **, no + // indexes are there. res = si1->idx > si2->idx ? 1 : -1; } @@ -15337,7 +15341,7 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) } else { list_T *const l = argvars[0].vval.v_list; if (tv_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) { - goto theend; + goto theend; } rettv->vval.v_list = l; rettv->v_type = VAR_LIST; @@ -15462,7 +15466,7 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) listitem_T *prev_li = NULL; TV_LIST_ITER(l, li, { if (prev_li != NULL) { - if (item_compare_func_ptr(prev_li, li) == 0) { + if (item_compare_func_ptr(&prev_li, &li) == 0) { ptrs[i++].item = prev_li; } if (info.item_compare_func_err) { -- cgit From fe55f37083b0bef07aa9ac78eb2727c244fdafd3 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 23:18:24 +0300 Subject: eval: Still check for NULL when doing :unlet --- src/nvim/eval.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 064f2c072c..6271603a1d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2875,9 +2875,12 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit) ret = FAIL; } *name_end = cc; - } else if (tv_check_lock(tv_list_locked(lp->ll_list), - (const char *)lp->ll_name, - lp->ll_name_len) + } else if ((lp->ll_list != NULL + // ll_list is not NULL when lvalue is not in a list, NULL lists + // yield E689. + && tv_check_lock(tv_list_locked(lp->ll_list), + (const char *)lp->ll_name, + lp->ll_name_len)) || (lp->ll_dict != NULL && tv_check_lock(lp->ll_dict->dv_lock, (const char *)lp->ll_name, -- cgit From ceb45a08858837319c8ea67b1aaeceaeb24c8510 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 11 Dec 2017 01:43:36 +0300 Subject: *: Fix test failures --- src/nvim/eval.c | 10 ++++++---- src/nvim/eval/encode.c | 4 +++- src/nvim/ex_getln.c | 1 + 3 files changed, 10 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 6271603a1d..b45cfcb427 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -14545,12 +14545,14 @@ static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr) // Default result is zero == OK. for (;; ) { + if (argvars[1].v_type == VAR_LIST) { // List argument, get next string. - if (li == NULL) { - break; + if (li == NULL) { + break; + } + line = tv_get_string_chk(TV_LIST_ITEM_TV(li)); + li = TV_LIST_ITEM_NEXT(l, li); } - line = tv_get_string_chk(TV_LIST_ITEM_TV(li)); - li = TV_LIST_ITEM_NEXT(l, li); rettv->vval.v_number = 1; // FAIL if (line == NULL || lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) { diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 50ddb1f38c..85fd4d1578 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -140,7 +140,9 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, ? tv_list_last(v.data.l.list) : TV_LIST_ITEM_PREV(v.data.l.list, v.data.l.li)); - int idx = (int)tv_list_idx_of_item(v.data.l.list, li); + int idx = (li == NULL + ? 0 + : (int)tv_list_idx_of_item(v.data.l.list, li)); if (v.type == kMPConvList || li == NULL || (TV_LIST_ITEM_TV(li)->v_type != VAR_LIST diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index fcd230d535..a5a8804e1c 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -2680,6 +2680,7 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) .end = end, .attr = attr, })); + i++; }); if (prev_end < colored_ccline->cmdlen) { kv_push(ccline_colors->colors, ((CmdlineColorChunk) { -- cgit From d46e37cb4c71f39312233799d7f28eb86dceef60 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 11 Dec 2017 10:12:59 +0300 Subject: *: Finish hiding list implementation --- src/nvim/eval.c | 104 +++++++--------------------------------- src/nvim/eval/typval.c | 90 +++++++++++++++++++++++++++++++++- src/nvim/eval/typval.h | 75 ++++++++++++++++++++++++++++- src/nvim/eval/typval_encode.c.h | 10 ++-- src/nvim/regexp.c | 8 ++-- 5 files changed, 187 insertions(+), 100 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index b45cfcb427..ad8b575867 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -110,9 +110,6 @@ #define DICT_MAXNEST 100 /* maximum nesting of lists and dicts */ -#define DO_NOT_FREE_CNT 99999 /* refcount for dict or list that should not - be freed. */ - #define AUTOLOAD_CHAR '#' /* Character used as separator in autoload function/variable names. */ @@ -5271,7 +5268,6 @@ static int free_unref_items(int copyID) for (ll = gc_first_list; ll != NULL; ll = ll_next) { ll_next = ll->lv_used_next; if ((ll->lv_copyID & COPYID_MASK) != (copyID & COPYID_MASK) - // FIXME: Abstract away lv_watch. && ll->lv_watch == NULL) { // Free the List and ordinary items it contains, but don't recurse // into Lists and Dictionaries, they will be in the list of dicts @@ -13313,14 +13309,7 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (li == NULL) { // Didn't find "item2" after "item". emsgf(_(e_invrange)); } else { - tv_list_remove_items(l, item, item2); - // FIXME: Abstract the below away or move to eval/typval. - l = tv_list_alloc_ret(rettv); - l->lv_first = item; - l->lv_last = item2; - item->li_prev = NULL; - item2->li_next = NULL; - l->lv_len = cnt; + tv_list_move_items(l, item, item2, tv_list_alloc_ret(rettv), cnt); } } } @@ -13558,19 +13547,10 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG2(_(e_listarg), "reverse()"); } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), N_("reverse() argument"), TV_TRANSLATE)) { - // FIXME: Abstract the below away or move to eval/typval. - listitem_T *li = l->lv_last; - l->lv_first = l->lv_last = NULL; - l->lv_len = 0; - while (li != NULL) { - listitem_T *const ni = li->li_prev; - tv_list_append(l, li); - li = ni; - } + tv_list_reverse(l); rettv->vval.v_list = l; rettv->v_type = VAR_LIST; tv_list_ref(l); - l->lv_idx = l->lv_len - l->lv_idx - 1; } } @@ -15445,12 +15425,7 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) if (!info.item_compare_func_err) { // Clear the list and append the items in the sorted order. - // FIXME: Somehow abstract away or move to eval/typval. - l->lv_first = NULL; - l->lv_last = NULL; - l->lv_idx_item = NULL; - l->lv_len = 0; - + tv_list_clear(l); for (i = 0; i < len; i++) { tv_list_append(l, ptrs[i].item); } @@ -15468,34 +15443,20 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) item_compare_func_ptr = item_compare_keeping_zero; } - listitem_T *prev_li = NULL; - TV_LIST_ITER(l, li, { - if (prev_li != NULL) { - if (item_compare_func_ptr(&prev_li, &li) == 0) { - ptrs[i++].item = prev_li; - } + int idx = 0; + for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l)) + ; li != NULL + ; li = TV_LIST_ITEM_NEXT(l, li)) { + listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li); + if (item_compare_func_ptr(&prev_li, &li) == 0) { if (info.item_compare_func_err) { EMSG(_("E882: Uniq compare function failed")); break; } - } - prev_li = li; - }); - - if (!info.item_compare_func_err) { - while (--i >= 0) { - // FIXME: Abstract away. - assert(ptrs[i].item->li_next); - listitem_T *const li = ptrs[i].item->li_next; - ptrs[i].item->li_next = li->li_next; - if (li->li_next != NULL) { - li->li_next->li_prev = ptrs[i].item; - } else { - l->lv_last = ptrs[i].item; - } - tv_list_watch_fix(l, li); - tv_list_item_free(li); - l->lv_len--; + tv_list_item_remove(l, li); + li = tv_list_find(l, idx); + } else { + idx++; } } } @@ -17533,35 +17494,6 @@ write_list_error: return false; } -/// Initializes a static list with 10 items. -void init_static_list(staticList10_T *sl) -{ - // FIXME: Move to eval/typval. - list_T *l = &sl->sl_list; - - memset(sl, 0, sizeof(staticList10_T)); - l->lv_first = &sl->sl_items[0]; - l->lv_last = &sl->sl_items[9]; - l->lv_refcount = DO_NOT_FREE_CNT; - tv_list_set_lock(l, VAR_FIXED); - sl->sl_list.lv_len = 10; - - for (int i = 0; i < 10; i++) { - listitem_T *li = &sl->sl_items[i]; - - if (i == 0) { - li->li_prev = NULL; - } else { - li->li_prev = li - 1; - } - if (i == 9) { - li->li_next = NULL; - } else { - li->li_next = li + 1; - } - } -} - /// Saves a typval_T as a string. /// /// For lists, replaces NLs with NUL and separates items with NLs. @@ -19305,10 +19237,9 @@ int var_item_copy(const vimconv_T *const conv, to->v_lock = 0; if (from->vval.v_list == NULL) to->vval.v_list = NULL; - else if (copyID != 0 && from->vval.v_list->lv_copyID == copyID) { - // FIXME: Abstract away. + else if (copyID != 0 && tv_list_copyid(from->vval.v_list) == copyID) { // Use the copy made earlier. - to->vval.v_list = from->vval.v_list->lv_copylist; + to->vval.v_list = tv_list_latest_copy(from->vval.v_list); tv_list_ref(to->vval.v_list); } else { to->vval.v_list = tv_list_copy(conv, from->vval.v_list, deep, copyID); @@ -21190,9 +21121,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, v->di_tv.v_type = VAR_LIST; v->di_tv.v_lock = VAR_FIXED; v->di_tv.vval.v_list = &fc->l_varlist; - // FIXME: Abstract away static list. - memset(&fc->l_varlist, 0, sizeof(list_T)); - fc->l_varlist.lv_refcount = DO_NOT_FREE_CNT; + tv_list_init_static(&fc->l_varlist); tv_list_set_lock(&fc->l_varlist, VAR_FIXED); // Set a:firstline to "firstline" and a:lastline to "lastline". @@ -21474,7 +21403,6 @@ static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force) return; } - // FIXME: Abstract away static list implementation details. if (--fc->fc_refcount <= 0 && (force || ( fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index eaf70b41fd..df81c0450d 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -153,6 +153,45 @@ list_T *tv_list_alloc(void) return list; } +/// Initialize a static list with 10 items +/// +/// @param[out] sl Static list to initialize. +void tv_list_init_static10(staticList10_T *const sl) + FUNC_ATTR_NONNULL_ALL +{ +#define SL_SIZE ARRAY_SIZE(sl->sl_items) + list_T *const l = &sl->sl_list; + + memset(sl, 0, sizeof(staticList10_T)); + l->lv_first = &sl->sl_items[0]; + l->lv_last = &sl->sl_items[SL_SIZE - 1]; + l->lv_refcount = DO_NOT_FREE_CNT; + tv_list_set_lock(l, VAR_FIXED); + sl->sl_list.lv_len = 10; + + sl->sl_items[0].li_prev = NULL; + sl->sl_items[0].li_next = &sl->sl_items[1]; + sl->sl_items[SL_SIZE - 1].li_prev = &sl->sl_items[SL_SIZE - 2]; + sl->sl_items[SL_SIZE - 1].li_next = NULL; + + for (size_t i = 1; i < SL_SIZE - 1; i++) { + listitem_T *const li = &sl->sl_items[i]; + li->li_prev = li - 1; + li->li_next = li + 1; + } +#undef SL_SIZE +} + +/// Initialize static list with undefined number of elements +/// +/// @param[out] l List to initialize. +void tv_list_init_static(list_T *const l) + FUNC_ATTR_NONNULL_ALL +{ + memset(l, 0, sizeof(*l)); + l->lv_refcount = DO_NOT_FREE_CNT; +} + /// Free items contained in a list /// /// @param[in,out] l List to clear. @@ -221,7 +260,7 @@ void tv_list_unref(list_T *const l) //{{{2 Add/remove -/// Remove items "item" to "item2" from list "l". +/// Remove items "item" to "item2" from list "l" /// /// @warning Does not free the listitem or the value! /// @@ -251,6 +290,30 @@ void tv_list_remove_items(list_T *const l, listitem_T *const item, l->lv_idx_item = NULL; } +/// Move items "item" to "item2" from list "l" to the end of the list "tgt_l" +/// +/// @param[out] l List to move from. +/// @param[in] item First item to move. +/// @param[in] item2 Last item to move. +/// @param[out] tgt_l List to move to. +/// @param[in] cnt Number of items moved. +void tv_list_move_items(list_T *const l, listitem_T *const item, + listitem_T *const item2, list_T *const tgt_l, + const int cnt) + FUNC_ATTR_NONNULL_ALL +{ + tv_list_remove_items(l, item, item2); + item->li_prev = tgt_l->lv_last; + item2->li_next = NULL; + if (tgt_l->lv_last == NULL) { + tgt_l->lv_first = item; + } else { + tgt_l->lv_last->li_next = item; + } + tgt_l->lv_last = item2; + tgt_l->lv_len += cnt; +} + /// Insert list item /// /// @param[out] l List to insert to. @@ -644,6 +707,31 @@ bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic, return true; } +/// Reverse list in-place +/// +/// @param[in,out] l List to reverse. +void tv_list_reverse(list_T *const l) +{ + if (tv_list_len(l) <= 1) { + return; + } +#define SWAP(a, b) \ + do { \ + tmp = a; \ + a = b; \ + b = tmp; \ + } while (0) + listitem_T *tmp; + + SWAP(l->lv_first, l->lv_last); + for (listitem_T *li = l->lv_first; li != NULL; li = li->li_next) { + SWAP(li->li_next, li->li_prev); + } +#undef SWAP + + l->lv_idx = l->lv_len - l->lv_idx - 1; +} + //{{{2 Indexing/searching /// Locate item with a given index in a list and return it diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index a05abd8a53..a7255fdba0 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -7,6 +7,7 @@ #include #include #include +#include #include "nvim/types.h" #include "nvim/hashtab.h" @@ -27,6 +28,9 @@ typedef uint64_t uvarnumber_T; /// Type used for VimL VAR_FLOAT values typedef double float_T; +/// Refcount for dict or list that should not be freed +enum { DO_NOT_FREE_CNT = (INT_MAX / 2) }; + /// Maximal possible value of varnumber_T variable #define VARNUMBER_MAX INT64_MAX #define UVARNUMBER_MAX UINT64_MAX @@ -151,12 +155,26 @@ struct listvar_S { list_T *lv_used_prev; ///< Previous list in used lists list. }; -// Static list with 10 items. Use init_static_list() to initialize. +// Static list with 10 items. Use tv_list_init_static10() to initialize. typedef struct { list_T sl_list; // must be first listitem_T sl_items[10]; } staticList10_T; +#define TV_LIST_STATIC10_INIT { \ + .sl_list = { \ + .lv_first = NULL, \ + .lv_last = NULL, \ + .lv_refcount = 0, \ + .lv_len = 0, \ + .lv_watch = NULL, \ + .lv_idx_item = NULL, \ + .lv_lock = VAR_FIXED, \ + .lv_used_next = NULL, \ + .lv_used_prev = NULL, \ + }, \ + } + // Structure to hold an item of a Dictionary. // Also used for a variable. // The key is copied into "di_key" to avoid an extra alloc/free for it. @@ -330,6 +348,19 @@ static inline void tv_list_set_lock(list_T *const l, l->lv_lock = lock; } +/// Set list copyID +/// +/// Does not expect NULL list, be careful. +/// +/// @param[out] l List to modify. +/// @param[in] copyid New copyID. +static inline void tv_list_set_copyid(list_T *const l, + const int copyid) + FUNC_ATTR_NONNULL_ALL +{ + l->lv_copyID = copyid; +} + static inline int tv_list_len(const list_T *const l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; @@ -344,6 +375,48 @@ static inline int tv_list_len(const list_T *const l) return l->lv_len; } +static inline int tv_list_copyid(const list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; + +/// Get list copyID +/// +/// Does not expect NULL list, be careful. +/// +/// @param[in] l List to check. +static inline int tv_list_copyid(const list_T *const l) +{ + return l->lv_copyID; +} + +static inline list_T *tv_list_latest_copy(const list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; + +/// Get latest list copy +/// +/// Gets lv_copylist field assigned by tv_list_copy() earlier. +/// +/// Does not expect NULL list, be careful. +/// +/// @param[in] l List to check. +static inline list_T *tv_list_latest_copy(const list_T *const l) +{ + return l->lv_copylist; +} + +/// Clear the list without freeing anything at all +/// +/// For use in sort() which saves items to a separate array and readds them back +/// after sorting via a number of tv_list_append() calls. +/// +/// @param[out] l List to clear. +static inline void tv_list_clear(list_T *const l) +{ + l->lv_first = NULL; + l->lv_last = NULL; + l->lv_idx_item = NULL; + l->lv_len = 0; +} + static inline int tv_list_uidx(const list_T *const l, int n) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index 800760cf7b..e4ed2bc636 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -359,7 +359,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( TYPVAL_ENCODE_CONV_EMPTY_LIST(tv); break; } - const int saved_copyID = tv->vval.v_list->lv_copyID; + const int saved_copyID = tv_list_copyid(tv->vval.v_list); _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)); @@ -515,7 +515,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( if (val_di->di_tv.v_type != VAR_LIST) { goto _convert_one_value_regular_dict; } - const int saved_copyID = val_di->di_tv.vval.v_list->lv_copyID; + const int saved_copyID = tv_list_copyid(val_di->di_tv.vval.v_list); _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list, lv_copyID, copyID, kMPConvList); @@ -550,7 +550,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( goto _convert_one_value_regular_dict; } }); - const int saved_copyID = val_di->di_tv.vval.v_list->lv_copyID; + const int saved_copyID = tv_list_copyid(val_di->di_tv.vval.v_list); _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_list, lv_copyID, copyID, kMPConvPairs); TYPVAL_ENCODE_CONV_DICT_START(tv, TYPVAL_ENCODE_NODICT_VAR, @@ -694,7 +694,7 @@ typval_encode_stop_converting_one_item: case kMPConvList: { if (cur_mpsv->data.l.li == NULL) { (void)_mp_pop(mpstack); - cur_mpsv->data.l.list->lv_copyID = cur_mpsv->saved_copyID; + tv_list_set_copyid(cur_mpsv->data.l.list, cur_mpsv->saved_copyID); TYPVAL_ENCODE_CONV_LIST_END(cur_mpsv->tv); continue; } else if (cur_mpsv->data.l.li @@ -709,7 +709,7 @@ typval_encode_stop_converting_one_item: case kMPConvPairs: { if (cur_mpsv->data.l.li == NULL) { (void)_mp_pop(mpstack); - cur_mpsv->data.l.list->lv_copyID = cur_mpsv->saved_copyID; + tv_list_set_copyid(cur_mpsv->data.l.list, cur_mpsv->saved_copyID); TYPVAL_ENCODE_CONV_DICT_END(cur_mpsv->tv, TYPVAL_ENCODE_NODICT_VAR); continue; } else if (cur_mpsv->data.l.li diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index ffe393f1b0..463a408885 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -6483,7 +6483,7 @@ static int fill_submatch_list(int argc, typval_T *argv, int argcount) } // Relies on sl_list to be the first item in staticList10_T. - init_static_list((staticList10_T *)(argv->vval.v_list)); + tv_list_init_static10((staticList10_T *)argv->vval.v_list); // There are always 10 list items in staticList10_T. listitem_T *li = tv_list_first(argv->vval.v_list); @@ -6639,14 +6639,12 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, typval_T argv[2]; int dummy; typval_T rettv; - staticList10_T matchList; + staticList10_T matchList = TV_LIST_STATIC10_INIT; rettv.v_type = VAR_STRING; rettv.vval.v_string = NULL; argv[0].v_type = VAR_LIST; argv[0].vval.v_list = &matchList.sl_list; - // FIXME: Abstract away - matchList.sl_list.lv_len = 0; if (expr->v_type == VAR_FUNC) { s = expr->vval.v_string; call_func(s, (int)STRLEN(s), &rettv, 1, argv, @@ -6660,7 +6658,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, fill_submatch_list, 0L, 0L, &dummy, true, partial, NULL); } - if (matchList.sl_list.lv_len > 0) { + if (tv_list_len(&matchList.sl_list) > 0) { // fill_submatch_list() was called. clear_submatch_list(&matchList); } -- cgit From f4132fb38b1355115d824b7c04eff25733d059d6 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 11 Dec 2017 10:19:20 +0300 Subject: *: Fix linter errors --- src/nvim/eval.c | 23 +++++++++++++---------- src/nvim/eval/decode.c | 2 +- src/nvim/eval/encode.c | 2 +- src/nvim/eval/typval.c | 4 ++-- src/nvim/eval/typval_encode.c.h | 10 ++++++---- src/nvim/ops.c | 2 +- src/nvim/shada.c | 8 ++++---- src/nvim/window.c | 3 +-- 8 files changed, 29 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index ad8b575867..10dbf208ea 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1540,7 +1540,8 @@ ex_let_vars ( listitem_T *item = tv_list_first(l); while (*arg != ']') { arg = skipwhite(arg + 1); - arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), TRUE, (char_u *)",;]", nextchars); + arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, (const char_u *)",;]", + nextchars); item = TV_LIST_ITEM_NEXT(l, item); if (arg == NULL) { return FAIL; @@ -2415,13 +2416,14 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, lp->ll_li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li); lp->ll_n1++; } - if (ri != NULL) + if (ri != NULL) { EMSG(_("E710: List value has more items than target")); - else if (lp->ll_empty2 - ? (lp->ll_li != NULL - && TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li) != NULL) - : lp->ll_n1 != lp->ll_n2) + } else if (lp->ll_empty2 + ? (lp->ll_li != NULL + && TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li) != NULL) + : lp->ll_n1 != lp->ll_n2) { EMSG(_("E711: List value has not enough items")); + } } else { typval_T oldtv = TV_INITIAL_VALUE; dict_T *dict = lp->ll_dict; @@ -8561,8 +8563,9 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) nli = TV_LIST_ITEM_NEXT(l, li); vimvars[VV_KEY].vv_nr = idx; if (filter_map_one(TV_LIST_ITEM_TV(li), expr, map, &rem) == FAIL - || did_emsg) + || did_emsg) { break; + } if (!map && rem) { tv_list_item_remove(l, li); } @@ -19235,9 +19238,9 @@ int var_item_copy(const vimconv_T *const conv, case VAR_LIST: to->v_type = VAR_LIST; to->v_lock = 0; - if (from->vval.v_list == NULL) + if (from->vval.v_list == NULL) { to->vval.v_list = NULL; - else if (copyID != 0 && tv_list_copyid(from->vval.v_list) == copyID) { + } else if (copyID != 0 && tv_list_copyid(from->vval.v_list) == copyID) { // Use the copy made earlier. to->vval.v_list = tv_list_latest_copy(from->vval.v_list); tv_list_ref(to->vval.v_list); @@ -22400,7 +22403,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments) {.v_type = VAR_LIST, .vval.v_list = arguments, .v_lock = 0}, {.v_type = VAR_UNKNOWN} }; - typval_T rettv = {.v_type = VAR_UNKNOWN, .v_lock = 0}; + typval_T rettv = { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED }; tv_list_ref(arguments); int dummy; diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index b2d901462a..d5c65ebe81 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -60,7 +60,7 @@ static inline void create_special_dict(typval_T *const rettv, dictitem_T *const type_di = tv_dict_item_alloc_len(S_LEN("_TYPE")); type_di->di_tv.v_type = VAR_LIST; type_di->di_tv.v_lock = VAR_UNLOCKED; - type_di->di_tv.vval.v_list = (list_T *) eval_msgpack_type_lists[type]; + type_di->di_tv.vval.v_list = (list_T *)eval_msgpack_type_lists[type]; tv_list_ref(type_di->di_tv.vval.v_list); tv_dict_add(dict, type_di); dictitem_T *const val_di = tv_dict_item_alloc_len(S_LEN("_VAL")); diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 85fd4d1578..9f16b78976 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -267,7 +267,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, } *p++ = NL; if (TV_LIST_ITEM_TV(state->li)->v_type != VAR_STRING) { - *read_bytes = (size_t) (p - buf); + *read_bytes = (size_t)(p - buf); return FAIL; } state->offset = 0; diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index df81c0450d..0c9c4a0347 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -696,8 +696,8 @@ bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic, listitem_T *item1 = tv_list_first(l1); listitem_T *item2 = tv_list_first(l2); for (; item1 != NULL && item2 != NULL - ; item1 = TV_LIST_ITEM_NEXT(l1, item1), - item2 = TV_LIST_ITEM_NEXT(n2, item2)) { + ; (item1 = TV_LIST_ITEM_NEXT(l1, item1), + item2 = TV_LIST_ITEM_NEXT(n2, item2))) { if (!tv_equal(TV_LIST_ITEM_TV(item1), TV_LIST_ITEM_TV(item2), ic, recursive)) { return false; diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index e4ed2bc636..e9531178ef 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -586,9 +586,10 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( } size_t len; char *buf; - if (!encode_vim_list_to_buf( - TV_LIST_ITEM_TV(tv_list_last(val_list))->vval.v_list, &len, - &buf)) { + if (!( + encode_vim_list_to_buf( + TV_LIST_ITEM_TV(tv_list_last(val_list))->vval.v_list, &len, + &buf))) { goto _convert_one_value_regular_dict; } TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type); @@ -721,7 +722,8 @@ typval_encode_stop_converting_one_item: TV_LIST_ITEM_TV(cur_mpsv->data.l.li)->vval.v_list); TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK( encode_vim_to__error_ret, *TV_LIST_ITEM_TV(tv_list_first(kv_pair))); - if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE( + if ( + _TYPVAL_ENCODE_CONVERT_ONE_VALUE( TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, cur_mpsv, TV_LIST_ITEM_TV(tv_list_first(kv_pair)), copyID, objname) == FAIL) { diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 3cc73b2e40..a5e131190d 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -5616,7 +5616,7 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet) goto err; } char_u *regtype = TV_LIST_ITEM_TV(tv_list_last(res))->vval.v_string; - if (regtype == NULL || strlen((char*)regtype) > 1) { + if (regtype == NULL || strlen((char *)regtype) > 1) { goto err; } switch (regtype[0]) { diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 75f91ce6d8..6bf816bb74 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1646,20 +1646,20 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, case kSDItemHistoryEntry: { const bool is_hist_search = entry.data.history_item.histtype == HIST_SEARCH; - const size_t arr_size = 2 + (size_t) is_hist_search + (size_t) ( + const size_t arr_size = 2 + (size_t)is_hist_search + (size_t)( tv_list_len(entry.data.history_item.additional_elements)); msgpack_pack_array(spacker, arr_size); msgpack_pack_uint8(spacker, entry.data.history_item.histtype); PACK_BIN(cstr_as_string(entry.data.history_item.string)); if (is_hist_search) { - msgpack_pack_uint8(spacker, (uint8_t) entry.data.history_item.sep); + msgpack_pack_uint8(spacker, (uint8_t)entry.data.history_item.sep); } DUMP_ADDITIONAL_ELEMENTS(entry.data.history_item.additional_elements, "history entry item"); break; } case kSDItemVariable: { - const size_t arr_size = 2 + (size_t) ( + const size_t arr_size = 2 + (size_t)( tv_list_len(entry.data.global_var.additional_elements)); msgpack_pack_array(spacker, arr_size); const String varname = cstr_as_string(entry.data.global_var.name); @@ -1679,7 +1679,7 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, break; } case kSDItemSubString: { - const size_t arr_size = 1 + (size_t) ( + const size_t arr_size = 1 + (size_t)( tv_list_len(entry.data.sub_string.additional_elements)); msgpack_pack_array(spacker, arr_size); PACK_BIN(cstr_as_string(entry.data.sub_string.sub)); diff --git a/src/nvim/window.c b/src/nvim/window.c index 21d668a2bd..4c996aea79 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -5577,8 +5577,7 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, } // Set up position matches - if (pos_list != NULL) - { + if (pos_list != NULL) { linenr_T toplnum = 0; linenr_T botlnum = 0; -- cgit From 1a961b57505f57130012fe4fcfda0e8009c8da45 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 11 Dec 2017 10:34:58 +0300 Subject: eval: Fix add() --- src/nvim/eval.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 10dbf208ea..357ca52a3d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -6544,11 +6544,11 @@ static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) { list_T *l; - rettv->vval.v_number = 1; /* Default: Failed */ if (argvars[0].v_type == VAR_LIST) { - if ((l = argvars[0].vval.v_list) != NULL - && !tv_check_lock(tv_list_locked(l), "add() argument", TV_TRANSLATE)) { + if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + N_("add() argument"), + TV_TRANSLATE)) { tv_list_append_tv(l, &argvars[1]); tv_copy(&argvars[0], rettv); } -- cgit From 9f534422e6c3649ee40904ae3606455d26b48188 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 11 Dec 2017 11:09:09 +0300 Subject: eval/typval: Fix typo [ci skip] --- src/nvim/eval/typval.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index a7255fdba0..c0d1790c63 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -422,7 +422,7 @@ static inline int tv_list_uidx(const list_T *const l, int n) /// Normalize index: that is, return either -1 or non-negative index /// -/// @param[in] l List to intex. Used to get length. +/// @param[in] l List to index. Used to get length. /// @param[in] n List index, possibly negative. /// /// @return -1 or list index in range [0, tv_list_len(l)). -- cgit From ceed29687f7772a661ab7caf5e32a7dfadebf743 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 11 Dec 2017 10:50:45 -0500 Subject: Disable translation of default 'titleold' value It's an empty string, so there's no reason to try to translate it. Closes #7717 --- src/nvim/options.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/options.lua b/src/nvim/options.lua index dd28a765fd..16f472230a 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2505,11 +2505,10 @@ return { full_name='titleold', type='string', scope={'global'}, secure=true, - gettext=true, no_mkrc=true, vi_def=true, varname='p_titleold', - defaults={if_true={vi=N_("")}} + defaults={if_true={vi=""}} }, { full_name='titlestring', -- cgit From 45998deb5d325c7a44ce38b0c7d954919458f105 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 12 Dec 2017 00:52:14 +0300 Subject: *: Fix linter errors --- src/nvim/api/vim.c | 4 ++-- src/nvim/eval.c | 8 +++----- src/nvim/tui/tui.c | 34 ++++++++++++++++++---------------- 3 files changed, 23 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index f0db391abe..c0daac8085 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -900,9 +900,9 @@ typedef struct { Object *ret_node_p; } ExprASTConvStackItem; -///@cond DOXYGEN_NOT_A_FUNCTION +/// @cond DOXYGEN_NOT_A_FUNCTION typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; -///@endcond +/// @endcond /// Parse a VimL expression /// diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 357ca52a3d..2d8c22cc7a 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -6543,12 +6543,10 @@ static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - list_T *l; - rettv->vval.v_number = 1; /* Default: Failed */ + rettv->vval.v_number = 1; // Default: failed. if (argvars[0].v_type == VAR_LIST) { - if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), - N_("add() argument"), - TV_TRANSLATE)) { + list_T *const l = argvars[0].vval.v_list; + if (!tv_check_lock(tv_list_locked(l), N_("add() argument"), TV_TRANSLATE)) { tv_list_append_tv(l, &argvars[1]); tv_copy(&argvars[0], rettv); } diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 9ff1acf64a..60897c08da 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1515,22 +1515,24 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, // DECSCUSR (cursor shape) sequence is widely supported by several terminal // types. https://github.com/gnachman/iTerm2/pull/92 // xterm extension: vertical bar - if (!konsole && ((xterm && !vte_version) // anything claiming xterm compat - // per MinTTY 0.4.3-1 release notes from 2009 - || putty - // per https://bugzilla.gnome.org/show_bug.cgi?id=720821 - || (vte_version >= 3900) - || tmux // per tmux manual page - // https://lists.gnu.org/archive/html/screen-devel/2013-03/msg00000.html - || screen - || st // #7641 - || rxvt // per command.C - // per analysis of VT100Terminal.m - || iterm || iterm_pretending_xterm - || teraterm // per TeraTerm "Supported Control Functions" doco - // Some linux-type terminals (such as console-terminal-emulator - // from the nosh toolset) implement the xterm extension. - || (linuxvt && (xterm_version || (vte_version > 0) || colorterm)))) { + if (!konsole + && ((xterm && !vte_version) // anything claiming xterm compat + // per MinTTY 0.4.3-1 release notes from 2009 + || putty + // per https://bugzilla.gnome.org/show_bug.cgi?id=720821 + || (vte_version >= 3900) + || tmux // per tmux manual page + // https://lists.gnu.org/archive/html/screen-devel/2013-03/msg00000.html + || screen + || st // #7641 + || rxvt // per command.C + // per analysis of VT100Terminal.m + || iterm || iterm_pretending_xterm + || teraterm // per TeraTerm "Supported Control Functions" doco + // Some linux-type terminals implement the xterm extension. + // Example: console-terminal-emulator from the nosh toolset. + || (linuxvt + && (xterm_version || (vte_version > 0) || colorterm)))) { data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss", "\x1b[%p1%d q"); if (-1 == data->unibi_ext.reset_cursor_style) { -- cgit From 932ea7a0d1d19288fad719afd52e9cbeb924c4c2 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 12 Dec 2017 01:13:04 +0300 Subject: clint,eval: Make linter check for direct usage of list attributes --- src/clint.py | 9 +++++++++ src/nvim/eval.c | 33 ++++++++++++++++++++++----------- src/nvim/eval/typval.h | 15 +++++++++++++++ 3 files changed, 46 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/clint.py b/src/clint.py index e63175a69b..8426807c80 100755 --- a/src/clint.py +++ b/src/clint.py @@ -201,6 +201,7 @@ _ERROR_CATEGORIES = [ 'runtime/printf', 'runtime/printf_format', 'runtime/threadsafe_fn', + 'runtime/deprecated', 'syntax/parenthesis', 'whitespace/alignment', 'whitespace/blank_line', @@ -3200,6 +3201,14 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, if match: error(filename, linenum, 'runtime/printf', 4, 'Use xstrlcat or snprintf instead of %s' % match.group(1)) + if not Search(r'eval/typval\.[ch]$', filename): + match = Search(r'(?:\.|->)' + r'(?:lv_(?:first|last|refcount|len|watch|idx(?:_item)?' + r'|copylist|lock)' + r'|li_(?:next|prev|tv))\b', line) + if match: + error(filename, linenum, 'runtime/deprecated', 4, + 'Accessing list_T internals directly is prohibited') # Check for suspicious usage of "if" like # } if (a == b) { diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 2d8c22cc7a..37356d43b4 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5249,8 +5249,8 @@ static int free_unref_items(int copyID) // But don't free a list that has a watcher (used in a for loop), these // are not referenced anywhere. for (list_T *ll = gc_first_list; ll != NULL; ll = ll->lv_used_next) { - if ((ll->lv_copyID & COPYID_MASK) != (copyID & COPYID_MASK) - && ll->lv_watch == NULL) { + if ((tv_list_copyid(ll) & COPYID_MASK) != (copyID & COPYID_MASK) + && !tv_list_has_watchers(ll)) { // Free the List and ordinary items it contains, but don't recurse // into Lists and Dictionaries, they will be in the list of dicts // or list of lists. @@ -5270,7 +5270,7 @@ static int free_unref_items(int copyID) for (ll = gc_first_list; ll != NULL; ll = ll_next) { ll_next = ll->lv_used_next; if ((ll->lv_copyID & COPYID_MASK) != (copyID & COPYID_MASK) - && ll->lv_watch == NULL) { + && !tv_list_has_watchers(ll)) { // Free the List and ordinary items it contains, but don't recurse // into Lists and Dictionaries, they will be in the list of dicts // or list of lists. @@ -21009,6 +21009,22 @@ void func_ptr_ref(ufunc_T *fp) } } +/// Check whether funccall is still referenced outside +/// +/// It is supposed to be referenced if either it is referenced itself or if l:, +/// a: or a:000 are referenced as all these are statically allocated within +/// funccall structure. +static inline bool fc_referenced(const funccall_T *const fc) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_NONNULL_ALL +{ + return ((fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated) + != DO_NOT_FREE_CNT) + || fc->l_vars.dv_refcount != DO_NOT_FREE_CNT + || fc->l_avars.dv_refcount != DO_NOT_FREE_CNT + || fc->fc_refcount > 0); +} + /// Call a user function /// /// @param fp Function to call. @@ -21357,10 +21373,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, // If the a:000 list and the l: and a: dicts are not referenced and there // is no closure using it, we can free the funccall_T and what's in it. - if (fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT - && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT - && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT - && fc->fc_refcount <= 0) { + if (!fc_referenced(fc)) { free_funccal(fc, false); } else { // "fc" is still in use. This can happen when returning "a:000", @@ -21404,10 +21417,8 @@ static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force) return; } - if (--fc->fc_refcount <= 0 && (force || ( - fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT - && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT - && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT))) { + fc->fc_refcount--; + if (force ? fc->fc_refcount <= 0 : !fc_referenced(fc)) { for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) { if (fc == *pfc) { *pfc = fc->caller; diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index c0d1790c63..2bce7bd6b2 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -440,6 +440,21 @@ static inline int tv_list_uidx(const list_T *const l, int n) return n; } +static inline bool tv_list_has_watchers(const list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Check whether list has watchers +/// +/// E.g. is referenced by a :for loop. +/// +/// @param[in] l List to check. +/// +/// @return true if there are watchers, false otherwise. +static inline bool tv_list_has_watchers(const list_T *const l) +{ + return l && l->lv_watch; +} + static inline listitem_T *tv_list_first(const list_T *const l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; -- cgit From 34057045beca40406673ff421a4ef1e8e8c08853 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Tue, 12 Dec 2017 18:23:19 +0100 Subject: ui: forward relevant option updates to UIs (#7520) also make termguicolors mutable after startup --- src/nvim/api/ui.c | 1 + src/nvim/api/ui_events.in.h | 2 ++ src/nvim/generators/gen_api_ui_events.lua | 15 ++++++++--- src/nvim/generators/gen_options.lua | 1 + src/nvim/option.c | 42 +++++++++++++++++++++++++++++++ src/nvim/option_defs.h | 3 +++ src/nvim/options.lua | 26 +++++++++++-------- src/nvim/tui/tui.c | 9 +++++++ src/nvim/ui.c | 1 + src/nvim/ui_bridge.c | 24 ++++++++++++++++++ 10 files changed, 110 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index a9eaccfac5..35508fde6b 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -93,6 +93,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, ui->suspend = remote_ui_suspend; ui->set_title = remote_ui_set_title; ui->set_icon = remote_ui_set_icon; + ui->option_set = remote_ui_option_set; ui->event = remote_ui_event; memset(ui->ui_ext, 0, sizeof(ui->ui_ext)); diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 847b21072a..3d2253e918 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -58,6 +58,8 @@ void set_title(String title) FUNC_API_SINCE(3); void set_icon(String icon) FUNC_API_SINCE(3); +void option_set(String name, Object value) + FUNC_API_SINCE(4) FUNC_API_BRIDGE_IMPL; void popupmenu_show(Array items, Integer selected, Integer row, Integer col) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua index d2b90db707..2666ca6e6f 100644 --- a/src/nvim/generators/gen_api_ui_events.lua +++ b/src/nvim/generators/gen_api_ui_events.lua @@ -37,7 +37,7 @@ function write_arglist(output, ev, need_copy) for j = 1, #ev.parameters do local param = ev.parameters[j] local kind = string.upper(param[1]) - local do_copy = need_copy and (kind == "ARRAY" or kind == "DICTIONARY" or kind == "STRING") + local do_copy = need_copy and (kind == "ARRAY" or kind == "DICTIONARY" or kind == "STRING" or kind == "OBJECT") output:write(' ADD(args, ') if do_copy then output:write('copy_object(') @@ -91,7 +91,7 @@ for i = 1, #events do recv_cleanup = recv_cleanup..' api_free_string('..param[2]..');\n' argc = argc+2 elseif param[1] == 'Array' then - send = send..' Array copy_'..param[2]..' = copy_array('..param[2]..');\n' + send = send..' Array '..copy..' = copy_array('..param[2]..');\n' argv = argv..', '..copy..'.items, INT2PTR('..copy..'.size)' recv = (recv..' Array '..param[2].. ' = (Array){.items = argv['..argc..'],'.. @@ -99,6 +99,15 @@ for i = 1, #events do recv_argv = recv_argv..', '..param[2] recv_cleanup = recv_cleanup..' api_free_array('..param[2]..');\n' argc = argc+2 + elseif param[1] == 'Object' then + send = send..' Object *'..copy..' = xmalloc(sizeof(Object));\n' + send = send..' *'..copy..' = copy_object('..param[2]..');\n' + argv = argv..', '..copy + recv = recv..' Object '..param[2]..' = *(Object *)argv['..argc..'];\n' + recv_argv = recv_argv..', '..param[2] + recv_cleanup = (recv_cleanup..' api_free_object('..param[2]..');\n'.. + ' xfree(argv['..argc..']);\n') + argc = argc+1 elseif param[1] == 'Integer' or param[1] == 'Boolean' then argv = argv..', INT2PTR('..param[2]..')' recv_argv = recv_argv..', PTR2INT(argv['..argc..'])' @@ -119,7 +128,7 @@ for i = 1, #events do write_signature(bridge_output, ev, 'UI *ui') bridge_output:write('\n{\n') bridge_output:write(send) - bridge_output:write(' UI_BRIDGE_CALL(ui, '..ev.name..', '..argc..', ui'..argv..');\n}\n') + bridge_output:write(' UI_BRIDGE_CALL(ui, '..ev.name..', '..argc..', ui'..argv..');\n}\n\n') end end diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index 36562c0be9..fdc00d5dc0 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -36,6 +36,7 @@ local redraw_flags={ all_windows='P_RALL', everything='P_RCLR', curswant='P_CURSWANT', + ui_option='P_UI_OPTION', } local list_flags={ diff --git a/src/nvim/option.c b/src/nvim/option.c index f8a05f133d..499cf79836 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -74,6 +74,7 @@ #include "nvim/undo.h" #include "nvim/window.h" #include "nvim/os/os.h" +#include "nvim/api/private/helpers.h" #include "nvim/os/input.h" #include "nvim/os/lang.h" @@ -248,6 +249,7 @@ typedef struct vimoption { #define P_RWINONLY 0x10000000U ///< only redraw current window #define P_NDNAME 0x20000000U ///< only normal dir name chars allowed +#define P_UI_OPTION 0x40000000U ///< send option to remote ui #define HIGHLIGHT_INIT \ "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText," \ @@ -1188,6 +1190,7 @@ do_set ( set_options_default(OPT_FREE | opt_flags); didset_options(); didset_options2(); + ui_refresh_options(); redraw_all_later(CLEAR); } else { showoptions(1, opt_flags); @@ -1815,6 +1818,10 @@ do_set ( NULL, false, NULL); reset_v_option_vars(); xfree(saved_origval); + if (options[opt_idx].flags & P_UI_OPTION) { + ui_call_option_set(cstr_as_string(options[opt_idx].fullname), + STRING_OBJ(cstr_as_string(*(char **)varp))); + } } } else { // key code option(FIXME(tarruda): Show a warning or something @@ -2417,6 +2424,10 @@ static char *set_string_option(const int opt_idx, const char *const value, NULL, false, NULL); reset_v_option_vars(); xfree(saved_oldval); + if (options[opt_idx].flags & P_UI_OPTION) { + ui_call_option_set(cstr_as_string(options[opt_idx].fullname), + STRING_OBJ(cstr_as_string((char *)(*varp)))); + } } return r; @@ -4024,6 +4035,10 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, (char_u *) options[opt_idx].fullname, NULL, false, NULL); reset_v_option_vars(); + if (options[opt_idx].flags & P_UI_OPTION) { + ui_call_option_set(cstr_as_string(options[opt_idx].fullname), + BOOLEAN_OBJ(value)); + } } comp_col(); /* in case 'ruler' or 'showcmd' changed */ @@ -4429,6 +4444,10 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, (char_u *) options[opt_idx].fullname, NULL, false, NULL); reset_v_option_vars(); + if (options[opt_idx].flags & P_UI_OPTION) { + ui_call_option_set(cstr_as_string(options[opt_idx].fullname), + INTEGER_OBJ(value)); + } } comp_col(); /* in case 'columns' or 'ls' changed */ @@ -4999,6 +5018,29 @@ static int optval_default(vimoption_T *p, char_u *varp) return STRCMP(*(char_u **)varp, p->def_val[dvi]) == 0; } +/// Send update to UIs with values of UI relevant options +void ui_refresh_options(void) +{ + for (int opt_idx = 0; options[opt_idx].fullname; opt_idx++) { + uint32_t flags = options[opt_idx].flags; + if (!(flags & P_UI_OPTION)) { + continue; + } + String name = cstr_as_string(options[opt_idx].fullname); + void *varp = options[opt_idx].var; + Object value = OBJECT_INIT; + if (flags & P_BOOL) { + value = BOOLEAN_OBJ(*(int *)varp); + } else if (flags & P_NUM) { + value = INTEGER_OBJ(*(long *)varp); + } else if (flags & P_STRING) { + // cstr_as_string handles NULL string + value = STRING_OBJ(cstr_as_string(*(char **)varp)); + } + ui_call_option_set(name, value); + } +} + /* * showoneopt: show the value of one option * must not be called with a hidden option! diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index b16f222705..1b978137ae 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -447,6 +447,9 @@ EXTERN char_u *p_popt; // 'printoptions' EXTERN char_u *p_header; // 'printheader' EXTERN int p_prompt; // 'prompt' EXTERN char_u *p_guicursor; // 'guicursor' +EXTERN char_u *p_guifont; // 'guifont' +EXTERN char_u *p_guifontset; // 'guifontset' +EXTERN char_u *p_guifontwide; // 'guifontwide' EXTERN char_u *p_hf; // 'helpfile' EXTERN long p_hh; // 'helpheight' EXTERN char_u *p_hlg; // 'helplang' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 16f472230a..45efd49391 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -68,7 +68,8 @@ return { type='bool', scope={'global'}, vi_def=true, vim=true, - redraw={'everything'}, + redraw={'everything', 'ui_option'}, + varname='p_arshape', defaults={if_true={vi=true}} }, @@ -91,7 +92,7 @@ return { full_name='ambiwidth', abbreviation='ambw', type='string', scope={'global'}, vi_def=true, - redraw={'everything'}, + redraw={'everything', 'ui_option'}, varname='p_ambw', defaults={if_true={vi="single"}} }, @@ -661,7 +662,7 @@ return { full_name='emoji', abbreviation='emo', type='bool', scope={'global'}, vi_def=true, - redraw={'everything'}, + redraw={'everything', 'ui_option'}, varname='p_emoji', defaults={if_true={vi=true}} }, @@ -1021,23 +1022,26 @@ return { type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, - redraw={'everything'}, - enable_if=false, + varname='p_guifont', + redraw={'everything', 'ui_option'}, + defaults={if_true={vi=""}} }, { full_name='guifontset', abbreviation='gfs', type='string', list='onecomma', scope={'global'}, vi_def=true, - redraw={'everything'}, - enable_if=false, + varname='p_guifontset', + redraw={'everything', 'ui_option'}, + defaults={if_true={vi=""}} }, { full_name='guifontwide', abbreviation='gfw', type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, - redraw={'everything'}, - enable_if=false, + redraw={'everything', 'ui_option'}, + varname='p_guifontwide', + defaults={if_true={vi=""}} }, { full_name='guioptions', abbreviation='go', @@ -2164,7 +2168,7 @@ return { full_name='showtabline', abbreviation='stal', type='number', scope={'global'}, vi_def=true, - redraw={'all_windows'}, + redraw={'all_windows', 'ui_option'}, varname='p_stal', defaults={if_true={vi=1}} }, @@ -2435,7 +2439,7 @@ return { full_name='termguicolors', abbreviation='tgc', type='bool', scope={'global'}, vi_def=false, - redraw={'everything'}, + redraw={'everything', 'ui_option'}, varname='p_tgc', defaults={if_true={vi=false}} }, diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 9ff1acf64a..49aa41b9b0 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -151,6 +151,7 @@ UI *tui_start(void) ui->suspend = tui_suspend; ui->set_title = tui_set_title; ui->set_icon = tui_set_icon; + ui->option_set= tui_option_set; ui->event = tui_event; memset(ui->ui_ext, 0, sizeof(ui->ui_ext)); @@ -1136,6 +1137,14 @@ static void tui_set_icon(UI *ui, String icon) { } +static void tui_option_set(UI *ui, String name, Object value) +{ + if (strequal(name.data, "termguicolors")) { + // NB: value for bridge is set in ui_bridge.c + ui->rgb = value.data.boolean; + } +} + // NB: if we start to use this, the ui_bridge must be updated // to make a copy for the tui thread static void tui_event(UI *ui, char *name, Array args, bool *args_consumed) diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 3b8b3ac9a7..81da88c54a 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -339,6 +339,7 @@ void ui_attach_impl(UI *ui) } uis[ui_count++] = ui; + ui_refresh_options(); ui_refresh(); } diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c index 7573fa1653..0a69cf0ecb 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -66,6 +66,7 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler) rv->bridge.suspend = ui_bridge_suspend; rv->bridge.set_title = ui_bridge_set_title; rv->bridge.set_icon = ui_bridge_set_icon; + rv->bridge.option_set = ui_bridge_option_set; rv->scheduler = scheduler; for (UIWidget i = 0; (int)i < UI_WIDGETS; i++) { @@ -144,6 +145,29 @@ static void ui_bridge_highlight_set_event(void **argv) xfree(argv[1]); } +static void ui_bridge_option_set(UI *ui, String name, Object value) +{ + // Assumes bridge is only used by TUI + if (strequal(name.data, "termguicolors")) { + ui->rgb = value.data.boolean; + } + String copy_name = copy_string(name); + Object *copy_value = xmalloc(sizeof(Object)); + *copy_value = copy_object(value); + UI_BRIDGE_CALL(ui, option_set, 4, ui, + copy_name.data, INT2PTR(copy_name.size), copy_value); +} +static void ui_bridge_option_set_event(void **argv) +{ + UI *ui = UI(argv[0]); + String name = (String){ .data = argv[1], .size = (size_t)argv[2] }; + Object value = *(Object *)argv[3]; + ui->option_set(ui, name, value); + api_free_string(name); + api_free_object(value); + xfree(argv[3]); +} + static void ui_bridge_suspend(UI *b) { UIBridgeData *data = (UIBridgeData *)b; -- cgit From 6203c23449cfdf8fb09c33d3ab267703d57123fa Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 12 Dec 2017 01:31:15 +0100 Subject: pty_process_unix: _exit() on execvp() failure Mostly cargo-culting based on a reading of the manpages, interwebs, and the Vim source. --- src/nvim/os/pty_process_unix.c | 8 ++++---- src/nvim/os/signal.c | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index 53301e4b53..855ca2ae47 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -72,8 +72,7 @@ int pty_process_spawn(PtyProcess *ptyproc) ELOG("forkpty failed: %s", strerror(errno)); return status; } else if (pid == 0) { - init_child(ptyproc); - abort(); + init_child(ptyproc); // never returns } // make sure the master file descriptor is non blocking @@ -163,14 +162,15 @@ static void init_child(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL Process *proc = (Process *)ptyproc; if (proc->cwd && os_chdir(proc->cwd) != 0) { - fprintf(stderr, "chdir failed: %s\n", strerror(errno)); + ELOG("chdir failed: %s", strerror(errno)); return; } char *prog = ptyproc->process.argv[0]; setenv("TERM", ptyproc->term_name ? ptyproc->term_name : "ansi", 1); execvp(prog, ptyproc->process.argv); - fprintf(stderr, "execvp failed: %s: %s\n", strerror(errno), prog); + ELOG("execvp failed: %s: %s", strerror(errno), prog); + _exit(122); // 122 is EXEC_FAILED in the Vim source. } static void init_termios(struct termios *termios) FUNC_ATTR_NONNULL_ALL diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index fd6d3b32e4..732be072e1 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -10,6 +10,7 @@ #endif #include "nvim/ascii.h" +#include "nvim/log.h" #include "nvim/vim.h" #include "nvim/globals.h" #include "nvim/memline.h" @@ -162,7 +163,7 @@ static void on_signal(SignalWatcher *handle, int signum, void *data) } break; default: - fprintf(stderr, "Invalid signal %d", signum); + ELOG("invalid signal: %d", signum); break; } } -- cgit From 6b51c72e0cc99c6c03c521f77793028cf63afd45 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 11 Dec 2017 23:05:36 +0100 Subject: tui: rework deferred-termcodes implementation Try another approach to defer the termcodes. Seems less janky, but still not perfect. ref #7664 ref #7649 ref #7664 ref 27f9b1c7b029d8 --- src/nvim/main.c | 3 +++ src/nvim/tui/tui.c | 15 +++++++-------- src/nvim/ui.c | 8 ++++++++ src/nvim/ui.h | 3 +-- src/nvim/ui_bridge.c | 11 +++++++++++ 5 files changed, 30 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/nvim/main.c b/src/nvim/main.c index 0346414697..5d826d9524 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -563,6 +563,9 @@ int main(int argc, char **argv) (void)eval_has_provider("clipboard"); } + if (!headless_mode) { + ui_builtin_after_startup(); + } TIME_MSG("before starting main loop"); ILOG("starting main loop"); diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 49aa41b9b0..0d3a241793 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -126,6 +126,7 @@ UI *tui_start(void) { UI *ui = xcalloc(1, sizeof(UI)); ui->stop = tui_stop; + ui->after_startup = tui_after_startup; ui->rgb = p_tgc; ui->resize = tui_resize; ui->clear = tui_clear; @@ -174,13 +175,7 @@ static size_t unibi_pre_fmt_str(TUIData *data, unsigned int unibi_index, static void terminfo_after_startup_event(void **argv) { UI *ui = argv[0]; - bool defer = argv[1] != NULL; // clever(?) boolean without malloc() dance. TUIData *data = ui->data; - if (defer) { // We're on the main-loop. Now forward to the TUI loop. - loop_schedule(data->loop, - event_create(terminfo_after_startup_event, 2, ui, NULL)); - return; - } // Enable bracketed paste unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste); // Enable focus reporting @@ -278,9 +273,13 @@ static void terminfo_start(UI *ui) uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0); uv_pipe_open(&data->output_handle.pipe, data->out_fd); } +} - loop_schedule(&main_loop, - event_create(terminfo_after_startup_event, 2, ui, ui)); +static void tui_after_startup(UI *ui) +{ + TUIData *data = ui->data; + loop_schedule(data->loop, + event_create(terminfo_after_startup_event, 1, ui)); } static void terminfo_stop(UI *ui) diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 81da88c54a..5c8e9380db 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -138,6 +138,14 @@ void ui_builtin_start(void) #endif } +/// Immediately after VimEnter event. +void ui_builtin_after_startup(void) +{ +#ifdef FEAT_TUI + UI_CALL(after_startup); +#endif +} + void ui_builtin_stop(void) { UI_CALL(stop); diff --git a/src/nvim/ui.h b/src/nvim/ui.h index f1ea0716e6..84d17e9ec2 100644 --- a/src/nvim/ui.h +++ b/src/nvim/ui.h @@ -31,13 +31,12 @@ struct ui_t { bool ui_ext[UI_WIDGETS]; ///< Externalized widgets int width, height; void *data; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ui_events.generated.h" #endif - void (*event)(UI *ui, char *name, Array args, bool *args_consumed); void (*stop)(UI *ui); + void (*after_startup)(UI *ui); }; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c index 0a69cf0ecb..324d821df5 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -42,6 +42,7 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler) rv->ui = ui; rv->bridge.rgb = ui->rgb; rv->bridge.stop = ui_bridge_stop; + rv->bridge.after_startup = ui_bridge_after_startup; rv->bridge.resize = ui_bridge_resize; rv->bridge.clear = ui_bridge_clear; rv->bridge.eol_clear = ui_bridge_eol_clear; @@ -106,6 +107,16 @@ static void ui_thread_run(void *data) bridge->ui_main(bridge, bridge->ui); } +static void ui_bridge_after_startup(UI *b) +{ + UI_BRIDGE_CALL(b, after_startup, 1, b); +} +static void ui_bridge_after_startup_event(void **argv) +{ + UI *ui = UI(argv[0]); + ui->after_startup(ui); +} + static void ui_bridge_stop(UI *b) { UIBridgeData *bridge = (UIBridgeData *)b; -- cgit From ed92ece815e1f32259b3f0397476ff6ac92726e3 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 12 Dec 2017 20:41:25 +0100 Subject: tui: defer termcodes using a timer With this implementation there is no "jank" during startup. Using the main_loop in any fashion is janky. Using only the TUI loop emits the termcodes too soon, or requires bad hacks like counting tui_flush invocations (9 seems to work). ref #7664 ref #7649 ref #7664 ref 27f9b1c7b029d8 --- src/nvim/main.c | 3 --- src/nvim/tui/tui.c | 41 +++++++++++++++++++++-------------------- src/nvim/ui.c | 8 -------- src/nvim/ui.h | 1 - src/nvim/ui_bridge.c | 11 ----------- 5 files changed, 21 insertions(+), 43 deletions(-) (limited to 'src') diff --git a/src/nvim/main.c b/src/nvim/main.c index 5d826d9524..0346414697 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -563,9 +563,6 @@ int main(int argc, char **argv) (void)eval_has_provider("clipboard"); } - if (!headless_mode) { - ui_builtin_after_startup(); - } TIME_MSG("before starting main loop"); ILOG("starting main loop"); diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 0d3a241793..b8f43e9d13 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -70,6 +70,7 @@ typedef struct { UIBridgeData *bridge; Loop *loop; bool stop; + uv_timer_t after_startup_timer; unibi_var_t params[9]; char buf[OUTBUF_SIZE]; size_t bufpos; @@ -126,7 +127,6 @@ UI *tui_start(void) { UI *ui = xcalloc(1, sizeof(UI)); ui->stop = tui_stop; - ui->after_startup = tui_after_startup; ui->rgb = p_tgc; ui->resize = tui_resize; ui->clear = tui_clear; @@ -170,18 +170,6 @@ static size_t unibi_pre_fmt_str(TUIData *data, unsigned int unibi_index, return unibi_run(str, data->params, buf, len); } -/// Emits some termcodes after Nvim startup, which were observed to slowdown -/// rendering during startup in tmux 2.3 (+focus-events). #7649 -static void terminfo_after_startup_event(void **argv) -{ - UI *ui = argv[0]; - TUIData *data = ui->data; - // Enable bracketed paste - unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste); - // Enable focus reporting - unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting); -} - static void termname_set_event(void **argv) { char *termname = argv[0]; @@ -261,6 +249,9 @@ static void terminfo_start(UI *ui) unibi_out(ui, unibi_enter_ca_mode); unibi_out(ui, unibi_keypad_xmit); unibi_out(ui, unibi_clear_screen); + // Enable bracketed paste + unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste); + uv_loop_init(&data->write_loop); if (data->out_isatty) { uv_tty_init(&data->write_loop, &data->output_handle.tty, data->out_fd, 0); @@ -275,13 +266,6 @@ static void terminfo_start(UI *ui) } } -static void tui_after_startup(UI *ui) -{ - TUIData *data = ui->data; - loop_schedule(data->loop, - event_create(terminfo_after_startup_event, 1, ui)); -} - static void terminfo_stop(UI *ui) { TUIData *data = ui->data; @@ -307,6 +291,18 @@ static void terminfo_stop(UI *ui) unibi_destroy(data->ut); } +static void after_startup_timer_cb(uv_timer_t *handle) + FUNC_ATTR_NONNULL_ALL +{ + UI *ui = handle->data; + TUIData *data = ui->data; + uv_timer_stop(&data->after_startup_timer); + + // Emit this after Nvim startup, not during. This works around a tmux + // 2.3 bug(?) which caused slow drawing during startup. #7649 + unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting); +} + static void tui_terminal_start(UI *ui) { TUIData *data = ui->data; @@ -316,6 +312,8 @@ static void tui_terminal_start(UI *ui) update_size(ui); signal_watcher_start(&data->winch_handle, sigwinch_cb, SIGWINCH); term_input_start(&data->input); + + uv_timer_start(&data->after_startup_timer, after_startup_timer_cb, 500, 0); } static void tui_terminal_stop(UI *ui) @@ -349,6 +347,8 @@ static void tui_main(UIBridgeData *bridge, UI *ui) #ifdef UNIX signal_watcher_start(&data->cont_handle, sigcont_cb, SIGCONT); #endif + uv_timer_init(&data->loop->uv, &data->after_startup_timer); + data->after_startup_timer.data = ui; #if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18 data->input.tk_ti_hook_fn = tui_tk_ti_getstr; @@ -367,6 +367,7 @@ static void tui_main(UIBridgeData *bridge, UI *ui) loop_poll_events(&tui_loop, -1); // tui_loop.events is never processed } + uv_close((uv_handle_t *)&data->after_startup_timer, NULL); ui_bridge_stopped(bridge); term_input_destroy(&data->input); signal_watcher_stop(&data->cont_handle); diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 5c8e9380db..81da88c54a 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -138,14 +138,6 @@ void ui_builtin_start(void) #endif } -/// Immediately after VimEnter event. -void ui_builtin_after_startup(void) -{ -#ifdef FEAT_TUI - UI_CALL(after_startup); -#endif -} - void ui_builtin_stop(void) { UI_CALL(stop); diff --git a/src/nvim/ui.h b/src/nvim/ui.h index 84d17e9ec2..0e40a1a215 100644 --- a/src/nvim/ui.h +++ b/src/nvim/ui.h @@ -36,7 +36,6 @@ struct ui_t { #endif void (*event)(UI *ui, char *name, Array args, bool *args_consumed); void (*stop)(UI *ui); - void (*after_startup)(UI *ui); }; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c index 324d821df5..0a69cf0ecb 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -42,7 +42,6 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler) rv->ui = ui; rv->bridge.rgb = ui->rgb; rv->bridge.stop = ui_bridge_stop; - rv->bridge.after_startup = ui_bridge_after_startup; rv->bridge.resize = ui_bridge_resize; rv->bridge.clear = ui_bridge_clear; rv->bridge.eol_clear = ui_bridge_eol_clear; @@ -107,16 +106,6 @@ static void ui_thread_run(void *data) bridge->ui_main(bridge, bridge->ui); } -static void ui_bridge_after_startup(UI *b) -{ - UI_BRIDGE_CALL(b, after_startup, 1, b); -} -static void ui_bridge_after_startup_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->after_startup(ui); -} - static void ui_bridge_stop(UI *b) { UIBridgeData *bridge = (UIBridgeData *)b; -- cgit From 7164f618504bb1b3374da1bcc26c6cf2ac8b3f86 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 13 Dec 2017 22:22:02 +0100 Subject: typval_encode.c.h: avoid -Wnonnull-compare warning (#7712) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * typval_encode.c.h: avoid -Wnonnull-compare warning closes #6847 The NULL check is needed because TYPVAL_ENCODE_CONV_EMPTY_DICT may be invoked with literal `NULL`. Warning occurs even for `Debug` build-type: neovim/src/nvim/eval/typval.c: In function ‘_typval_encode_nothing_convert_one_value’: neovim/src/nvim/eval/typval.c:1802:10: warning: nonnull argument ‘tv’ compared to NULL [-Wnonnull-compare] if (tv != NULL) { \ ^ ../src/nvim/eval/typval_encode.c.h:398:9: note: in expansion of macro ‘TYPVAL_ENCODE_CONV_EMPTY_DICT’ TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, tv->vval.v_dict); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ gcc version: gcc (Ubuntu 6.3.0-12ubuntu2) 6.3.0 20170406 * fixup! typval_encode.c.h: avoid -Wnonnull-compare warning --- src/nvim/eval/typval.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 4bc3a85efb..b5382d1ccb 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1794,14 +1794,20 @@ static inline void _nothing_conv_func_end(typval_T *const tv, const int copyID) tv->v_lock = VAR_UNLOCKED; \ } while (0) +static inline void _nothing_conv_empty_dict(typval_T *const tv, + dict_T **const dictp) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ARG(2) +{ + tv_dict_unref(*dictp); + *dictp = NULL; + if (tv != NULL) { + tv->v_lock = VAR_UNLOCKED; + } +} #define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ do { \ assert((void *)&dict != (void *)&TYPVAL_ENCODE_NODICT_VAR); \ - tv_dict_unref((dict_T *)dict); \ - *((dict_T **)&dict) = NULL; \ - if (tv != NULL) { \ - ((typval_T *)tv)->v_lock = VAR_UNLOCKED; \ - } \ + _nothing_conv_empty_dict(tv, ((dict_T **)&dict)); \ } while (0) static inline int _nothing_conv_real_list_after_start( -- cgit From c8a5d6181b19009e170a3497a30ce35cf288bddf Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 15 Dec 2017 02:39:46 +0300 Subject: *: Fix some problems found during review Still missing: problems in window.c, it should be possible to construct a test for them. --- src/nvim/eval/encode.c | 2 +- src/nvim/eval/typval.c | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 9f16b78976..39897aa9ab 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -209,7 +209,7 @@ bool encode_vim_list_to_buf(const list_T *const list, size_t *const ret_len, return false; } len++; - if (TV_LIST_ITEM_TV(li)->vval.v_string != 0) { + if (TV_LIST_ITEM_TV(li)->vval.v_string != NULL) { len += STRLEN(TV_LIST_ITEM_TV(li)->vval.v_string); } }); diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 0c9c4a0347..5040695b09 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -406,9 +406,7 @@ void tv_list_append_list(list_T *const list, list_T *const itemlist) TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; TV_LIST_ITEM_TV(li)->vval.v_list = itemlist; tv_list_append(list, li); - if (itemlist != NULL) { - tv_list_ref(itemlist); - } + tv_list_ref(itemlist); } /// Append a dictionary to a list @@ -510,6 +508,9 @@ list_T *tv_list_copy(const vimconv_T *const conv, list_T *const orig, orig->lv_copylist = copy; } TV_LIST_ITER(orig, item, { + if (got_int) { + break; + } listitem_T *const ni = tv_list_item_alloc(); if (deep) { if (var_item_copy(conv, TV_LIST_ITEM_TV(item), TV_LIST_ITEM_TV(ni), @@ -605,6 +606,9 @@ static int list_join_inner(garray_T *const gap, list_T *const l, // Stringify each item in the list. TV_LIST_ITER(l, item, { + if (got_int) { + break; + } char *s; size_t len; s = encode_tv2echo(TV_LIST_ITEM_TV(item), &len); @@ -697,7 +701,7 @@ bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic, listitem_T *item2 = tv_list_first(l2); for (; item1 != NULL && item2 != NULL ; (item1 = TV_LIST_ITEM_NEXT(l1, item1), - item2 = TV_LIST_ITEM_NEXT(n2, item2))) { + item2 = TV_LIST_ITEM_NEXT(l2, item2))) { if (!tv_equal(TV_LIST_ITEM_TV(item1), TV_LIST_ITEM_TV(item2), ic, recursive)) { return false; @@ -2108,9 +2112,7 @@ void tv_copy(typval_T *const from, typval_T *const to) break; } case VAR_LIST: { - if (from->vval.v_list != NULL) { - tv_list_ref(to->vval.v_list); - } + tv_list_ref(to->vval.v_list); break; } case VAR_DICT: { -- cgit From fb07391ce46b8bff90ef7ef073b75919a307e64c Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 15 Dec 2017 11:38:34 +0300 Subject: window: Fix matchaddpos() and enhance error reporting --- src/nvim/eval.c | 82 +++++++++++++++++++++++++++---------------------------- src/nvim/window.c | 20 ++++++++------ 2 files changed, 53 insertions(+), 49 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 37356d43b4..640967b91c 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -12440,56 +12440,56 @@ static void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - rettv->vval.v_number = -1; + rettv->vval.v_number = -1; - char buf[NUMBUFLEN]; - const char *const group = tv_get_string_buf_chk(&argvars[0], buf); - if (group == NULL) { - return; - } + char buf[NUMBUFLEN]; + const char *const group = tv_get_string_buf_chk(&argvars[0], buf); + if (group == NULL) { + return; + } - if (argvars[1].v_type != VAR_LIST) { - EMSG2(_(e_listarg), "matchaddpos()"); - return; - } + if (argvars[1].v_type != VAR_LIST) { + EMSG2(_(e_listarg), "matchaddpos()"); + return; + } - list_T *l; - l = argvars[1].vval.v_list; - if (l == NULL) { - return; - } + list_T *l; + l = argvars[1].vval.v_list; + if (l == NULL) { + return; + } - bool error = false; - int prio = 10; - int id = -1; - const char *conceal_char = NULL; + bool error = false; + int prio = 10; + int id = -1; + const char *conceal_char = NULL; - if (argvars[2].v_type != VAR_UNKNOWN) { - prio = tv_get_number_chk(&argvars[2], &error); - if (argvars[3].v_type != VAR_UNKNOWN) { - id = tv_get_number_chk(&argvars[3], &error); - if (argvars[4].v_type != VAR_UNKNOWN) { - if (argvars[4].v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - return; - } - dictitem_T *di; - if ((di = tv_dict_find(argvars[4].vval.v_dict, S_LEN("conceal"))) - != NULL) { - conceal_char = tv_get_string(&di->di_tv); - } + if (argvars[2].v_type != VAR_UNKNOWN) { + prio = tv_get_number_chk(&argvars[2], &error); + if (argvars[3].v_type != VAR_UNKNOWN) { + id = tv_get_number_chk(&argvars[3], &error); + if (argvars[4].v_type != VAR_UNKNOWN) { + if (argvars[4].v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + return; + } + dictitem_T *di; + if ((di = tv_dict_find(argvars[4].vval.v_dict, S_LEN("conceal"))) + != NULL) { + conceal_char = tv_get_string(&di->di_tv); } } } - if (error == true) { - return; - } + } + if (error == true) { + return; + } - // id == 3 is ok because matchaddpos() is supposed to substitute :3match - if (id == 1 || id == 2) { - EMSGN(_("E798: ID is reserved for \"match\": %" PRId64), id); - return; - } + // id == 3 is ok because matchaddpos() is supposed to substitute :3match + if (id == 1 || id == 2) { + EMSGN(_("E798: ID is reserved for \"match\": %" PRId64), id); + return; + } rettv->vval.v_number = match_add(curwin, group, NULL, prio, id, l, conceal_char); diff --git a/src/nvim/window.c b/src/nvim/window.c index 28cb9449d1..583314d437 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -5619,19 +5619,17 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) { const list_T *const subl = TV_LIST_ITEM_TV(li)->vval.v_list; - if (subl == NULL) { - goto fail; - } const listitem_T *subli = tv_list_first(subl); if (subli == NULL) { + emsgf(_("E5030: Empty list at position %d"), + (int)tv_list_idx_of_item(pos_list, li)); goto fail; } lnum = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); if (error) { goto fail; } - if (lnum == 0) { - --i; + if (lnum <= 0) { continue; } m->pos.pos[i].lnum = lnum; @@ -5641,9 +5639,15 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, if (error) { goto fail; } + if (col < 0) { + continue; + } subli = TV_LIST_ITEM_NEXT(subl, subli); if (subli != NULL) { len = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); + if (len < 0) { + continue; + } if (error) { goto fail; } @@ -5652,15 +5656,15 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, m->pos.pos[i].col = col; m->pos.pos[i].len = len; } else if (TV_LIST_ITEM_TV(li)->v_type == VAR_NUMBER) { - if (TV_LIST_ITEM_TV(li)->vval.v_number == 0) { - i--; + if (TV_LIST_ITEM_TV(li)->vval.v_number <= 0) { continue; } m->pos.pos[i].lnum = TV_LIST_ITEM_TV(li)->vval.v_number; m->pos.pos[i].col = 0; m->pos.pos[i].len = 0; } else { - EMSG(_("List or number required")); + emsgf(_("E5031: List or number required at position %d"), + (int)tv_list_idx_of_item(pos_list, li)); goto fail; } if (toplnum == 0 || lnum < toplnum) { -- cgit From d5bce42b524708a54243658e87b1e3bd9c7acdf3 Mon Sep 17 00:00:00 2001 From: Michael Schupikov Date: Sat, 23 Sep 2017 09:56:44 +0200 Subject: vim-patch:8.0.0074 Problem: Cannot make Vim fail on an internal error. Solution: Add IEMSG() and IEMSG2(). (Domenique Pelle) Avoid reporting an internal error without mentioning where. https://github.com/vim/vim/commit/95f096030ed1a8afea028f2ea295d6f6a70f466f Signed-off-by: Michael Schupikov --- src/nvim/edit.c | 4 ++-- src/nvim/eval.c | 25 +++++++++++----------- src/nvim/eval/encode.c | 2 +- src/nvim/eval/typval_encode.c.h | 2 +- src/nvim/ex_eval.c | 14 ++++++------ src/nvim/getchar.c | 29 +++++++++++++------------ src/nvim/globals.h | 2 +- src/nvim/hashtab.c | 2 +- src/nvim/memfile.c | 5 +++-- src/nvim/memline.c | 39 +++++++++++++++++----------------- src/nvim/message.c | 47 ++++++++++++++++++++++++++++++++++++++--- src/nvim/message.h | 9 ++++++++ src/nvim/option.c | 8 +++---- src/nvim/os/env.c | 2 +- src/nvim/quickfix.c | 2 +- src/nvim/regexp.c | 11 +++++----- src/nvim/spell.c | 2 +- src/nvim/undo.c | 20 +++++++++--------- src/nvim/version.c | 2 +- src/nvim/window.c | 2 +- 20 files changed, 144 insertions(+), 85 deletions(-) (limited to 'src') diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 859f98d2ad..4f0a3eaf34 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -1927,7 +1927,7 @@ bool vim_is_ctrl_x_key(int c) case CTRL_X_EVAL: return (c == Ctrl_P || c == Ctrl_N); } - EMSG(_(e_internal)); + internal_error("vim_is_ctrl_x_key()"); return false; } @@ -4681,7 +4681,7 @@ static int ins_complete(int c, bool enable_pum) line = ml_get(curwin->w_cursor.lnum); compl_pattern = vim_strnsave(line + compl_col, compl_length); } else { - EMSG2(_(e_intern2), "ins_complete()"); + internal_error("ins_complete()"); return FAIL; } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 56aedb1b4e..7fa9f7563e 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1102,10 +1102,11 @@ static void restore_vimvar(int idx, typval_T *save_tv) vimvars[idx].vv_tv = *save_tv; if (vimvars[idx].vv_type == VAR_UNKNOWN) { hi = hash_find(&vimvarht, vimvars[idx].vv_di.di_key); - if (HASHITEM_EMPTY(hi)) - EMSG2(_(e_intern2), "restore_vimvar()"); - else + if (HASHITEM_EMPTY(hi)) { + internal_error("restore_vimvar()"); + } else { hash_remove(&vimvarht, hi); + } } } @@ -1567,7 +1568,7 @@ ex_let_vars ( } break; } else if (*arg != ',' && *arg != ']') { - EMSG2(_(e_intern2), "ex_let_vars()"); + internal_error("ex_let_vars()"); return FAIL; } } @@ -2960,7 +2961,7 @@ int do_unlet(const char *const name, const size_t name_len, const int forceit) d = di->di_tv.vval.v_dict; } if (d == NULL) { - EMSG2(_(e_intern2), "do_unlet()"); + internal_error("do_unlet()"); return FAIL; } hashitem_T *hi = hash_find(ht, (const char_u *)varname); @@ -7959,7 +7960,7 @@ static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) n = argvars[0].vval.v_special != kSpecialVarTrue; break; case VAR_UNKNOWN: - EMSG2(_(e_intern2), "f_empty(UNKNOWN)"); + internal_error("f_empty(UNKNOWN)"); break; } @@ -17151,7 +17152,7 @@ static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr) break; } case VAR_UNKNOWN: { - EMSG2(_(e_intern2), "f_type(UNKNOWN)"); + internal_error("f_type(UNKNOWN)"); break; } } @@ -19030,7 +19031,7 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv, } return; } else if (v->di_tv.v_type != tv->v_type) { - EMSG2(_(e_intern2), "set_var()"); + internal_error("set_var()"); } } @@ -19297,7 +19298,7 @@ int var_item_copy(const vimconv_T *const conv, } break; case VAR_UNKNOWN: - EMSG2(_(e_intern2), "var_item_copy(UNKNOWN)"); + internal_error("var_item_copy(UNKNOWN)"); ret = FAIL; } --recurse; @@ -20985,11 +20986,11 @@ void func_unref(char_u *name) if (fp == NULL && isdigit(*name)) { #ifdef EXITFREE if (!entered_free_all_mem) { - EMSG2(_(e_intern2), "func_unref()"); + internal_error("func_unref()"); abort(); } #else - EMSG2(_(e_intern2), "func_unref()"); + internal_error("func_unref()"); abort(); #endif } @@ -21028,7 +21029,7 @@ void func_ref(char_u *name) } else if (isdigit(*name)) { // Only give an error for a numbered function. // Fail silently, when named or lambda function isn't found. - EMSG2(_(e_intern2), "func_ref()"); + internal_error("func_ref()"); } } diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index ef647b3ee4..1607d2139a 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -340,7 +340,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, do { \ const char *const fun_ = (const char *)(fun); \ if (fun_ == NULL) { \ - EMSG2(_(e_intern2), "string(): NULL function name"); \ + internal_error("string(): NULL function name"); \ ga_concat(gap, "function(NULL"); \ } else { \ ga_concat(gap, "function("); \ diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index a93ad2dbba..df820f664c 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -600,7 +600,7 @@ _convert_one_value_regular_dict: {} break; } case VAR_UNKNOWN: { - EMSG2(_(e_intern2), STR(_TYPVAL_ENCODE_CONVERT_ONE_VALUE) "()"); + internal_error(STR(_TYPVAL_ENCODE_CONVERT_ONE_VALUE) "()"); return FAIL; } } diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index 9037b3c151..4010a088c8 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -517,7 +517,7 @@ static void discard_exception(except_T *excp, int was_finished) char_u *saved_IObuff; if (excp == NULL) { - EMSG(_(e_internal)); + internal_error("discard_exception()"); return; } @@ -619,8 +619,9 @@ static void catch_exception(except_T *excp) */ static void finish_exception(except_T *excp) { - if (excp != caught_stack) - EMSG(_(e_internal)); + if (excp != caught_stack) { + internal_error("finish_exception()"); + } caught_stack = caught_stack->caught; if (caught_stack != NULL) { set_vim_var_string(VV_EXCEPTION, (char *) caught_stack->value, -1); @@ -1422,8 +1423,9 @@ void ex_catch(exarg_T *eap) * ":endtry" or when the catch clause is left by a ":continue", * ":break", ":return", ":finish", error, interrupt, or another * exception. */ - if (cstack->cs_exception[cstack->cs_idx] != current_exception) - EMSG(_(e_internal)); + if (cstack->cs_exception[cstack->cs_idx] != current_exception) { + internal_error("ex_catch()"); + } } else { /* * If there is a preceding catch clause and it caught the exception, @@ -1547,7 +1549,7 @@ void ex_finally(exarg_T *eap) * exception will be discarded. */ if (did_throw && cstack->cs_exception[cstack->cs_idx] != current_exception) - EMSG(_(e_internal)); + internal_error("ex_finally()"); } /* diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 4f8a8528a0..1b5d3472ab 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -254,16 +254,17 @@ static void add_buff(buffheader_T *const buf, const char *const s, return; } - if (buf->bh_first.b_next == NULL) { /* first add to list */ + if (buf->bh_first.b_next == NULL) { // first add to list buf->bh_space = 0; buf->bh_curr = &(buf->bh_first); - } else if (buf->bh_curr == NULL) { /* buffer has already been read */ - EMSG(_("E222: Add to read buffer")); + } else if (buf->bh_curr == NULL) { // buffer has already been read + IEMSG(_("E222: Add to read buffer")); return; - } else if (buf->bh_index != 0) + } else if (buf->bh_index != 0) { memmove(buf->bh_first.b_next->b_str, - buf->bh_first.b_next->b_str + buf->bh_index, - STRLEN(buf->bh_first.b_next->b_str + buf->bh_index) + 1); + buf->bh_first.b_next->b_str + buf->bh_index, + STRLEN(buf->bh_first.b_next->b_str + buf->bh_index) + 1); + } buf->bh_index = 0; size_t len; @@ -1152,14 +1153,16 @@ void alloc_typebuf(void) */ void free_typebuf(void) { - if (typebuf.tb_buf == typebuf_init) - EMSG2(_(e_intern2), "Free typebuf 1"); - else + if (typebuf.tb_buf == typebuf_init) { + internal_error("Free typebuf 1"); + } else { xfree(typebuf.tb_buf); - if (typebuf.tb_noremap == noremapbuf_init) - EMSG2(_(e_intern2), "Free typebuf 2"); - else + } + if (typebuf.tb_noremap == noremapbuf_init) { + internal_error("Free typebuf 2"); + } else { xfree(typebuf.tb_noremap); + } } /* @@ -3905,7 +3908,7 @@ makemap ( c1 = 't'; break; default: - EMSG(_("E228: makemap: Illegal mode")); + IEMSG(_("E228: makemap: Illegal mode")); return FAIL; } do { /* do this twice if c2 is set, 3 times with c3 */ diff --git a/src/nvim/globals.h b/src/nvim/globals.h index ef56954aa0..dd216f177f 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -1043,6 +1043,7 @@ EXTERN char_u e_for[] INIT(= N_("E588: :endfor without :for")); EXTERN char_u e_exists[] INIT(= N_("E13: File exists (add ! to override)")); EXTERN char_u e_failed[] INIT(= N_("E472: Command failed")); EXTERN char_u e_internal[] INIT(= N_("E473: Internal error")); +EXTERN char_u e_intern2[] INIT(= N_("E685: Internal error: %s")); EXTERN char_u e_interr[] INIT(= N_("Interrupted")); EXTERN char_u e_invaddr[] INIT(= N_("E14: Invalid address")); EXTERN char_u e_invarg[] INIT(= N_("E474: Invalid argument")); @@ -1134,7 +1135,6 @@ EXTERN char_u e_write[] INIT(= N_("E80: Error while writing")); EXTERN char_u e_zerocount[] INIT(= N_("E939: Positive count required")); EXTERN char_u e_usingsid[] INIT(= N_( "E81: Using not in a script context")); -EXTERN char_u e_intern2[] INIT(= N_("E685: Internal error: %s")); EXTERN char_u e_maxmempat[] INIT(= N_( "E363: pattern uses more memory than 'maxmempattern'")); EXTERN char_u e_emptybuf[] INIT(= N_("E749: empty buffer")); diff --git a/src/nvim/hashtab.c b/src/nvim/hashtab.c index 3397788b00..526bc284a4 100644 --- a/src/nvim/hashtab.c +++ b/src/nvim/hashtab.c @@ -208,7 +208,7 @@ int hash_add(hashtab_T *ht, char_u *key) hash_T hash = hash_hash(key); hashitem_T *hi = hash_lookup(ht, (const char *)key, STRLEN(key), hash); if (!HASHITEM_EMPTY(hi)) { - EMSG2(_(e_intern2), "hash_add()"); + internal_error("hash_add()"); return FAIL; } hash_add_item(ht, hi, key, hash); diff --git a/src/nvim/memfile.c b/src/nvim/memfile.c index 4428dd42aa..4eeba12b87 100644 --- a/src/nvim/memfile.c +++ b/src/nvim/memfile.c @@ -376,8 +376,9 @@ void mf_put(memfile_T *mfp, bhdr_T *hp, bool dirty, bool infile) { unsigned flags = hp->bh_flags; - if ((flags & BH_LOCKED) == 0) - EMSG(_("E293: block was not locked")); + if ((flags & BH_LOCKED) == 0) { + IEMSG(_("E293: block was not locked")); + } flags &= ~BH_LOCKED; if (dirty) { flags |= BH_DIRTY; diff --git a/src/nvim/memline.c b/src/nvim/memline.c index f28a9e60f4..fde21f7992 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -293,7 +293,7 @@ int ml_open(buf_T *buf) */ hp = mf_new(mfp, false, 1); if (hp->bh_bnum != 0) { - EMSG(_("E298: Didn't get block nr 0?")); + IEMSG(_("E298: Didn't get block nr 0?")); goto error; } b0p = hp->bh_data; @@ -335,7 +335,7 @@ int ml_open(buf_T *buf) if ((hp = ml_new_ptr(mfp)) == NULL) goto error; if (hp->bh_bnum != 1) { - EMSG(_("E298: Didn't get block nr 1?")); + IEMSG(_("E298: Didn't get block nr 1?")); goto error; } pp = hp->bh_data; @@ -351,7 +351,7 @@ int ml_open(buf_T *buf) */ hp = ml_new_data(mfp, FALSE, 1); if (hp->bh_bnum != 2) { - EMSG(_("E298: Didn't get block nr 2?")); + IEMSG(_("E298: Didn't get block nr 2?")); goto error; } @@ -635,13 +635,14 @@ static void ml_upd_block0(buf_T *buf, upd_block0_T what) if (mfp == NULL || (hp = mf_get(mfp, 0, 1)) == NULL) return; b0p = hp->bh_data; - if (ml_check_b0_id(b0p) == FAIL) - EMSG(_("E304: ml_upd_block0(): Didn't get block 0??")); - else { - if (what == UB_FNAME) + if (ml_check_b0_id(b0p) == FAIL) { + IEMSG(_("E304: ml_upd_block0(): Didn't get block 0??")); + } else { + if (what == UB_FNAME) { set_b0_fname(b0p, buf); - else /* what == UB_SAME_DIR */ + } else { // what == UB_SAME_DIR set_b0_dir_flag(b0p, buf); + } } mf_put(mfp, hp, true, false); } @@ -1745,7 +1746,7 @@ ml_get_buf ( /* Avoid giving this message for a recursive call, may happen when * the GUI redraws part of the text. */ ++recursive; - EMSGN(_("E315: ml_get: invalid lnum: %" PRId64), lnum); + IEMSGN(_("E315: ml_get: invalid lnum: %" PRId64), lnum); --recursive; } errorret: @@ -1777,7 +1778,7 @@ errorret: /* Avoid giving this message for a recursive call, may happen * when the GUI redraws part of the text. */ ++recursive; - EMSGN(_("E316: ml_get: cannot find line %" PRId64), lnum); + IEMSGN(_("E316: ml_get: cannot find line %" PRId64), lnum); --recursive; } goto errorret; @@ -2162,7 +2163,7 @@ ml_append_int ( return FAIL; pp = hp->bh_data; /* must be pointer block */ if (pp->pb_id != PTR_ID) { - EMSG(_("E317: pointer block id wrong 3")); + IEMSG(_("E317: pointer block id wrong 3")); mf_put(mfp, hp, false, false); return FAIL; } @@ -2295,8 +2296,8 @@ ml_append_int ( * Safety check: fallen out of for loop? */ if (stack_idx < 0) { - EMSG(_("E318: Updated too many blocks?")); - buf->b_ml.ml_stack_top = 0; /* invalidate stack */ + IEMSG(_("E318: Updated too many blocks?")); + buf->b_ml.ml_stack_top = 0; // invalidate stack } } @@ -2435,7 +2436,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, int message) return FAIL; pp = hp->bh_data; /* must be pointer block */ if (pp->pb_id != PTR_ID) { - EMSG(_("E317: pointer block id wrong 4")); + IEMSG(_("E317: pointer block id wrong 4")); mf_put(mfp, hp, false, false); return FAIL; } @@ -2631,7 +2632,7 @@ static void ml_flush_line(buf_T *buf) hp = ml_find_line(buf, lnum, ML_FIND); if (hp == NULL) - EMSGN(_("E320: Cannot find line %" PRId64), lnum); + IEMSGN(_("E320: Cannot find line %" PRId64), lnum); else { dp = hp->bh_data; idx = lnum - buf->b_ml.ml_locked_low; @@ -2841,7 +2842,7 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) pp = (PTR_BL *)(dp); /* must be pointer block */ if (pp->pb_id != PTR_ID) { - EMSG(_("E317: pointer block id wrong")); + IEMSG(_("E317: pointer block id wrong")); goto error_block; } @@ -2880,11 +2881,11 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) } if (idx >= (int)pp->pb_count) { /* past the end: something wrong! */ if (lnum > buf->b_ml.ml_line_count) - EMSGN(_("E322: line number out of range: %" PRId64 " past the end"), + IEMSGN(_("E322: line number out of range: %" PRId64 " past the end"), lnum - buf->b_ml.ml_line_count); else - EMSGN(_("E323: line count wrong in block %" PRId64), bnum); + IEMSGN(_("E323: line count wrong in block %" PRId64), bnum); goto error_block; } if (action == ML_DELETE) { @@ -2960,7 +2961,7 @@ static void ml_lineadd(buf_T *buf, int count) pp = hp->bh_data; /* must be pointer block */ if (pp->pb_id != PTR_ID) { mf_put(mfp, hp, false, false); - EMSG(_("E317: pointer block id wrong 2")); + IEMSG(_("E317: pointer block id wrong 2")); break; } pp->pb_pointer[ip->ip_index].pe_line_count += count; diff --git a/src/nvim/message.c b/src/nvim/message.c index 5c8f0655bf..1d9a4de9c0 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -582,20 +582,61 @@ void emsg_invreg(int name) /// Print an error message with unknown number of arguments bool emsgf(const char *const fmt, ...) +{ + bool ret; + + va_list ap; + va_start(ap, fmt); + ret = emsgfv(fmt, ap); + va_end(ap); + + return ret; +} + +/// Print an error message with unknown number of arguments +static bool emsgfv(const char *fmt, va_list ap) { static char errbuf[IOSIZE]; if (emsg_not_now()) { return true; } - va_list ap; - va_start(ap, fmt); vim_vsnprintf(errbuf, sizeof(errbuf), fmt, ap, NULL); - va_end(ap); return emsg((const char_u *)errbuf); } +/// Same as emsg(...), but abort on error when ABORT_ON_INTERNAL_ERROR is +/// defined. It is used for internal errors only, so that they can be +/// detected when fuzzing vim. +void iemsg(const char *s) +{ + msg((char_u *)s); +#ifdef ABORT_ON_INTERNAL_ERROR + abort(); +#endif +} + +/// Same as emsgf(...) but abort on error when ABORT_ON_INTERNAL_ERROR is +/// defined. It is used for internal errors only, so that they can be +/// detected when fuzzing vim. +void iemsgf(const char *s, ...) +{ + va_list ap; + va_start(ap, s); + (void)emsgfv(s, ap); + va_end(ap); +#ifdef ABORT_ON_INTERNAL_ERROR + abort(); +#endif +} + +/// Give an "Internal error" message. +void internal_error(char *where) +{ + IEMSG2(_(e_intern2), where); +} + static void msg_emsgf_event(void **argv) { char *s = argv[0]; diff --git a/src/nvim/message.h b/src/nvim/message.h index 904a9d3ca1..82935a36a9 100644 --- a/src/nvim/message.h +++ b/src/nvim/message.h @@ -49,6 +49,15 @@ /// Like #EMSG, but for messages with one "%" PRIu64 inside #define EMSGU(s, n) emsgf((const char *) (s), (uint64_t)(n)) +/// Like #EMSG, but for internal messages +#define IEMSG(s) iemsg((const char *)(s)) + +/// Like #EMSG2, but for internal messages +#define IEMSG2(s, p) iemsgf((const char *)(s), (p)) + +/// Like #EMSGN, but for internal messages +#define IEMSGN(s, n) iemsgf((const char *)(s), (int64_t)(n)) + /// Display message at the recorded position #define MSG_PUTS(s) msg_puts((const char *)(s)) diff --git a/src/nvim/option.c b/src/nvim/option.c index 499cf79836..a345906200 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2258,7 +2258,7 @@ int was_set_insecurely(char_u *opt, int opt_flags) uint32_t *flagp = insecure_flag(idx, opt_flags); return (*flagp & P_INSECURE) != 0; } - EMSG2(_(e_intern2), "was_set_insecurely()"); + internal_error("was_set_insecurely()"); return -1; } @@ -2317,8 +2317,8 @@ set_string_option_direct ( if (idx == -1) { // Use name. idx = findoption((const char *)name); if (idx < 0) { // Not found (should not happen). - EMSG2(_(e_intern2), "set_string_option_direct()"); - EMSG2(_("For option %s"), name); + internal_error("set_string_option_direct()"); + IEMSG2(_("For option %s"), name); return; } } @@ -5584,7 +5584,7 @@ static char_u *get_varp(vimoption_T *p) case PV_KMAP: return (char_u *)&(curbuf->b_p_keymap); case PV_SCL: return (char_u *)&(curwin->w_p_scl); case PV_WINHL: return (char_u *)&(curwin->w_p_winhl); - default: EMSG(_("E356: get_varp ERROR")); + default: IEMSG(_("E356: get_varp ERROR")); } /* always return a valid pointer to avoid a crash! */ return (char_u *)&(curbuf->b_p_wm); diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index de0cd10d9c..999fcd434a 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -887,7 +887,7 @@ bool os_setenv_append_path(const char *fname) # define MAX_ENVPATHLEN INT_MAX #endif if (!path_is_absolute_path((char_u *)fname)) { - EMSG2(_(e_intern2), "os_setenv_append_path()"); + internal_error("os_setenv_append_path()"); return false; } const char *tail = (char *)path_tail_with_sep((char_u *)fname); diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 1fc585f0c9..3df025a51d 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2853,7 +2853,7 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) if (old_last == NULL) { if (buf != curbuf) { - EMSG2(_(e_intern2), "qf_fill_buffer()"); + internal_error("qf_fill_buffer()"); return; } diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index ae611a0005..e921706b3a 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -459,14 +459,15 @@ static int toggle_Magic(int x) /* Used for an error (down from) vim_regcomp(): give the error message, set * rc_did_emsg and return NULL */ #define EMSG_RET_NULL(m) return (EMSG(m), rc_did_emsg = TRUE, (void *)NULL) +#define IEMSG_RET_NULL(m) return (IEMSG(m), rc_did_emsg = true, (void *)NULL) #define EMSG_RET_FAIL(m) return (EMSG(m), rc_did_emsg = TRUE, FAIL) #define EMSG2_RET_NULL(m, \ c) return (EMSG2((m), \ - (c) ? "" : "\\"), rc_did_emsg = TRUE, \ + (c) ? "" : "\\"), rc_did_emsg = true, \ (void *)NULL) #define EMSG2_RET_FAIL(m, \ c) return (EMSG2((m), \ - (c) ? "" : "\\"), rc_did_emsg = TRUE, \ + (c) ? "" : "\\"), rc_did_emsg = true, \ FAIL) #define EMSG_ONE_RET_NULL EMSG2_RET_NULL(_( \ "E369: invalid item in %s%%[]"), reg_magic == MAGIC_ALL) @@ -1891,8 +1892,8 @@ static char_u *regatom(int *flagp) case Magic(')'): if (one_exactly) EMSG_ONE_RET_NULL; - EMSG_RET_NULL(_(e_internal)); /* Supposed to be caught earlier. */ - /* NOTREACHED */ + IEMSG_RET_NULL(_(e_internal)); // Supposed to be caught earlier. + // NOTREACHED case Magic('='): case Magic('?'): @@ -4534,7 +4535,7 @@ regmatch ( brace_max[no] = OPERAND_MAX(scan); brace_count[no] = 0; } else { - EMSG(_(e_internal)); /* Shouldn't happen */ + internal_error("BRACE_LIMITS"); status = RA_FAIL; } } diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 42c9bcc0ee..c837038659 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -5355,7 +5355,7 @@ add_sound_suggest ( // Find the word nr in the soundfold tree. sfwordnr = soundfold_find(slang, goodword); if (sfwordnr < 0) { - EMSG2(_(e_intern2), "add_sound_suggest()"); + internal_error("add_sound_suggest()"); return; } diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 3d7ebc8837..f611a3bb29 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -2096,8 +2096,8 @@ void undo_time(long step, int sec, int file, int absolute) uhp = uhp->uh_prev.ptr; if (uhp == NULL || uhp->uh_walk != mark) { - /* Need to redo more but can't find it... */ - EMSG2(_(e_intern2), "undo_time()"); + // Need to redo more but can't find it... + internal_error("undo_time()"); break; } } @@ -2163,8 +2163,8 @@ static void u_undoredo(int undo) if (top > curbuf->b_ml.ml_line_count || top >= bot || bot > curbuf->b_ml.ml_line_count + 1) { unblock_autocmds(); - EMSG(_("E438: u_undo: line numbers wrong")); - changed(); /* don't want UNCHANGED now */ + IEMSG(_("E438: u_undo: line numbers wrong")); + changed(); // don't want UNCHANGED now return; } @@ -2655,7 +2655,7 @@ static void u_unch_branch(u_header_T *uhp) static u_entry_T *u_get_headentry(void) { if (curbuf->b_u_newhead == NULL || curbuf->b_u_newhead->uh_entry == NULL) { - EMSG(_("E439: undo list corrupt")); + IEMSG(_("E439: undo list corrupt")); return NULL; } return curbuf->b_u_newhead->uh_entry; @@ -2684,11 +2684,11 @@ static void u_getbot(void) extra = curbuf->b_ml.ml_line_count - uep->ue_lcount; uep->ue_bot = uep->ue_top + uep->ue_size + 1 + extra; if (uep->ue_bot < 1 || uep->ue_bot > curbuf->b_ml.ml_line_count) { - EMSG(_("E440: undo line missing")); - uep->ue_bot = uep->ue_top + 1; /* assume all lines deleted, will - * get all the old lines back - * without deleting the current - * ones */ + IEMSG(_("E440: undo line missing")); + uep->ue_bot = uep->ue_top + 1; // assume all lines deleted, will + // get all the old lines back + // without deleting the current + // ones } curbuf->b_u_newhead->uh_getbot_entry = NULL; diff --git a/src/nvim/version.c b/src/nvim/version.c index e8cf1d0f0b..8ab9fc1a4b 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -1182,7 +1182,7 @@ static const int included_patches[] = { // 77 NA // 76 NA 75, - // 74, + 74, 73, // 72 NA // 71 NA diff --git a/src/nvim/window.c b/src/nvim/window.c index e39569321e..63c9361663 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -2115,7 +2115,7 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) ptp = ptp->tp_next) ; if (ptp == NULL) { - EMSG2(_(e_intern2), "win_close_othertab()"); + internal_error("win_close_othertab()"); return; } ptp->tp_next = tp->tp_next; -- cgit From dcb2780b834d4df006f55a0475e03bd2a38ac344 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Fri, 15 Dec 2017 15:57:38 -0500 Subject: lint --- src/nvim/memline.c | 29 +++++++++++++++-------------- src/nvim/regexp.c | 16 ++++++---------- 2 files changed, 21 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/nvim/memline.c b/src/nvim/memline.c index fde21f7992..80775c607d 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -1743,11 +1743,11 @@ ml_get_buf ( if (lnum > buf->b_ml.ml_line_count) { /* invalid line number */ if (recursive == 0) { - /* Avoid giving this message for a recursive call, may happen when - * the GUI redraws part of the text. */ - ++recursive; + // Avoid giving this message for a recursive call, may happen when + // the GUI redraws part of the text. + recursive++; IEMSGN(_("E315: ml_get: invalid lnum: %" PRId64), lnum); - --recursive; + recursive--; } errorret: STRCPY(IObuff, "???"); @@ -1775,11 +1775,11 @@ errorret: */ if ((hp = ml_find_line(buf, lnum, ML_FIND)) == NULL) { if (recursive == 0) { - /* Avoid giving this message for a recursive call, may happen - * when the GUI redraws part of the text. */ - ++recursive; + // Avoid giving this message for a recursive call, may happen + // when the GUI redraws part of the text. + recursive++; IEMSGN(_("E316: ml_get: cannot find line %" PRId64), lnum); - --recursive; + recursive--; } goto errorret; } @@ -2631,9 +2631,9 @@ static void ml_flush_line(buf_T *buf) new_line = buf->b_ml.ml_line_ptr; hp = ml_find_line(buf, lnum, ML_FIND); - if (hp == NULL) + if (hp == NULL) { IEMSGN(_("E320: Cannot find line %" PRId64), lnum); - else { + } else { dp = hp->bh_data; idx = lnum - buf->b_ml.ml_locked_low; start = ((dp->db_index[idx]) & DB_INDEX_MASK); @@ -2879,13 +2879,14 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) break; } } - if (idx >= (int)pp->pb_count) { /* past the end: something wrong! */ - if (lnum > buf->b_ml.ml_line_count) + if (idx >= (int)pp->pb_count) { // past the end: something wrong! + if (lnum > buf->b_ml.ml_line_count) { IEMSGN(_("E322: line number out of range: %" PRId64 " past the end"), - lnum - buf->b_ml.ml_line_count); + lnum - buf->b_ml.ml_line_count); - else + } else { IEMSGN(_("E323: line count wrong in block %" PRId64), bnum); + } goto error_block; } if (action == ML_DELETE) { diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index e921706b3a..ec9916f4a2 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -458,17 +458,13 @@ static int toggle_Magic(int x) /* Used for an error (down from) vim_regcomp(): give the error message, set * rc_did_emsg and return NULL */ -#define EMSG_RET_NULL(m) return (EMSG(m), rc_did_emsg = TRUE, (void *)NULL) +#define EMSG_RET_NULL(m) return (EMSG(m), rc_did_emsg = true, (void *)NULL) #define IEMSG_RET_NULL(m) return (IEMSG(m), rc_did_emsg = true, (void *)NULL) -#define EMSG_RET_FAIL(m) return (EMSG(m), rc_did_emsg = TRUE, FAIL) -#define EMSG2_RET_NULL(m, \ - c) return (EMSG2((m), \ - (c) ? "" : "\\"), rc_did_emsg = true, \ - (void *)NULL) -#define EMSG2_RET_FAIL(m, \ - c) return (EMSG2((m), \ - (c) ? "" : "\\"), rc_did_emsg = true, \ - FAIL) +#define EMSG_RET_FAIL(m) return (EMSG(m), rc_did_emsg = true, FAIL) +#define EMSG2_RET_NULL(m, c) \ + return (EMSG2((m), (c) ? "" : "\\"), rc_did_emsg = true, (void *)NULL) +#define EMSG2_RET_FAIL(m, c) \ + return (EMSG2((m), (c) ? "" : "\\"), rc_did_emsg = true, FAIL) #define EMSG_ONE_RET_NULL EMSG2_RET_NULL(_( \ "E369: invalid item in %s%%[]"), reg_magic == MAGIC_ALL) -- cgit From 91d3efa35a26f6c5e58850413ccbb350cb8e7b37 Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 15 Dec 2017 11:40:27 +0300 Subject: eval/encode: Avoid unnecessary tv_list_idx_of_item() calls --- src/nvim/eval/encode.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 39897aa9ab..b7af102681 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -136,13 +136,18 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, } case kMPConvPairs: case kMPConvList: { + const int idx = (v.data.l.li == tv_list_first(v.data.l.list) + ? 0 + : (v.data.l.li == NULL + ? tv_list_len(v.data.l.list) - 1 + : (int)tv_list_idx_of_item( + v.data.l.list, + TV_LIST_ITEM_PREV(v.data.l.list, + v.data.l.li)))); const listitem_T *const li = (v.data.l.li == NULL ? tv_list_last(v.data.l.list) : TV_LIST_ITEM_PREV(v.data.l.list, v.data.l.li)); - int idx = (li == NULL - ? 0 - : (int)tv_list_idx_of_item(v.data.l.list, li)); if (v.type == kMPConvList || li == NULL || (TV_LIST_ITEM_TV(li)->v_type != VAR_LIST -- cgit From bfb21f3e012d9473d6038dd254fc3a0ecdf8c0e9 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 16 Dec 2017 00:38:58 +0100 Subject: tui: rework deferred-termcodes ... again - Revert timer-based approach. - Instead, call loop_poll_events() with a timeout in an "active" loop, to infer that "TUI startup activity has mostly finished", but also to enforce a mininum time (100 ms) before emitting "enable focus reporting" termcode. (If TUI startup takes longer than that minimum time, it's probably a slow environment anyways.) - Tickle `main_loop` by sending a dummy event. Without this, the initial "focus-gained" response from the terminal may not get processed until the user hits a key. ref #7720 ref #7664 ref #7649 ref #7664 ref 27f9b1c7b029d8 --- src/nvim/event/loop.c | 19 +++++++++++++++---- src/nvim/tui/tui.c | 42 +++++++++++++++++++++++++----------------- 2 files changed, 40 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c index 5adf16c0f3..55ef0261d9 100644 --- a/src/nvim/event/loop.c +++ b/src/nvim/event/loop.c @@ -30,19 +30,22 @@ void loop_init(Loop *loop, void *data) uv_signal_init(&loop->uv, &loop->children_watcher); uv_timer_init(&loop->uv, &loop->children_kill_timer); uv_timer_init(&loop->uv, &loop->poll_timer); + loop->poll_timer.data = xmalloc(sizeof(bool)); // "timeout expired" flag } -void loop_poll_events(Loop *loop, int ms) +/// @returns true if `ms` timeout was reached +bool loop_poll_events(Loop *loop, int ms) { if (loop->recursive++) { abort(); // Should not re-enter uv_run } uv_run_mode mode = UV_RUN_ONCE; + bool timeout_expired = false; if (ms > 0) { - // Use a repeating timeout of ms milliseconds to make sure - // we do not block indefinitely for I/O. + *((bool *)loop->poll_timer.data) = false; // reset "timeout expired" flag + // Dummy timer to ensure UV_RUN_ONCE does not block indefinitely for I/O. uv_timer_start(&loop->poll_timer, timer_cb, (uint64_t)ms, (uint64_t)ms); } else if (ms == 0) { // For ms == 0, do a non-blocking event poll. @@ -52,11 +55,13 @@ void loop_poll_events(Loop *loop, int ms) uv_run(&loop->uv, mode); if (ms > 0) { + timeout_expired = *((bool *)loop->poll_timer.data); uv_timer_stop(&loop->poll_timer); } loop->recursive--; // Can re-enter uv_run now multiqueue_process_events(loop->fast_events); + return timeout_expired; } /// Schedules an event from another thread. @@ -111,7 +116,7 @@ bool loop_close(Loop *loop, bool wait) uv_mutex_destroy(&loop->mutex); uv_close((uv_handle_t *)&loop->children_watcher, NULL); uv_close((uv_handle_t *)&loop->children_kill_timer, NULL); - uv_close((uv_handle_t *)&loop->poll_timer, NULL); + uv_close((uv_handle_t *)&loop->poll_timer, timer_close_cb); uv_close((uv_handle_t *)&loop->async, NULL); uint64_t start = wait ? os_hrtime() : 0; while (true) { @@ -163,5 +168,11 @@ static void async_cb(uv_async_t *handle) static void timer_cb(uv_timer_t *handle) { + bool *timeout_expired = handle->data; + *timeout_expired = true; } +static void timer_close_cb(uv_handle_t *handle) +{ + xfree(handle->data); +} diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index b8f43e9d13..3c181aca65 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -70,7 +70,6 @@ typedef struct { UIBridgeData *bridge; Loop *loop; bool stop; - uv_timer_t after_startup_timer; unibi_var_t params[9]; char buf[OUTBUF_SIZE]; size_t bufpos; @@ -291,18 +290,6 @@ static void terminfo_stop(UI *ui) unibi_destroy(data->ut); } -static void after_startup_timer_cb(uv_timer_t *handle) - FUNC_ATTR_NONNULL_ALL -{ - UI *ui = handle->data; - TUIData *data = ui->data; - uv_timer_stop(&data->after_startup_timer); - - // Emit this after Nvim startup, not during. This works around a tmux - // 2.3 bug(?) which caused slow drawing during startup. #7649 - unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting); -} - static void tui_terminal_start(UI *ui) { TUIData *data = ui->data; @@ -312,8 +299,16 @@ static void tui_terminal_start(UI *ui) update_size(ui); signal_watcher_start(&data->winch_handle, sigwinch_cb, SIGWINCH); term_input_start(&data->input); +} + +static void tui_terminal_after_startup(UI *ui) + FUNC_ATTR_NONNULL_ALL +{ + TUIData *data = ui->data; - uv_timer_start(&data->after_startup_timer, after_startup_timer_cb, 500, 0); + // Emit this after Nvim startup, not during. This works around a tmux + // 2.3 bug(?) which caused slow drawing during startup. #7649 + unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting); } static void tui_terminal_stop(UI *ui) @@ -347,8 +342,6 @@ static void tui_main(UIBridgeData *bridge, UI *ui) #ifdef UNIX signal_watcher_start(&data->cont_handle, sigcont_cb, SIGCONT); #endif - uv_timer_init(&data->loop->uv, &data->after_startup_timer); - data->after_startup_timer.data = ui; #if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18 data->input.tk_ti_hook_fn = tui_tk_ti_getstr; @@ -363,11 +356,21 @@ static void tui_main(UIBridgeData *bridge, UI *ui) loop_schedule_deferred(&main_loop, event_create(show_termcap_event, 1, data->ut)); + // "Active" loop: first ~100 ms of startup. + for (size_t ms = 0; ms < 100 && !data->stop;) { + ms += (loop_poll_events(&tui_loop, 20) ? 20 : 1); + } + if (!data->stop) { + tui_terminal_after_startup(ui); + // Tickle `main_loop` with a dummy event, else the initial "focus-gained" + // terminal response may not get processed until user hits a key. + loop_schedule_deferred(&main_loop, event_create(tui_dummy_event, 0)); + } + // "Passive" (I/O-driven) loop: TUI thread "main loop". while (!data->stop) { loop_poll_events(&tui_loop, -1); // tui_loop.events is never processed } - uv_close((uv_handle_t *)&data->after_startup_timer, NULL); ui_bridge_stopped(bridge); term_input_destroy(&data->input); signal_watcher_stop(&data->cont_handle); @@ -379,6 +382,10 @@ static void tui_main(UIBridgeData *bridge, UI *ui) xfree(ui); } +static void tui_dummy_event(void **argv) +{ +} + static void tui_scheduler(Event event, void *d) { UI *ui = d; @@ -1100,6 +1107,7 @@ static void suspend_event(void **argv) loop_poll_events(data->loop, -1); } tui_terminal_start(ui); + tui_terminal_after_startup(ui); if (enable_mouse) { tui_mouse_on(ui); } -- cgit From 76ffe0c5aa51fe4fe14811e86867af2c711190a1 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 16 Dec 2017 14:21:56 +0300 Subject: eval: Fix linter error --- src/nvim/eval.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 640967b91c..3aaa5f349d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -12485,7 +12485,7 @@ static void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - // id == 3 is ok because matchaddpos() is supposed to substitute :3match + // id == 3 is ok because matchaddpos() is supposed to substitute :3match if (id == 1 || id == 2) { EMSGN(_("E798: ID is reserved for \"match\": %" PRId64), id); return; -- cgit From edccf18df56d982dea350770ea42ffa25759b43f Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 17 Dec 2017 15:23:27 +0300 Subject: eval: Fix some issues found in review --- src/nvim/eval.c | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 74b0cdc746..6b04dd4eea 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5336,18 +5336,16 @@ bool set_ref_in_list(list_T *l, int copyID, ht_stack_T **ht_stack) list_T *cur_l = l; for (;;) { - if (!abort) { - // Mark each item in the list. If the item contains a hashtab - // it is added to ht_stack, if it contains a list it is added to - // list_stack. - TV_LIST_ITER(cur_l, li, { - abort = set_ref_in_item(TV_LIST_ITEM_TV(li), copyID, ht_stack, - &list_stack); - if (abort) { - break; - } - }); - } + // Mark each item in the list. If the item contains a hashtab + // it is added to ht_stack, if it contains a list it is added to + // list_stack. + TV_LIST_ITER(cur_l, li, { + if (abort) { + break; + } + abort = set_ref_in_item(TV_LIST_ITEM_TV(li), copyID, ht_stack, + &list_stack); + }); if (list_stack == NULL) { break; @@ -8968,7 +8966,7 @@ static void common_function(typval_T *argvars, typval_T *rettv, goto theend; } list = argvars[arg_idx].vval.v_list; - if (list == NULL || tv_list_len(list) == 0) { + if (tv_list_len(list) == 0) { arg_idx = 0; } } @@ -11069,8 +11067,7 @@ static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[2].v_type != VAR_UNKNOWN) { bool error = false; - // Start at specified item. Use the cached index that tv_list_find() - // sets, so that a negative number also works. + // Start at specified item. idx = tv_list_uidx(l, tv_get_number_chk(&argvars[2], &error)); if (error || idx == -1) { item = NULL; @@ -11080,6 +11077,9 @@ static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr) } if (argvars[3].v_type != VAR_UNKNOWN) { ic = !!tv_get_number_chk(&argvars[3], &error); + if (error) { + item = NULL; + } } } @@ -12248,10 +12248,10 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) } if (l != NULL) { idx = tv_list_uidx(l, start); - li = tv_list_find(l, idx); - if (li == NULL) { + if (idx == -1) { goto theend; } + li = tv_list_find(l, idx); } else { if (start < 0) start = 0; -- cgit From c162bc629440afaf8910f1a29f1dfdb03f898101 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Sat, 16 Dec 2017 21:50:20 -0500 Subject: vim-patch:8.0.0420: text garbled when the system encoding differs from 'encoding' Problem: When running :make the output may be in the system encoding, different from 'encoding'. Solution: Add the 'makeencoding' option. (Ken Takata) https://github.com/vim/vim/commit/2c7292dc5bbf155fe2192d417363b8c085759cad --- src/nvim/buffer.c | 1 + src/nvim/buffer_defs.h | 1 + src/nvim/if_cscope.c | 4 +- src/nvim/main.c | 2 +- src/nvim/option.c | 12 +++- src/nvim/option_defs.h | 2 + src/nvim/options.lua | 7 +++ src/nvim/quickfix.c | 56 +++++++++++++---- src/nvim/testdir/Makefile | 1 + src/nvim/testdir/test_makeencoding.py | 67 +++++++++++++++++++++ src/nvim/testdir/test_makeencoding.vim | 106 +++++++++++++++++++++++++++++++++ 11 files changed, 241 insertions(+), 18 deletions(-) create mode 100644 src/nvim/testdir/test_makeencoding.py create mode 100644 src/nvim/testdir/test_makeencoding.vim (limited to 'src') diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 766003a021..21830539f5 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1808,6 +1808,7 @@ void free_buf_options(buf_T *buf, int free_p_ff) buf->b_p_ul = NO_LOCAL_UNDOLEVEL; clear_string_option(&buf->b_p_lw); clear_string_option(&buf->b_p_bkc); + clear_string_option(&buf->b_p_menc); } diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index f1cbcb2627..5702ceaaef 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -637,6 +637,7 @@ struct file_buffer { uint32_t b_p_fex_flags; ///< flags for 'formatexpr' char_u *b_p_kp; ///< 'keywordprg' int b_p_lisp; ///< 'lisp' + char_u *b_p_menc; ///< 'makeencoding' char_u *b_p_mps; ///< 'matchpairs' int b_p_ml; ///< 'modeline' int b_p_ml_nobin; ///< b_p_ml saved for binary mode diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index 3c02f5acbd..654b4630c5 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -1012,9 +1012,9 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose, fclose(f); if (use_ll) /* Use location list */ wp = curwin; - /* '-' starts a new error list */ + // '-' starts a new error list if (qf_init(wp, tmp, (char_u *)"%f%*\\t%l%*\\t%m", - *qfpos == '-', cmdline) > 0) { + *qfpos == '-', cmdline, NULL) > 0) { if (postponed_split != 0) { (void)win_split(postponed_split > 0 ? postponed_split : 0, postponed_split_flags); diff --git a/src/nvim/main.c b/src/nvim/main.c index 0346414697..0b24023ad0 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -1369,7 +1369,7 @@ static void handle_quickfix(mparm_T *paramp) set_string_option_direct((char_u *)"ef", -1, paramp->use_ef, OPT_FREE, SID_CARG); vim_snprintf((char *)IObuff, IOSIZE, "cfile %s", p_ef); - if (qf_init(NULL, p_ef, p_efm, true, IObuff) < 0) { + if (qf_init(NULL, p_ef, p_efm, true, IObuff, p_menc) < 0) { ui_linefeed(); mch_exit(3); } diff --git a/src/nvim/option.c b/src/nvim/option.c index a345906200..192d2b0f78 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2217,6 +2217,7 @@ void check_buf_options(buf_T *buf) check_string_option(&buf->b_p_tsr); check_string_option(&buf->b_p_lw); check_string_option(&buf->b_p_bkc); + check_string_option(&buf->b_p_menc); } /* @@ -2635,8 +2636,8 @@ did_set_string_option ( else if (varp == &p_ei) { if (check_ei() == FAIL) errmsg = e_invarg; - /* 'encoding' and 'fileencoding' */ - } else if (varp == &p_enc || gvarp == &p_fenc) { + // 'encoding', 'fileencoding' and 'makeencoding' + } else if (varp == &p_enc || gvarp == &p_fenc || gvarp == &p_menc) { if (gvarp == &p_fenc) { if (!MODIFIABLE(curbuf) && opt_flags != OPT_GLOBAL) { errmsg = e_modifiable; @@ -5400,6 +5401,9 @@ void unset_global_local_option(char *name, void *from) case PV_LW: clear_string_option(&buf->b_p_lw); break; + case PV_MENC: + clear_string_option(&buf->b_p_menc); + break; } } @@ -5433,6 +5437,7 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags) case PV_UL: return (char_u *)&(curbuf->b_p_ul); case PV_LW: return (char_u *)&(curbuf->b_p_lw); case PV_BKC: return (char_u *)&(curbuf->b_p_bkc); + case PV_MENC: return (char_u *)&(curbuf->b_p_menc); } return NULL; /* "cannot happen" */ } @@ -5488,6 +5493,8 @@ static char_u *get_varp(vimoption_T *p) ? (char_u *)&(curbuf->b_p_ul) : p->var; case PV_LW: return *curbuf->b_p_lw != NUL ? (char_u *)&(curbuf->b_p_lw) : p->var; + case PV_MENC: return *curbuf->b_p_menc != NUL + ? (char_u *)&(curbuf->b_p_menc) : p->var; case PV_ARAB: return (char_u *)&(curwin->w_p_arab); case PV_LIST: return (char_u *)&(curwin->w_p_list); @@ -5885,6 +5892,7 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_qe = vim_strsave(p_qe); buf->b_p_udf = p_udf; buf->b_p_lw = empty_option; + buf->b_p_menc = empty_option; /* * Don't copy the options set by ex_help(), use the saved values, diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 1b978137ae..864723a10c 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -489,6 +489,7 @@ EXTERN char_u *p_lcs; // 'listchars' EXTERN int p_lz; // 'lazyredraw' EXTERN int p_lpl; // 'loadplugins' EXTERN int p_magic; // 'magic' +EXTERN char_u *p_menc; // 'makeencoding' EXTERN char_u *p_mef; // 'makeef' EXTERN char_u *p_mp; // 'makeprg' EXTERN char_u *p_cc; // 'colorcolumn' @@ -736,6 +737,7 @@ enum { , BV_KP , BV_LISP , BV_LW + , BV_MENC , BV_MA , BV_ML , BV_MOD diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 45efd49391..35baaf948f 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1456,6 +1456,13 @@ return { varname='p_mef', defaults={if_true={vi=""}} }, + { + full_name='makeencoding', abbreviation='menc', + type='string', scope={'global', 'buffer'}, + vi_def=true, + varname='p_menc', + defaults={if_true={vi=""}} + }, { full_name='makeprg', abbreviation='mp', type='string', scope={'global', 'buffer'}, diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 3df025a51d..844ff071c9 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -160,6 +160,7 @@ typedef struct { buf_T *buf; linenr_T buflnum; linenr_T lnumlast; + vimconv_T vc; } qfstate_T; typedef struct { @@ -204,7 +205,8 @@ qf_init ( char_u *efile, char_u *errorformat, int newlist, /* TRUE: start a new error list */ - char_u *qf_title + char_u *qf_title, + char_u *enc ) { qf_info_T *qi = &ql_info; @@ -214,8 +216,8 @@ qf_init ( } return qf_init_ext(qi, efile, curbuf, NULL, errorformat, newlist, - (linenr_T)0, (linenr_T)0, - qf_title); + (linenr_T)0, (linenr_T)0, + qf_title, enc); } // Maximum number of bytes allowed per line while reading an errorfile. @@ -638,6 +640,22 @@ retry: } else { state->linebuf = IObuff; } + + // Convert a line if it contains a non-ASCII character + if (state->vc.vc_type != CONV_NONE && has_non_ascii(state->linebuf)) { + char_u *line = string_convert(&state->vc, state->linebuf, &state->linelen); + if (line != NULL) { + if (state->linelen < IOSIZE) { + STRLCPY(state->linebuf, line, state->linelen + 1); + xfree(line); + } else { + xfree(state->growbuf); + state->linebuf = state->growbuf = line; + state->growbufsiz = state->linelen < LINE_MAXLEN + ? state->linelen : LINE_MAXLEN; + } + } + } return QF_OK; } @@ -979,12 +997,12 @@ qf_init_ext( int newlist, /* TRUE: start a new error list */ linenr_T lnumfirst, /* first line number to use */ linenr_T lnumlast, /* last line number to use */ - char_u *qf_title + char_u *qf_title, + char_u *enc ) { - qfstate_T state = { NULL, 0, NULL, 0, NULL, NULL, NULL, NULL, - NULL, 0, 0 }; - qffields_T fields = { NULL, NULL, 0, 0L, 0, false, NULL, 0, 0, 0 }; + qfstate_T state; + qffields_T fields; qfline_T *old_last = NULL; bool adding = false; static efm_T *fmt_first = NULL; @@ -997,6 +1015,13 @@ qf_init_ext( xfree(qf_last_bufname); qf_last_bufname = NULL; + memset(&state, 0, sizeof(state)); + memset(&fields, 0, sizeof(fields)); + state.vc.vc_type = CONV_NONE; + if (enc != NULL && *enc != NUL) { + convert_setup(&state.vc, enc, p_enc); + } + fields.namebuf = xmalloc(CMDBUFFSIZE + 1); fields.errmsglen = CMDBUFFSIZE + 1; fields.errmsg = xmalloc(fields.errmsglen); @@ -1147,6 +1172,10 @@ qf_init_end: qf_update_buffer(qi, old_last); + if (state.vc.vc_type != CONV_NONE) { + convert_setup(&state.vc, NULL, NULL); + } + return retval; } @@ -3024,6 +3053,7 @@ void ex_make(exarg_T *eap) qf_info_T *qi = &ql_info; int res; char_u *au_name = NULL; + char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; /* Redirect ":grep" to ":vimgrep" if 'grepprg' is "internal". */ if (grep_internal(eap->cmdidx)) { @@ -3083,9 +3113,8 @@ void ex_make(exarg_T *eap) res = qf_init(wp, fname, (eap->cmdidx != CMD_make && eap->cmdidx != CMD_lmake) ? p_gefm : p_efm, - (eap->cmdidx != CMD_grepadd - && eap->cmdidx != CMD_lgrepadd), - *eap->cmdlinep); + (eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd), + *eap->cmdlinep, enc); if (wp != NULL) qi = GET_LOC_LIST(wp); if (au_name != NULL) { @@ -3429,6 +3458,7 @@ void ex_cfile(exarg_T *eap) if (*eap->arg != NUL) set_string_option_direct((char_u *)"ef", -1, eap->arg, OPT_FREE, 0); + char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; /* * This function is used by the :cfile, :cgetfile and :caddfile * commands. @@ -3441,7 +3471,7 @@ void ex_cfile(exarg_T *eap) */ if (qf_init(wp, p_ef, p_efm, (eap->cmdidx != CMD_caddfile && eap->cmdidx != CMD_laddfile), - *eap->cmdlinep) > 0 + *eap->cmdlinep, enc) > 0 && (eap->cmdidx == CMD_cfile || eap->cmdidx == CMD_lfile)) { if (au_name != NULL) @@ -4338,7 +4368,7 @@ void ex_cbuffer(exarg_T *eap) if (qf_init_ext(qi, NULL, buf, NULL, p_efm, (eap->cmdidx != CMD_caddbuffer && eap->cmdidx != CMD_laddbuffer), - eap->line1, eap->line2, qf_title) > 0) { + eap->line1, eap->line2, qf_title, NULL) > 0) { if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name, curbuf->b_fname, true, curbuf); @@ -4403,7 +4433,7 @@ void ex_cexpr(exarg_T *eap) if (qf_init_ext(qi, NULL, NULL, &tv, p_efm, (eap->cmdidx != CMD_caddexpr && eap->cmdidx != CMD_laddexpr), - (linenr_T)0, (linenr_T)0, *eap->cmdlinep) > 0) { + (linenr_T)0, (linenr_T)0, *eap->cmdlinep, NULL) > 0) { if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name, curbuf->b_fname, true, curbuf); diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index e1faaccb84..1f8cf8a0e7 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -65,6 +65,7 @@ NEW_TESTS ?= \ test_increment_dbcs.res \ test_lambda.res \ test_langmap.res \ + test_makeencoding.res \ test_marks.res \ test_match.res \ test_matchadd_conceal.res \ diff --git a/src/nvim/testdir/test_makeencoding.py b/src/nvim/testdir/test_makeencoding.py new file mode 100644 index 0000000000..041edadc0a --- /dev/null +++ b/src/nvim/testdir/test_makeencoding.py @@ -0,0 +1,67 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Test program for :make, :grep and :cgetfile. + +from __future__ import print_function, unicode_literals +import locale +import io +import sys + +def set_output_encoding(enc=None): + """Set the encoding of stdout and stderr + + arguments: + enc -- Encoding name. + If omitted, locale.getpreferredencoding() is used. + """ + if enc is None: + enc = locale.getpreferredencoding() + + def get_text_writer(fo, **kwargs): + kw = dict(kwargs) + kw.setdefault('errors', 'backslashreplace') # use \uXXXX style + kw.setdefault('closefd', False) + + if sys.version_info[0] < 3: + # Work around for Python 2.x + # New line conversion isn't needed here. Done in somewhere else. + writer = io.open(fo.fileno(), mode='w', newline='', **kw) + write = writer.write # save the original write() function + enc = locale.getpreferredencoding() + def convwrite(s): + if isinstance(s, bytes): + write(s.decode(enc)) # convert to unistr + else: + write(s) + try: + writer.flush() # needed on Windows + except IOError: + pass + writer.write = convwrite + else: + writer = io.open(fo.fileno(), mode='w', **kw) + return writer + + sys.stdout = get_text_writer(sys.stdout, encoding=enc) + sys.stderr = get_text_writer(sys.stderr, encoding=enc) + + +def main(): + enc = 'utf-8' + if len(sys.argv) > 1: + enc = sys.argv[1] + set_output_encoding(enc) + + message_tbl = { + 'utf-8': 'ÀÈÌÒÙ こんにちは 你好', + 'latin1': 'ÀÈÌÒÙ', + 'cp932': 'こんにちは', + 'cp936': '你好', + } + + print('Xfoobar.c(10) : %s (%s)' % (message_tbl[enc], enc)) + + +if __name__ == "__main__": + main() diff --git a/src/nvim/testdir/test_makeencoding.vim b/src/nvim/testdir/test_makeencoding.vim new file mode 100644 index 0000000000..a3d5538a47 --- /dev/null +++ b/src/nvim/testdir/test_makeencoding.vim @@ -0,0 +1,106 @@ +" Tests for 'makeencoding'. +if !has('multi_byte') + finish +endif + +source shared.vim + +let s:python = PythonProg() +if s:python == '' + " Can't run this test. + finish +endif + +let s:script = 'test_makeencoding.py' + +let s:message_tbl = { + \ 'utf-8': 'ÀÈÌÒÙ こんにちは 你好', + \ 'latin1': 'ÀÈÌÒÙ', + \ 'cp932': 'こんにちは', + \ 'cp936': '你好', + \} + + +" Tests for :cgetfile and :lgetfile. +func Test_getfile() + set errorfile=Xerror.txt + set errorformat=%f(%l)\ :\ %m + + " :cgetfile + for enc in keys(s:message_tbl) + let &makeencoding = enc + exec "silent !" . s:python . " " . s:script . " " . enc . " > " . &errorfile + cgetfile + copen + call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", + \ getline('.')) + cclose + endfor + + " :lgetfile + for enc in keys(s:message_tbl) + let &makeencoding = enc + exec "silent !" . s:python . " " . s:script . " " . enc . " > " . &errorfile + lgetfile + lopen + call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", + \ getline('.')) + lclose + endfor + + call delete(&errorfile) +endfunc + + +" Tests for :grep and :lgrep. +func Test_grep() + let &grepprg = s:python + set grepformat=%f(%l)\ :\ %m + + " :grep + for enc in keys(s:message_tbl) + let &makeencoding = enc + exec "silent grep! " . s:script . " " . enc + copen + call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", + \ getline('.')) + cclose + endfor + + " :lgrep + for enc in keys(s:message_tbl) + let &makeencoding = enc + exec "silent lgrep! " . s:script . " " . enc + lopen + call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", + \ getline('.')) + lclose + endfor +endfunc + + +" Tests for :make and :lmake. +func Test_make() + let &makeprg = s:python + set errorformat=%f(%l)\ :\ %m + + " :make + for enc in keys(s:message_tbl) + let &makeencoding = enc + exec "silent make! " . s:script . " " . enc + copen + call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", + \ getline('.')) + cclose + endfor + + " :lmake + for enc in keys(s:message_tbl) + let &makeencoding = enc + exec "silent lmake! " . s:script . " " . enc + lopen + call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", + \ getline('.')) + lclose + endfor +endfunc -- cgit From db0685a663a7d86d960f22e18f4e8e5041f80833 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Sun, 17 Dec 2017 11:17:30 -0500 Subject: lint --- src/nvim/quickfix.c | 54 ++++++++++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 844ff071c9..696f123871 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -194,20 +194,19 @@ typedef struct { static char_u *qf_last_bufname = NULL; static bufref_T qf_last_bufref = { NULL, 0, 0 }; -/* - * Read the errorfile "efile" into memory, line by line, building the error - * list. Set the error list's title to qf_title. - * Return -1 for error, number of errors for success. - */ -int -qf_init ( - win_T *wp, - char_u *efile, - char_u *errorformat, - int newlist, /* TRUE: start a new error list */ - char_u *qf_title, - char_u *enc -) +/// Read the errorfile "efile" into memory, line by line, building the error +/// list. Set the error list's title to qf_title. +/// +/// @params wp If non-NULL, make a location list +/// @params efile If non-NULL, errorfile to parse +/// @params errorformat 'errorformat' string used to parse the error lines +/// @params newlist If true, create a new error list +/// @params qf_title If non-NULL, title of the error list +/// @params enc If non-NULL, encoding used to parse errors +/// +/// @returns -1 for error, number of errors for success. +int qf_init(win_T *wp, char_u *efile, char_u *errorformat, int newlist, + char_u *qf_title, char_u *enc) { qf_info_T *qi = &ql_info; @@ -994,9 +993,9 @@ qf_init_ext( buf_T *buf, typval_T *tv, char_u *errorformat, - int newlist, /* TRUE: start a new error list */ - linenr_T lnumfirst, /* first line number to use */ - linenr_T lnumlast, /* last line number to use */ + int newlist, // TRUE: start a new error list + linenr_T lnumfirst, // first line number to use + linenr_T lnumlast, // last line number to use char_u *qf_title, char_u *enc ) @@ -3115,8 +3114,9 @@ void ex_make(exarg_T *eap) && eap->cmdidx != CMD_lmake) ? p_gefm : p_efm, (eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd), *eap->cmdlinep, enc); - if (wp != NULL) + if (wp != NULL) { qi = GET_LOC_LIST(wp); + } if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, TRUE, curbuf); @@ -3459,16 +3459,14 @@ void ex_cfile(exarg_T *eap) set_string_option_direct((char_u *)"ef", -1, eap->arg, OPT_FREE, 0); char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; - /* - * This function is used by the :cfile, :cgetfile and :caddfile - * commands. - * :cfile always creates a new quickfix list and jumps to the - * first error. - * :cgetfile creates a new quickfix list but doesn't jump to the - * first error. - * :caddfile adds to an existing quickfix list. If there is no - * quickfix list then a new list is created. - */ + // This function is used by the :cfile, :cgetfile and :caddfile + // commands. + // :cfile always creates a new quickfix list and jumps to the + // first error. + // :cgetfile creates a new quickfix list but doesn't jump to the + // first error. + // :caddfile adds to an existing quickfix list. If there is no + // quickfix list then a new list is created. if (qf_init(wp, p_ef, p_efm, (eap->cmdidx != CMD_caddfile && eap->cmdidx != CMD_laddfile), *eap->cmdlinep, enc) > 0 -- cgit From 6e0c038a3cf3565b10ce3b49e4394ba464418b92 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 17 Dec 2017 22:47:27 +0100 Subject: ASAN/LeakSanitizer: ignore loop_schedule_deferred() clang ASAN/LeakSanitizer error (observed in #7706): ==21832==ERROR: LeakSanitizer: detected memory leaks Direct leak of 56 byte(s) in 1 object(s) allocated from: 0 0x511b26 in malloc (/home/travis/build/neovim/neovim/build/bin/nvim+0x511b26) 1 0x1009a84 in try_malloc /home/travis/build/neovim/neovim/src/nvim/memory.c:87:15 2 0x1009c44 in xmalloc /home/travis/build/neovim/neovim/src/nvim/memory.c:121:15 3 0xaa8c36 in loop_schedule_deferred /home/travis/build/neovim/neovim/src/nvim/event/loop.c:89:19 4 0x190856a in tui_main /home/travis/build/neovim/neovim/src/nvim/tui/tui.c:367:5 5 0x1963d61 in ui_thread_run /home/travis/build/neovim/neovim/src/nvim/ui_bridge.c:106:3 6 0x2b5d4190d183 in start_thread /build/eglibc-SvCtMH/eglibc-2.19/nptl/pthread_create.c:312 Possible explanation: During exit, `Loop.thread_events` may not get flushed, so `loop_deferred_event()` is never called. We could instead try to unwind `Loop.thread_events` during teardown, but it seems lower-risk to just tell ASAN to ignore it. Valgrind does not complain: $ while :; do { 2>valglog.txt valgrind ./build/bin/nvim -u NONE +q ; } ; if ! [ $? = 0 ] ; then break ; fi ; done --- src/.asan-blacklist | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/.asan-blacklist b/src/.asan-blacklist index 928d81bd5a..9d7f721a88 100644 --- a/src/.asan-blacklist +++ b/src/.asan-blacklist @@ -1,3 +1,7 @@ # multiqueue.h pointer arithmetic is not accepted by asan fun:multiqueue_node_data fun:tv_dict_watcher_node_data + +# Allocation in loop_schedule_deferred() is freed by loop_deferred_event(), but +# this sometimes does not happen during teardown. +func:loop_schedule_deferred -- cgit From ccbf14322a8d56567a0fe9708cead27818d14271 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Sun, 17 Dec 2017 20:51:51 -0500 Subject: vim-patch:8.0.0404: not enough testing for quickfix Problem: Not enough testing for quickfix. Solution: Add some more tests. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/391b1dd040af204b150d43c5a1c97477ee450a28 --- src/nvim/testdir/test_quickfix.vim | 83 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index aff5fc2eed..773502ede4 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -128,6 +128,14 @@ func XlistTests(cchar) let l = split(result, "\n") call assert_equal([' 2 Xtestfile1:1 col 3: Line1', \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l) + + " Test for '+' + redir => result + Xlist! +2 + redir END + let l = split(result, "\n") + call assert_equal([' 2 Xtestfile1:1 col 3: Line1', + \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l) endfunc func Test_clist() @@ -907,6 +915,11 @@ func Test_efm2() \ "(67,3) warning: 's' already defined" \] set efm=%+P[%f],(%l\\,%c)%*[\ ]%t%*[^:]:\ %m,%-Q + " To exercise the push/pop file functionality in quickfix, the test files + " need to be created. + call writefile(['Line1'], 'Xtestfile1') + call writefile(['Line2'], 'Xtestfile2') + call writefile(['Line3'], 'Xtestfile3') cexpr "" for l in lines caddexpr l @@ -917,6 +930,9 @@ func Test_efm2() call assert_equal(2, l[2].col) call assert_equal('w', l[2].type) call assert_equal('e', l[3].type) + call delete('Xtestfile1') + call delete('Xtestfile2') + call delete('Xtestfile3') " Tests for %E, %C and %Z format specifiers let lines = ["Error 275", @@ -1351,11 +1367,25 @@ func Test_switchbuf() call assert_equal(2, winnr('$')) call assert_equal(1, bufwinnr('Xqftestfile3')) + " If only quickfix window is open in the current tabpage, jumping to an + " entry with 'switchubf' set to 'usetab' should search in other tabpages. enew | only + set switchbuf=usetab + tabedit Xqftestfile1 + tabedit Xqftestfile2 + tabedit Xqftestfile3 + tabfirst + copen | only + clast + call assert_equal(4, tabpagenr()) + tabfirst | tabonly | enew | only call delete('Xqftestfile1') call delete('Xqftestfile2') call delete('Xqftestfile3') + set switchbuf&vim + + enew | only endfunc func Xadjust_qflnum(cchar) @@ -1673,3 +1703,56 @@ func Test_dirstack_cleanup() caddbuffer let &efm = save_efm endfunc + +" Tests for jumping to entries from the location list window and quickfix +" window +func Test_cwindow_jump() + set efm=%f%%%l%%%m + lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"] + lopen | only + lfirst + call assert_true(winnr('$') == 2) + call assert_true(winnr() == 1) + " Location list for the new window should be set + call assert_true(getloclist(0)[2].text == 'Line 30') + + " Open a scratch buffer + " Open a new window and create a location list + " Open the location list window and close the other window + " Jump to an entry. + " Should create a new window and jump to the entry. The scrtach buffer + " should not be used. + enew | only + set buftype=nofile + below new + lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"] + lopen + 2wincmd c + lnext + call assert_true(winnr('$') == 3) + call assert_true(winnr() == 2) + + " Open two windows with two different location lists + " Open the location list window and close the previous window + " Jump to an entry in the location list window + " Should open the file in the first window and not set the location list. + enew | only + lgetexpr ["F1%5%Line 5"] + below new + lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"] + lopen + 2wincmd c + lnext + call assert_true(winnr() == 1) + call assert_true(getloclist(0)[0].text == 'Line 5') + + enew | only + cgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"] + copen + cnext + call assert_true(winnr('$') == 2) + call assert_true(winnr() == 1) + + enew | only + set efm&vim +endfunc -- cgit From 853634881335eb9fe4c593710fbb8c71324e1690 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Sun, 17 Dec 2017 20:56:01 -0500 Subject: vim-patch:8.0.0484: :lhelpgrep does not fail after a successful one Problem: Using :lhelpgrep with an argument that should fail does not produce an error if the previous :helpgrep worked. Solution: Use another way to detect that autocommands made the quickfix info invalid. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/ee85df37634dfb0c40ae5de0b4f246aef460b392 --- src/nvim/quickfix.c | 10 +- src/nvim/testdir/test_quickfix.vim | 184 +++++++++++++++++++++++++++++-------- 2 files changed, 151 insertions(+), 43 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 696f123871..6d59d15515 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4502,6 +4502,9 @@ void ex_helpgrep(exarg_T *eap) } } + // Autocommands may change the list. Save it for later comparison + qf_info_T *save_qi = qi; + regmatch.regprog = vim_regcomp(eap->arg, RE_MAGIC + RE_STRING); regmatch.rm_ic = FALSE; if (regmatch.regprog != NULL) { @@ -4614,10 +4617,11 @@ void ex_helpgrep(exarg_T *eap) if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, - curbuf->b_fname, TRUE, curbuf); - if (!new_qi && qi != &ql_info && qf_find_buf(qi) == NULL) - /* autocommands made "qi" invalid */ + curbuf->b_fname, true, curbuf); + if (!new_qi && qi != save_qi && qf_find_buf(qi) == NULL) { + // autocommands made "qi" invalid return; + } } /* Jump to first match. */ diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 773502ede4..3d8d18cf88 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -31,7 +31,8 @@ func s:setup_commands(cchar) command! -nargs=* -bang Xnfile cnfile command! -nargs=* -bang Xpfile cpfile command! -nargs=* Xexpr cexpr - command! -nargs=* Xvimgrep vimgrep + command! -range -nargs=* Xvimgrep vimgrep + command! -nargs=* Xvimgrepadd vimgrepadd command! -nargs=* Xgrep grep command! -nargs=* Xgrepadd grepadd command! -nargs=* Xhelpgrep helpgrep @@ -61,7 +62,8 @@ func s:setup_commands(cchar) command! -nargs=* -bang Xnfile lnfile command! -nargs=* -bang Xpfile lpfile command! -nargs=* Xexpr lexpr - command! -nargs=* Xvimgrep lvimgrep + command! -range -nargs=* Xvimgrep lvimgrep + command! -nargs=* Xvimgrepadd lvimgrepadd command! -nargs=* Xgrep lgrep command! -nargs=* Xgrepadd lgrepadd command! -nargs=* Xhelpgrep lhelpgrep @@ -85,57 +87,52 @@ func XlistTests(cchar) \ 'non-error 3', 'Xtestfile3:3:1:Line3'] " List only valid entries - redir => result - Xlist - redir END - let l = split(result, "\n") + let l = split(execute('Xlist', ''), "\n") call assert_equal([' 2 Xtestfile1:1 col 3: Line1', \ ' 4 Xtestfile2:2 col 2: Line2', \ ' 6 Xtestfile3:3 col 1: Line3'], l) " List all the entries - redir => result - Xlist! - redir END - let l = split(result, "\n") + let l = split(execute('Xlist!', ''), "\n") call assert_equal([' 1: non-error 1', ' 2 Xtestfile1:1 col 3: Line1', \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2', \ ' 5: non-error 3', ' 6 Xtestfile3:3 col 1: Line3'], l) " List a range of errors - redir => result - Xlist 3,6 - redir END - let l = split(result, "\n") + let l = split(execute('Xlist 3,6', ''), "\n") call assert_equal([' 4 Xtestfile2:2 col 2: Line2', \ ' 6 Xtestfile3:3 col 1: Line3'], l) - redir => result - Xlist! 3,4 - redir END - let l = split(result, "\n") + let l = split(execute('Xlist! 3,4', ''), "\n") call assert_equal([' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l) - redir => result - Xlist -6,-4 - redir END - let l = split(result, "\n") + let l = split(execute('Xlist -6,-4', ''), "\n") call assert_equal([' 2 Xtestfile1:1 col 3: Line1'], l) - redir => result - Xlist! -5,-3 - redir END - let l = split(result, "\n") + let l = split(execute('Xlist! -5,-3', ''), "\n") call assert_equal([' 2 Xtestfile1:1 col 3: Line1', \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l) " Test for '+' - redir => result - Xlist! +2 - redir END - let l = split(result, "\n") + let l = split(execute('Xlist! +2', ''), "\n") call assert_equal([' 2 Xtestfile1:1 col 3: Line1', \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l) + + " Different types of errors + call g:Xsetlist([{'lnum':10,'col':5,'type':'W', 'text':'Warning','nr':11}, + \ {'lnum':20,'col':10,'type':'e','text':'Error','nr':22}, + \ {'lnum':30,'col':15,'type':'i','text':'Info','nr':33}, + \ {'lnum':40,'col':20,'type':'x', 'text':'Other','nr':44}, + \ {'lnum':50,'col':25,'type':"\",'text':'one','nr':55}]) + let l = split(execute('Xlist', ""), "\n") + call assert_equal([' 1:10 col 5 warning 11: Warning', + \ ' 2:20 col 10 error 22: Error', + \ ' 3:30 col 15 info 33: Info', + \ ' 4:40 col 20 x 44: Other', + \ ' 5:50 col 25 55: one'], l) + + " Error cases + call assert_fails('Xlist abc', 'E488:') endfunc func Test_clist() @@ -324,6 +321,23 @@ func XbufferTests(cchar) \ l[3].lnum == 750 && l[3].col == 25 && l[3].text ==# 'Line 750') enew! + " Check for invalid buffer + call assert_fails('Xbuffer 199', 'E474:') + + " Check for unloaded buffer + edit Xtestfile1 + let bnr = bufnr('%') + enew! + call assert_fails('Xbuffer ' . bnr, 'E681:') + + " Check for invalid range + " Using Xbuffer will not run the range check in the cbuffer/lbuffer + " commands. So directly call the commands. + if (a:cchar == 'c') + call assert_fails('900,999cbuffer', 'E16:') + else + call assert_fails('900,999lbuffer', 'E16:') + endif endfunc func Test_cbuffer() @@ -372,6 +386,9 @@ func Xtest_browse(cchar) call assert_equal('Xqftestfile1', bufname('%')) call assert_equal(5, line('.')) + Xexpr "" + call assert_fails('Xnext', 'E42:') + call delete('Xqftestfile1') call delete('Xqftestfile2') endfunc @@ -393,6 +410,9 @@ func s:test_xhelpgrep(cchar) call assert_true(w:quickfix_title =~ title_text, w:quickfix_title) " This wipes out the buffer, make sure that doesn't cause trouble. Xclose + + " Search for non existing help string + call assert_fails('Xhelpgrep a1b2c3', 'E480:') endfunc func Test_helpgrep() @@ -586,7 +606,7 @@ func Test_locationlist() wincmd n | only augroup! testgroup - endfunc +endfunc func Test_locationlist_curwin_was_closed() augroup testgroup @@ -605,7 +625,7 @@ func Test_locationlist_curwin_was_closed() call assert_fails('lrewind', 'E924:') augroup! testgroup - endfunc +endfunc func Test_locationlist_cross_tab_jump() call writefile(['loclistfoo'], 'loclistfoo') @@ -742,7 +762,7 @@ func Test_efm1() call delete('Xerrorfile1') call delete('Xerrorfile2') call delete('Xtestfile') - endfunc +endfunc " Test for quickfix directory stack support func s:dir_stack_tests(cchar) @@ -901,20 +921,26 @@ func Test_efm2() call assert_equal(l[0].pattern, '^\VLine search text\$') call assert_equal(l[0].lnum, 0) + let l = split(execute('clist', ''), "\n") + call assert_equal([' 1 Xtestfile:^\VLine search text\$: '], l) + " Test for %P, %Q and %t format specifiers let lines=["[Xtestfile1]", \ "(1,17) error: ';' missing", \ "(21,2) warning: variable 'z' not defined", \ "(67,3) error: end of file found before string ended", + \ "--", \ "", \ "[Xtestfile2]", + \ "--", \ "", \ "[Xtestfile3]", \ "NEW compiler v1.1", \ "(2,2) warning: variable 'x' not defined", - \ "(67,3) warning: 's' already defined" + \ "(67,3) warning: 's' already defined", + \ "--" \] - set efm=%+P[%f],(%l\\,%c)%*[\ ]%t%*[^:]:\ %m,%-Q + set efm=%+P[%f]%r,(%l\\,%c)%*[\ ]%t%*[^:]:\ %m,%+Q--%r " To exercise the push/pop file functionality in quickfix, the test files " need to be created. call writefile(['Line1'], 'Xtestfile1') @@ -925,7 +951,7 @@ func Test_efm2() caddexpr l endfor let l = getqflist() - call assert_equal(9, len(l)) + call assert_equal(12, len(l)) call assert_equal(21, l[2].lnum) call assert_equal(2, l[2].col) call assert_equal('w', l[2].type) @@ -1080,6 +1106,13 @@ func SetXlistTests(cchar, bnum) call g:Xsetlist([]) let l = g:Xgetlist() call assert_equal(0, len(l)) + + " Error cases: + " Refer to a non-existing buffer and pass a non-dictionary type + call assert_fails("call g:Xsetlist([{'bufnr':998, 'lnum':4}," . + \ " {'bufnr':999, 'lnum':5}])", 'E92:') + call g:Xsetlist([[1, 2,3]]) + call assert_equal(0, len(g:Xgetlist())) endfunc func Test_setqflist() @@ -1098,7 +1131,8 @@ func Xlist_empty_middle(cchar) call s:setup_commands(a:cchar) " create three quickfix lists - Xvimgrep Test_ test_quickfix.vim + let @/ = 'Test_' + Xvimgrep // test_quickfix.vim let testlen = len(g:Xgetlist()) call assert_true(testlen > 0) Xvimgrep empty test_quickfix.vim @@ -1591,6 +1625,22 @@ func Xproperty_tests(cchar) call g:Xsetlist([], ' ', {'title' : 'N3'}) call assert_equal('N2', g:Xgetlist({'nr':2, 'title':1}).title) + " Changing the title of an earlier quickfix list + call g:Xsetlist([], ' ', {'title' : 'NewTitle', 'nr' : 2}) + call assert_equal('NewTitle', g:Xgetlist({'nr':2, 'title':1}).title) + + " Changing the title of an invalid quickfix list + call assert_equal(-1, g:Xsetlist([], ' ', + \ {'title' : 'SomeTitle', 'nr' : 99})) + call assert_equal(-1, g:Xsetlist([], ' ', + \ {'title' : 'SomeTitle', 'nr' : 'abc'})) + + if a:cchar == 'c' + copen + call assert_equal({'winid':win_getid()}, getqflist({'winid':1})) + cclose + endif + " Invalid arguments call assert_fails('call g:Xgetlist([])', 'E715') call assert_fails('call g:Xsetlist([], "a", [])', 'E715') @@ -1598,16 +1648,18 @@ func Xproperty_tests(cchar) call assert_equal(-1, s) call assert_equal({}, g:Xgetlist({'abc':1})) + call assert_equal({}, g:Xgetlist({'nr':99, 'title':1})) + call assert_equal({}, g:Xgetlist({'nr':[], 'title':1})) if a:cchar == 'l' - call assert_equal({}, getloclist(99, {'title': 1})) + call assert_equal({}, getloclist(99, {'title': 1})) endif - endfunc +endfunc func Test_qf_property() call Xproperty_tests('c') call Xproperty_tests('l') - endfunc +endfunc " Tests for the QuickFixCmdPre/QuickFixCmdPost autocommands func QfAutoCmdHandler(loc, cmd) @@ -1756,3 +1808,55 @@ func Test_cwindow_jump() enew | only set efm&vim endfunc + +func XvimgrepTests(cchar) + call s:setup_commands(a:cchar) + + call writefile(['Editor:VIM vim', + \ 'Editor:Emacs EmAcS', + \ 'Editor:Notepad NOTEPAD'], 'Xtestfile1') + call writefile(['Linux', 'MacOS', 'MS-Windows'], 'Xtestfile2') + + " Error cases + call assert_fails('Xvimgrep /abc *', 'E682:') + + let @/='' + call assert_fails('Xvimgrep // *', 'E35:') + + call assert_fails('Xvimgrep abc', 'E683:') + call assert_fails('Xvimgrep a1b2c3 Xtestfile1', 'E480:') + call assert_fails('Xvimgrep pat Xa1b2c3', 'E480:') + + Xexpr "" + Xvimgrepadd Notepad Xtestfile1 + Xvimgrepadd MacOS Xtestfile2 + let l = g:Xgetlist() + call assert_equal(2, len(l)) + call assert_equal('Editor:Notepad NOTEPAD', l[0].text) + + Xvimgrep #\cvim#g Xtestfile? + let l = g:Xgetlist() + call assert_equal(2, len(l)) + call assert_equal(8, l[0].col) + call assert_equal(12, l[1].col) + + 1Xvimgrep ?Editor? Xtestfile* + let l = g:Xgetlist() + call assert_equal(1, len(l)) + call assert_equal('Editor:VIM vim', l[0].text) + + edit +3 Xtestfile2 + Xvimgrep +\cemacs+j Xtestfile1 + let l = g:Xgetlist() + call assert_equal('Xtestfile2', bufname('')) + call assert_equal('Editor:Emacs EmAcS', l[0].text) + + call delete('Xtestfile1') + call delete('Xtestfile2') +endfunc + +" Tests for the :vimgrep command +func Test_vimgrep() + call XvimgrepTests('c') + call XvimgrepTests('l') +endfunc -- cgit From fb8592b7ba673f230f144dc8c29e5dffc33fc50a Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 18 Dec 2017 14:26:49 -0500 Subject: vim-patch:8.0.0517: there is no way to remove quickfix lists Problem: There is no way to remove quickfix lists (for testing). Solution: Add the 'f' action to setqflist(). Add tests. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/b6fa30ccc39cdb7f1d07b99fe2f4c6b61671dac2 --- src/nvim/eval.c | 3 ++- src/nvim/quickfix.c | 15 ++++++++++++- src/nvim/testdir/test_quickfix.vim | 46 +++++++++++++++++++++++++++++++++----- 3 files changed, 56 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 7fa9f7563e..1461ddb8f9 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -14594,7 +14594,8 @@ static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv) return; } const char *const act = tv_get_string_chk(action_arg); - if ((*act == 'a' || *act == 'r' || *act == ' ') && act[1] == NUL) { + if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f') + && act[1] == NUL) { action = *act; } else { EMSG2(_(e_invact), act); diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 6d59d15515..c7326c46fe 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4267,6 +4267,16 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action) return retval; } +static void qf_free_stack(win_T *wp, qf_info_T *qi) +{ + qf_free_all(wp); + if (wp == NULL) { + // quickfix list + qi->qf_curlist = 0; + qi->qf_listcount = 0; + } +} + // Populate the quickfix list with the items supplied in the list // of dictionaries. "title" will be copied to w:quickfix_title // "action" is 'a' for add, 'r' for replace. Otherwise create a new list. @@ -4280,7 +4290,10 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title, qi = ll_get_or_alloc_list(wp); } - if (what != NULL) { + if (action == 'f') { + // Free the entire quickfix or location list stack + qf_free_stack(wp, qi); + } else if (what != NULL) { retval = qf_set_properties(qi, what, action); } else { retval = qf_add_entries(qi, list, title, action); diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 3d8d18cf88..b953df3fc8 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -38,6 +38,7 @@ func s:setup_commands(cchar) command! -nargs=* Xhelpgrep helpgrep let g:Xgetlist = function('getqflist') let g:Xsetlist = function('setqflist') + call setqflist([], 'f') else command! -nargs=* -bang Xlist llist command! -nargs=* Xgetexpr lgetexpr @@ -69,6 +70,7 @@ func s:setup_commands(cchar) command! -nargs=* Xhelpgrep lhelpgrep let g:Xgetlist = function('getloclist', [0]) let g:Xsetlist = function('setloclist', [0]) + call setloclist(0, [], 'f') endif endfunc @@ -76,6 +78,9 @@ endfunc func XlistTests(cchar) call s:setup_commands(a:cchar) + if a:cchar == 'l' + call assert_fails('llist', 'E776:') + endif " With an empty list, command should return error Xgetexpr [] silent! Xlist @@ -146,6 +151,9 @@ endfunc func XageTests(cchar) call s:setup_commands(a:cchar) + let list = [{'bufnr': 1, 'lnum': 1}] + call g:Xsetlist(list) + " Jumping to a non existent list should return error silent! Xolder 99 call assert_true(v:errmsg ==# 'E380: At bottom of quickfix stack') @@ -179,11 +187,7 @@ func XageTests(cchar) endfunc func Test_cage() - let list = [{'bufnr': 1, 'lnum': 1}] - call setqflist(list) call XageTests('c') - - call setloclist(0, list) call XageTests('l') endfunc @@ -192,6 +196,11 @@ endfunc func XwindowTests(cchar) call s:setup_commands(a:cchar) + " Opening the location list window without any errors should fail + if a:cchar == 'l' + call assert_fails('lopen', 'E776:') + endif + " Create a list with no valid entries Xgetexpr ['non-error 1', 'non-error 2', 'non-error 3'] @@ -232,6 +241,19 @@ func XwindowTests(cchar) " Calling cwindow should close the quickfix window with no valid errors Xwindow call assert_true(winnr('$') == 1) + + if a:cchar == 'c' + " Opening the quickfix window in multiple tab pages should reuse the + " quickfix buffer + Xgetexpr ['Xtestfile1:1:3:Line1', 'Xtestfile2:2:2:Line2', + \ 'Xtestfile3:3:1:Line3'] + Xopen + let qfbufnum = bufnr('%') + tabnew + Xopen + call assert_equal(qfbufnum, bufnr('%')) + new | only | tabonly + endif endfunc func Test_cwindow() @@ -360,6 +382,13 @@ endfunc func Xtest_browse(cchar) call s:setup_commands(a:cchar) + " Jumping to first or next location list entry without any error should + " result in failure + if a:cchar == 'l' + call assert_fails('lfirst', 'E776:') + call assert_fails('lnext', 'E776:') + endif + call s:create_test_file('Xqftestfile1') call s:create_test_file('Xqftestfile2') @@ -1532,6 +1561,11 @@ endfunc func XbottomTests(cchar) call s:setup_commands(a:cchar) + " Calling lbottom without any errors should fail + if a:cchar == 'l' + call assert_fails('lbottom', 'E776:') + endif + call g:Xsetlist([{'filename': 'foo', 'lnum': 42}]) Xopen let wid = win_getid() @@ -1553,10 +1587,9 @@ endfunc func HistoryTest(cchar) call s:setup_commands(a:cchar) - call assert_fails(a:cchar . 'older 99', 'E380:') " clear all lists after the first one, then replace the first one. call g:Xsetlist([]) - Xolder + call assert_fails('Xolder 99', 'E380:') let entry = {'filename': 'foo', 'lnum': 42} call g:Xsetlist([entry], 'r') call g:Xsetlist([entry, entry]) @@ -1599,6 +1632,7 @@ func Xproperty_tests(cchar) call assert_fails('call g:Xsetlist([], "a", [])', 'E715:') " Set and get the title + call g:Xsetlist([]) Xopen wincmd p call g:Xsetlist([{'filename':'foo', 'lnum':27}]) -- cgit From f0bd2bc39aeb7797e3dd5c14797606dac45c8a75 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 18 Dec 2017 14:36:13 -0500 Subject: vim-patch:8.0.0536: quickfix window not updated when freeing quickfix stack Problem: Quickfix window not updated when freeing quickfix stack. Solution: Update the quickfix window. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/69f40be64555d50f603c6f22722cf762aaa6bbc1 --- src/nvim/quickfix.c | 44 ++++++++++++++++++++++++++ src/nvim/testdir/test_quickfix.vim | 63 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index c7326c46fe..9a4341f3a4 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4267,13 +4267,57 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action) return retval; } +// Find the non-location list window with the specified location list. +static win_T * find_win_with_ll(qf_info_T *qi) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if ((wp->w_llist == qi) && !bt_quickfix(wp->w_buffer)) { + return wp; + } + } + + return NULL; +} + +// Free the entire quickfix/location list stack. +// If the quickfix/location list window is open, then clear it. static void qf_free_stack(win_T *wp, qf_info_T *qi) { + win_T *qfwin = qf_find_win(qi); + + if (qfwin != NULL) { + // If the quickfix/location list window is open, then clear it + if (qi->qf_curlist < qi->qf_listcount) { + qf_free(qi, qi->qf_curlist); + } + qf_update_buffer(qi, NULL); + } + + win_T *llwin = NULL; + win_T *orig_wp = wp; + if (wp != NULL && IS_LL_WINDOW(wp)) { + // If in the location list window, then use the non-location list + // window with this location list (if present) + llwin = find_win_with_ll(qi); + if (llwin != NULL) { + wp = llwin; + } + } + qf_free_all(wp); if (wp == NULL) { // quickfix list qi->qf_curlist = 0; qi->qf_listcount = 0; + } else if (IS_LL_WINDOW(orig_wp)) { + // If the location list window is open, then create a new empty location + // list + qf_info_T *new_ll = ll_new_list(); + orig_wp->w_llist_ref = new_ll; + if (llwin != NULL) { + llwin->w_llist = new_ll; + new_ll->qf_refcount++; + } } } diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index b953df3fc8..40ad387dee 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1894,3 +1894,66 @@ func Test_vimgrep() call XvimgrepTests('c') call XvimgrepTests('l') endfunc + +func XfreeTests(cchar) + call s:setup_commands(a:cchar) + + enew | only + + " Deleting the quickfix stack should work even When the current list is + " somewhere in the middle of the stack + Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15'] + Xexpr ['Xfile2:20:20:Line 20', 'Xfile2:25:25:Line 25'] + Xexpr ['Xfile3:30:30:Line 30', 'Xfile3:35:35:Line 35'] + Xolder + call g:Xsetlist([], 'f') + call assert_equal(0, len(g:Xgetlist())) + + " After deleting the stack, adding a new list should create a stack with a + " single list. + Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15'] + call assert_equal(1, g:Xgetlist({'all':1}).nr) + + " Deleting the stack from a quickfix window should update/clear the + " quickfix/location list window. + Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15'] + Xexpr ['Xfile2:20:20:Line 20', 'Xfile2:25:25:Line 25'] + Xexpr ['Xfile3:30:30:Line 30', 'Xfile3:35:35:Line 35'] + Xolder + Xwindow + call g:Xsetlist([], 'f') + call assert_equal(2, winnr('$')) + call assert_equal(1, line('$')) + Xclose + + " Deleting the stack from a non-quickfix window should update/clear the + " quickfix/location list window. + Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15'] + Xexpr ['Xfile2:20:20:Line 20', 'Xfile2:25:25:Line 25'] + Xexpr ['Xfile3:30:30:Line 30', 'Xfile3:35:35:Line 35'] + Xolder + Xwindow + wincmd p + call g:Xsetlist([], 'f') + call assert_equal(0, len(g:Xgetlist())) + wincmd p + call assert_equal(2, winnr('$')) + call assert_equal(1, line('$')) + + " After deleting the location list stack, if the location list window is + " opened, then a new location list should be created. So opening the + " location list window again should not create a new window. + if a:cchar == 'l' + lexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15'] + wincmd p + lopen + call assert_equal(2, winnr('$')) + endif + Xclose +endfunc + +" Tests for the quickifx free functionality +func Test_qf_free() + call XfreeTests('c') + call XfreeTests('l') +endfunc -- cgit From 765ff94b5be90bc33eb8eba3fdfe3e910124450d Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 18 Dec 2017 14:56:49 -0500 Subject: vim-patch:8.0.0584: memory leak when executing quickfix tests Problem: Memory leak when executing quickfix tests. Solution: Free the list reference. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/d788f6fe89c77262c474de323f5dab6d1c814e27 --- src/nvim/quickfix.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 9a4341f3a4..73ad14ab22 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4313,6 +4313,10 @@ static void qf_free_stack(win_T *wp, qf_info_T *qi) // If the location list window is open, then create a new empty location // list qf_info_T *new_ll = ll_new_list(); + + // first free the list reference in the location list window + ll_free_all(&orig_wp->w_llist_ref); + orig_wp->w_llist_ref = new_ll; if (llwin != NULL) { llwin->w_llist = new_ll; -- cgit From 1b2d386a852fcdf1f7509dc29f2a26ab8e5b59ca Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 18 Dec 2017 21:35:40 -0500 Subject: vim-patch:8.0.0565: using freed memory in :caddbuf Problem: Using freed memory in :caddbuf after clearing quickfix list. (Dominique Pelle) Solution: Set qf_last to NULL. https://github.com/vim/vim/commit/31bdd13c335533c749993b57dcd980a87373139e --- src/nvim/quickfix.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 73ad14ab22..be998077c0 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2411,6 +2411,7 @@ static void qf_free(qf_info_T *qi, int idx) qi->qf_lists[idx].qf_ptr = NULL; qi->qf_lists[idx].qf_title = NULL; qi->qf_lists[idx].qf_index = 0; + qi->qf_lists[idx].qf_last = NULL; qf_clean_dir_stack(&qi->qf_dir_stack); qi->qf_directory = NULL; -- cgit From dd2739286195c8953a31346908f02a85563a3fa2 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 18 Dec 2017 21:37:11 -0500 Subject: vim-patch:8.0.0574: get only one quickfix list after :caddbuf Problem: Get only one quickfix list after :caddbuf. Solution: Reset qf_multiline. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/99895eac1cf71be43ece7e14b50e206e041fbe9f --- src/nvim/quickfix.c | 6 +++ src/nvim/testdir/test_quickfix.vim | 89 +++++++++++++++++++++++++++++++------- 2 files changed, 80 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index be998077c0..ee3bb1dfba 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2411,12 +2411,18 @@ static void qf_free(qf_info_T *qi, int idx) qi->qf_lists[idx].qf_ptr = NULL; qi->qf_lists[idx].qf_title = NULL; qi->qf_lists[idx].qf_index = 0; + qi->qf_lists[idx].qf_start = NULL; qi->qf_lists[idx].qf_last = NULL; + qi->qf_lists[idx].qf_ptr = NULL; + qi->qf_lists[idx].qf_nonevalid = true; qf_clean_dir_stack(&qi->qf_dir_stack); qi->qf_directory = NULL; qf_clean_dir_stack(&qi->qf_file_stack); qi->qf_currfile = NULL; + qi->qf_multiline = false; + qi->qf_multiignore = false; + qi->qf_multiscan = false; } /* diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 40ad387dee..e729290c1e 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -24,8 +24,8 @@ func s:setup_commands(cchar) command! -nargs=* Xgetbuffer cgetbuffer command! -nargs=* Xaddbuffer caddbuffer command! -nargs=* Xrewind crewind - command! -nargs=* -bang Xnext cnext - command! -nargs=* -bang Xprev cprev + command! -count -nargs=* -bang Xnext cnext + command! -count -nargs=* -bang Xprev cprev command! -nargs=* -bang Xfirst cfirst command! -nargs=* -bang Xlast clast command! -nargs=* -bang Xnfile cnfile @@ -56,8 +56,8 @@ func s:setup_commands(cchar) command! -nargs=* Xgetbuffer lgetbuffer command! -nargs=* Xaddbuffer laddbuffer command! -nargs=* Xrewind lrewind - command! -nargs=* -bang Xnext lnext - command! -nargs=* -bang Xprev lprev + command! -count -nargs=* -bang Xnext lnext + command! -count -nargs=* -bang Xprev lprev command! -nargs=* -bang Xfirst lfirst command! -nargs=* -bang Xlast llast command! -nargs=* -bang Xnfile lnfile @@ -395,7 +395,9 @@ func Xtest_browse(cchar) Xgetexpr ['Xqftestfile1:5:Line5', \ 'Xqftestfile1:6:Line6', \ 'Xqftestfile2:10:Line10', - \ 'Xqftestfile2:11:Line11'] + \ 'Xqftestfile2:11:Line11', + \ 'RegularLine1', + \ 'RegularLine2'] Xfirst call assert_fails('Xprev', 'E553') @@ -407,6 +409,7 @@ func Xtest_browse(cchar) call assert_equal('Xqftestfile1', bufname('%')) call assert_equal(6, line('.')) Xlast + Xprev call assert_equal('Xqftestfile2', bufname('%')) call assert_equal(11, line('.')) call assert_fails('Xnext', 'E553') @@ -415,6 +418,13 @@ func Xtest_browse(cchar) call assert_equal('Xqftestfile1', bufname('%')) call assert_equal(5, line('.')) + 10Xnext + call assert_equal('Xqftestfile2', bufname('%')) + call assert_equal(11, line('.')) + 10Xprev + call assert_equal('Xqftestfile1', bufname('%')) + call assert_equal(5, line('.')) + Xexpr "" call assert_fails('Xnext', 'E42:') @@ -437,9 +447,30 @@ func s:test_xhelpgrep(cchar) let title_text = ':lhelpgrep quickfix' endif call assert_true(w:quickfix_title =~ title_text, w:quickfix_title) + + " Jumping to a help topic should open the help window + only + Xnext + call assert_true(&buftype == 'help') + call assert_true(winnr('$') == 2) + " Jumping to the next match should reuse the help window + Xnext + call assert_true(&buftype == 'help') + call assert_true(winnr() == 1) + call assert_true(winnr('$') == 2) + " Jumping to the next match from the quickfix window should reuse the help + " window + Xopen + Xnext + call assert_true(&buftype == 'help') + call assert_true(winnr() == 1) + call assert_true(winnr('$') == 2) + " This wipes out the buffer, make sure that doesn't cause trouble. Xclose + new | only + " Search for non existing help string call assert_fails('Xhelpgrep a1b2c3', 'E480:') endfunc @@ -578,10 +609,7 @@ func Test_locationlist() lrewind enew lopen - lnext - lnext - lnext - lnext + 4lnext vert split wincmd L lopen @@ -1039,6 +1067,25 @@ func Test_efm2() call assert_equal(1, l[4].valid) call assert_equal('unittests/dbfacadeTest.py', bufname(l[4].bufnr)) + " The following sequence of commands used to crash Vim + set efm=%W%m + cgetexpr ['msg1'] + let l = getqflist() + call assert_equal(1, len(l), string(l)) + call assert_equal('msg1', l[0].text) + set efm=%C%m + lexpr 'msg2' + let l = getloclist(0) + call assert_equal(1, len(l), string(l)) + call assert_equal('msg2', l[0].text) + lopen + call setqflist([], 'r') + caddbuf + let l = getqflist() + call assert_equal(1, len(l), string(l)) + call assert_equal('|| msg2', l[0].text) + + new | only let &efm = save_efm endfunc @@ -1369,18 +1416,18 @@ func Test_switchbuf() let winid = win_getid() cfirst | cnext call assert_equal(winid, win_getid()) - cnext | cnext + 2cnext call assert_equal(winid, win_getid()) - cnext | cnext + 2cnext call assert_equal(winid, win_getid()) enew set switchbuf=useopen cfirst | cnext call assert_equal(file1_winid, win_getid()) - cnext | cnext + 2cnext call assert_equal(file2_winid, win_getid()) - cnext | cnext + 2cnext call assert_equal(file2_winid, win_getid()) enew | only @@ -1390,9 +1437,9 @@ func Test_switchbuf() tabfirst cfirst | cnext call assert_equal(2, tabpagenr()) - cnext | cnext + 2cnext call assert_equal(3, tabpagenr()) - cnext | cnext + 2cnext call assert_equal(3, tabpagenr()) tabfirst | tabonly | enew @@ -1957,3 +2004,15 @@ func Test_qf_free() call XfreeTests('c') call XfreeTests('l') endfunc + +func Test_no_reuse_mem() + set efm=E,%W%m, + cgetexpr ['C'] + set efm=%C%m + lexpr '0' + lopen + call setqflist([], 'r') + caddbuf + + set efm& +endfunc -- cgit From 9fb7926a0dbc08daf19fca5eb93f95ae83a6303c Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 18 Dec 2017 21:40:08 -0500 Subject: vim-patch:8.0.0579: duplicate test case for quickfix Problem: Duplicate test case for quickfix. Solution: Remove the function. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/9b77016545d5ef1a1f4a90c9bb4b7a6693af8918 --- src/nvim/testdir/test_quickfix.vim | 12 ------------ 1 file changed, 12 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index e729290c1e..7e4100cca0 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -2004,15 +2004,3 @@ func Test_qf_free() call XfreeTests('c') call XfreeTests('l') endfunc - -func Test_no_reuse_mem() - set efm=E,%W%m, - cgetexpr ['C'] - set efm=%C%m - lexpr '0' - lopen - call setqflist([], 'r') - caddbuf - - set efm& -endfunc -- cgit From 4d2d844c12e1a94a5d3cbe93794bb04ef6fb8377 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 18 Dec 2017 21:40:34 -0500 Subject: vim-patch:8.0.0580: cannot set the valid flag with setqflist() Problem: Cannot set the valid flag with setqflist(). Solution: Add the "valid" argument. (Yegappan Lakshmanan, closes vim/vim#1642) https://github.com/vim/vim/commit/f1d21c8cc83f40c815b6bf13cd2043152db533ee --- src/nvim/quickfix.c | 5 +++++ src/nvim/testdir/test_quickfix.vim | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index ee3bb1dfba..d77cec4fd8 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4189,6 +4189,11 @@ static int qf_add_entries(qf_info_T *qi, list_T *list, char_u *title, bufnum = 0; } + // If the 'valid' field is present it overrules the detected value. + if (tv_dict_find(d, "valid", -1) != NULL) { + valid = (int)tv_dict_get_number(d, "valid"); + } + int status = qf_add_entry(qi, NULL, // dir (char_u *)filename, diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 7e4100cca0..dd64e37ce3 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1183,6 +1183,25 @@ func SetXlistTests(cchar, bnum) let l = g:Xgetlist() call assert_equal(0, len(l)) + " Tests for setting the 'valid' flag + call g:Xsetlist([{'bufnr':a:bnum, 'lnum':4, 'valid':0}]) + Xwindow + call assert_equal(1, winnr('$')) + let l = g:Xgetlist() + call g:Xsetlist(l) + call assert_equal(0, g:Xgetlist()[0].valid) + call g:Xsetlist([{'text':'Text1', 'valid':1}]) + Xwindow + call assert_equal(2, winnr('$')) + Xclose + let save_efm = &efm + set efm=%m + Xgetexpr 'TestMessage' + let l = g:Xgetlist() + call g:Xsetlist(l) + call assert_equal(1, g:Xgetlist()[0].valid) + let &efm = save_efm + " Error cases: " Refer to a non-existing buffer and pass a non-dictionary type call assert_fails("call g:Xsetlist([{'bufnr':998, 'lnum':4}," . -- cgit From 20708a07bfc1de2c56657123c41bb52919a728d8 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Tue, 19 Dec 2017 08:39:09 -0500 Subject: vim-patch:8.0.0590: cannot add a context to locations Problem: Cannot add a context to locations. Solution: Add the "context" entry in location entries. (Yegappan Lakshmanan, closes vim/vim#1012) https://github.com/vim/vim/commit/8f77c5a4ec756f3f866bd6b18feb6fca6f2a2e91 --- src/nvim/eval.c | 2 ++ src/nvim/quickfix.c | 71 ++++++++++++++++++++++++++++++++++++++ src/nvim/testdir/test_quickfix.vim | 32 +++++++++++++++++ 3 files changed, 105 insertions(+) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 1461ddb8f9..2355c8c813 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5173,6 +5173,8 @@ bool garbage_collect(bool testing) ABORTING(set_ref_list)(sub.additional_elements, copyID); } + ABORTING(set_ref_in_quickfix)(copyID); + bool did_free = false; if (!abort) { // 2. Free lists and dictionaries that are not referenced. diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index d77cec4fd8..e7d11df833 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -85,6 +85,7 @@ typedef struct qf_list_S { int qf_nonevalid; // TRUE if not a single valid entry found char_u *qf_title; // title derived from the command that created // the error list + typval_T *qf_ctx; // context set by setqflist/setloclist } qf_list_T; struct qf_info_S { @@ -1409,6 +1410,13 @@ void copy_loclist(win_T *from, win_T *to) else to_qfl->qf_title = NULL; + if (from_qfl->qf_ctx != NULL) { + to_qfl->qf_ctx = xcalloc(1, sizeof(typval_T)); + tv_copy(from_qfl->qf_ctx, to_qfl->qf_ctx); + } else { + to_qfl->qf_ctx = NULL; + } + if (from_qfl->qf_count) { qfline_T *from_qfp; qfline_T *prevp; @@ -2410,6 +2418,8 @@ static void qf_free(qf_info_T *qi, int idx) qi->qf_lists[idx].qf_start = NULL; qi->qf_lists[idx].qf_ptr = NULL; qi->qf_lists[idx].qf_title = NULL; + tv_free(qi->qf_lists[idx].qf_ctx); + qi->qf_lists[idx].qf_ctx = NULL; qi->qf_lists[idx].qf_index = 0; qi->qf_lists[idx].qf_start = NULL; qi->qf_lists[idx].qf_last = NULL; @@ -4060,6 +4070,7 @@ enum { QF_GETLIST_ITEMS = 0x2, QF_GETLIST_NR = 0x4, QF_GETLIST_WINID = 0x8, + QF_GETLIST_CONTEXT = 0x10, QF_GETLIST_ALL = 0xFF }; @@ -4110,6 +4121,10 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) flags |= QF_GETLIST_WINID; } + if (tv_dict_find(what, S_LEN("context")) != NULL) { + flags |= QF_GETLIST_CONTEXT; + } + if (flags & QF_GETLIST_TITLE) { char_u *t = qi->qf_lists[qf_idx].qf_title; if (t == NULL) { @@ -4127,6 +4142,18 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) } } + if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) { + if (qi->qf_lists[qf_idx].qf_ctx != NULL) { + di = tv_dict_item_alloc_len(S_LEN("context")); + if (di != NULL) { + tv_copy(qi->qf_lists[qf_idx].qf_ctx, &di->di_tv); + tv_dict_add(retdict, di); + } + } else { + status = tv_dict_add_str(retdict, S_LEN("context"), ""); + } + } + return status; } @@ -4276,6 +4303,14 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action) } } + if ((di = tv_dict_find(what, S_LEN("context"))) != NULL) { + tv_free(qi->qf_lists[qi->qf_curlist].qf_ctx); + + typval_T *ctx = xcalloc(1, sizeof(typval_T)); + tv_copy(&di->di_tv, ctx); + qi->qf_lists[qi->qf_curlist].qf_ctx = ctx; + } + return retval; } @@ -4362,6 +4397,42 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title, return retval; } +static bool mark_quickfix_ctx(qf_info_T *qi, int copyID) +{ + bool abort = false; + + for (int i = 0; i < LISTCOUNT && !abort; i++) { + typval_T *ctx = qi->qf_lists[i].qf_ctx; + if (ctx != NULL && ctx->v_type != VAR_NUMBER + && ctx->v_type != VAR_STRING && ctx->v_type != VAR_FLOAT) { + abort = set_ref_in_item(ctx, copyID, NULL, NULL); + } + } + + return abort; +} + +/// Mark the context of the quickfix list and the location lists (if present) as +/// "in use". So that garabage collection doesn't free the context. +bool set_ref_in_quickfix(int copyID) +{ + bool abort = mark_quickfix_ctx(&ql_info, copyID); + if (abort) { + return abort; + } + + FOR_ALL_TAB_WINDOWS(tp, win) { + if (win->w_llist != NULL) { + abort = mark_quickfix_ctx(win->w_llist, copyID); + if (abort) { + return abort; + } + } + } + + return abort; +} + /* * ":[range]cbuffer [bufnr]" command. * ":[range]caddbuffer [bufnr]" command. diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index dd64e37ce3..1d17da31ed 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1754,6 +1754,38 @@ func Xproperty_tests(cchar) if a:cchar == 'l' call assert_equal({}, getloclist(99, {'title': 1})) endif + + " Context related tests + call g:Xsetlist([], 'a', {'context':[1,2,3]}) + call test_garbagecollect_now() + let d = g:Xgetlist({'context':1}) + call assert_equal([1,2,3], d.context) + call g:Xsetlist([], 'a', {'context':{'color':'green'}}) + let d = g:Xgetlist({'context':1}) + call assert_equal({'color':'green'}, d.context) + call g:Xsetlist([], 'a', {'context':"Context info"}) + let d = g:Xgetlist({'context':1}) + call assert_equal("Context info", d.context) + call g:Xsetlist([], 'a', {'context':246}) + let d = g:Xgetlist({'context':1}) + call assert_equal(246, d.context) + if a:cchar == 'l' + " Test for copying context across two different location lists + new | only + let w1_id = win_getid() + let l = [1] + call setloclist(0, [], 'a', {'context':l}) + new + let w2_id = win_getid() + call add(l, 2) + call assert_equal([1, 2], getloclist(w1_id, {'context':1}).context) + call assert_equal([1, 2], getloclist(w2_id, {'context':1}).context) + unlet! l + call assert_equal([1, 2], getloclist(w2_id, {'context':1}).context) + only + call setloclist(0, [], 'f') + call assert_equal({}, getloclist(0, {'context':1})) + endif endfunc func Test_qf_property() -- cgit From 6fcadab3ce16ff29be53176f8658e61819691df1 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Tue, 19 Dec 2017 10:41:50 -0500 Subject: vim-patch:8.0.0595: Coverity warning for not checking return value Problem: Coverity warning for not checking return value of dict_add(). Solution: Check the return value for FAIL. https://github.com/vim/vim/commit/beb9cb19c660484488a71a25eda46ab0fa579278 --- src/nvim/quickfix.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index e7d11df833..df735e32a9 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4147,7 +4147,9 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) di = tv_dict_item_alloc_len(S_LEN("context")); if (di != NULL) { tv_copy(qi->qf_lists[qf_idx].qf_ctx, &di->di_tv); - tv_dict_add(retdict, di); + if (tv_dict_add(retdict, di) == FAIL) { + tv_dict_item_free(di); + } } } else { status = tv_dict_add_str(retdict, S_LEN("context"), ""); -- cgit From cdd86f42cf93135d1c4dddcc2ba13b9798034350 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Tue, 19 Dec 2017 10:48:31 -0500 Subject: vim-patch:8.0.0597: off-by-one error in size computation Problem: Off-by-one error in buffer size computation. Solution: Use ">=" instead of ">". (Lemonboy, closes vim/vim#1694) https://github.com/vim/vim/commit/253f9128779f315ea670f9b4a17446b7b4c74927 --- src/nvim/quickfix.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index df735e32a9..3608a41a6e 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -801,7 +801,7 @@ restofline: fields->type = *regmatch.startp[i]; } if (fmt_ptr->flags == '+' && !qi->qf_multiscan) { // %+ - if (linelen > fields->errmsglen) { + if (linelen >= fields->errmsglen) { // linelen + null terminator fields->errmsg = xrealloc(fields->errmsg, linelen + 1); fields->errmsglen = linelen + 1; @@ -812,7 +812,7 @@ restofline: continue; } len = (size_t)(regmatch.endp[i] - regmatch.startp[i]); - if (len > fields->errmsglen) { + if (len >= fields->errmsglen) { // len + null terminator fields->errmsg = xrealloc(fields->errmsg, len + 1); fields->errmsglen = len + 1; @@ -889,7 +889,7 @@ restofline: fields->namebuf[0] = NUL; // no match found, remove file name fields->lnum = 0; // don't jump to this line fields->valid = false; - if (linelen > fields->errmsglen) { + if (linelen >= fields->errmsglen) { // linelen + null terminator fields->errmsg = xrealloc(fields->errmsg, linelen + 1); fields->errmsglen = linelen + 1; -- cgit From 190814bdae496c0ce4dfb524f9bb5ee7a09b4f60 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Tue, 19 Dec 2017 10:53:59 -0500 Subject: vim-patch:8.0.0606: cannot set the context for a specified quickfix list Problem: Cannot set the context for a specified quickfix list. Solution: Use the list index instead of the current list. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/6e62da3e14d32f76f60d5cc8b267059923842f17 --- src/nvim/quickfix.c | 9 +++++--- src/nvim/testdir/test_quickfix.vim | 47 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 3608a41a6e..c5d03e73f1 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4278,7 +4278,10 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action) if ((di = tv_dict_find(what, S_LEN("nr"))) != NULL) { // Use the specified quickfix/location list if (di->di_tv.v_type == VAR_NUMBER) { - qf_idx = (int)di->di_tv.vval.v_number - 1; + // for zero use the current list + if (di->di_tv.vval.v_number != 0) { + qf_idx = (int)di->di_tv.vval.v_number - 1; + } if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { return FAIL; } @@ -4306,11 +4309,11 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action) } if ((di = tv_dict_find(what, S_LEN("context"))) != NULL) { - tv_free(qi->qf_lists[qi->qf_curlist].qf_ctx); + tv_free(qi->qf_lists[qf_idx].qf_ctx); typval_T *ctx = xcalloc(1, sizeof(typval_T)); tv_copy(&di->di_tv, ctx); - qi->qf_lists[qi->qf_curlist].qf_ctx = ctx; + qi->qf_lists[qf_idx].qf_ctx = ctx; } return retval; diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 1d17da31ed..30023dddc9 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1786,6 +1786,37 @@ func Xproperty_tests(cchar) call setloclist(0, [], 'f') call assert_equal({}, getloclist(0, {'context':1})) endif + + " Test for changing the context of previous quickfix lists + call g:Xsetlist([], 'f') + Xexpr "One" + Xexpr "Two" + Xexpr "Three" + call g:Xsetlist([], ' ', {'context' : [1], 'nr' : 1}) + call g:Xsetlist([], ' ', {'context' : [2], 'nr' : 2}) + " Also, check for setting the context using quickfix list number zero. + call g:Xsetlist([], ' ', {'context' : [3], 'nr' : 0}) + call test_garbagecollect_now() + let l = g:Xgetlist({'nr' : 1, 'context' : 1}) + call assert_equal([1], l.context) + let l = g:Xgetlist({'nr' : 2, 'context' : 1}) + call assert_equal([2], l.context) + let l = g:Xgetlist({'nr' : 3, 'context' : 1}) + call assert_equal([3], l.context) + + " Test for changing the context through reference and for garbage + " collection of quickfix context + let l = ["red"] + call g:Xsetlist([], ' ', {'context' : l}) + call add(l, "blue") + let x = g:Xgetlist({'context' : 1}) + call add(x.context, "green") + call assert_equal(["red", "blue", "green"], l) + call assert_equal(["red", "blue", "green"], x.context) + unlet l + call test_garbagecollect_now() + let m = g:Xgetlist({'context' : 1}) + call assert_equal(["red", "blue", "green"], m.context) endfunc func Test_qf_property() @@ -2055,3 +2086,19 @@ func Test_qf_free() call XfreeTests('c') call XfreeTests('l') endfunc + +" Test for buffer overflow when parsing lines and adding new entries to +" the quickfix list. +func Test_bufoverflow() + set efm=%f:%l:%m + cgetexpr ['File1:100:' . repeat('x', 1025)] + + set efm=%+GCompiler:\ %.%#,%f:%l:%m + cgetexpr ['Compiler: ' . repeat('a', 1015), 'File1:10:Hello World'] + + set efm=%DEntering\ directory\ %f,%f:%l:%m + cgetexpr ['Entering directory ' . repeat('a', 1006), + \ 'File1:10:Hello World'] + set efm&vim +endfunc + -- cgit From 6c731d33f6729a272c5168d040ee4b86942c599b Mon Sep 17 00:00:00 2001 From: ckelsel Date: Thu, 21 Dec 2017 18:31:26 +0800 Subject: vim-patch:8.0.0314: getcmd*() functions are not tested Problem: getcmdtype(), getcmdpos() and getcmdline() are not tested. Solution: Add tests. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/65189a1294307abf007faab7385dc0145ba72b06 --- src/nvim/testdir/test_cmdline.vim | 32 ++++++++++++++++++++++++++++++++ src/nvim/version.c | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index ac44e09a5a..dc9790a39c 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -330,4 +330,36 @@ func Test_cmdline_search_range() bwipe! endfunc +" Tests for getcmdline(), getcmdpos() and getcmdtype() +func Check_cmdline(cmdtype) + call assert_equal('MyCmd a', getcmdline()) + call assert_equal(8, getcmdpos()) + call assert_equal(a:cmdtype, getcmdtype()) + return '' +endfunc + +func Test_getcmdtype() + call feedkeys(":MyCmd a\=Check_cmdline(':')\\", "xt") + + let cmdtype = '' + debuggreedy + call feedkeys(":debug echo 'test'\", "t") + call feedkeys("let cmdtype = \=string(getcmdtype())\\", "t") + call feedkeys("cont\", "xt") + 0debuggreedy + call assert_equal('>', cmdtype) + + call feedkeys("/MyCmd a\=Check_cmdline('/')\\", "xt") + call feedkeys("?MyCmd a\=Check_cmdline('?')\\", "xt") + + call feedkeys(":call input('Answer?')\", "t") + call feedkeys("MyCmd a\=Check_cmdline('@')\\", "xt") + + call feedkeys(":insert\MyCmd a\=Check_cmdline('-')\\", "xt") + + cnoremap Check_cmdline('=') + call feedkeys("a\=MyCmd a\\\", "xt") + cunmap +endfunc + set cpo& diff --git a/src/nvim/version.c b/src/nvim/version.c index 8ab9fc1a4b..26e15e3b15 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -942,7 +942,7 @@ static const int included_patches[] = { // 317, // 316, // 315, - // 314, + 314, // 313, // 312, 311, -- cgit From eb95b88156aa7ee022db4cf5d48656c1b5fd5a06 Mon Sep 17 00:00:00 2001 From: ckelsel Date: Thu, 21 Dec 2017 18:40:40 +0800 Subject: vim-patch:8.0.0315: :help :[range] does not work Problem: ":help :[range]" does not work. (Tony Mechelynck) Solution: Translate to insert a backslash. https://github.com/vim/vim/commit/a76f59d817e2da31d83b4f0e978b52abe81e0ae9 --- src/nvim/ex_cmds.c | 6 ++++-- src/nvim/version.c | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 8616508d88..7188be5407 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -4619,7 +4619,8 @@ int find_help_tags(char_u *arg, int *num_matches, char_u ***matches, int keep_la "/\\(\\)", "/\\%(\\)", "?", ":?", "?", "g?", "g?g?", "g??", "z?", "/\\?", "/\\z(\\)", "\\=", ":s\\=", - "[count]", "[quotex]", "[range]", + "[count]", "[quotex]", + "[range]", ":[range]", "[pattern]", "\\|", "\\%$", "s/\\~", "s/\\U", "s/\\L", "s/\\1", "s/\\2", "s/\\3", "s/\\9"}; @@ -4628,7 +4629,8 @@ int find_help_tags(char_u *arg, int *num_matches, char_u ***matches, int keep_la "/\\\\(\\\\)", "/\\\\%(\\\\)", "?", ":?", "?", "g?", "g?g?", "g??", "z?", "/\\\\?", "/\\\\z(\\\\)", "\\\\=", ":s\\\\=", - "\\[count]", "\\[quotex]", "\\[range]", + "\\[count]", "\\[quotex]", + "\\[range]", ":\\[range]", "\\[pattern]", "\\\\bar", "/\\\\%\\$", "s/\\\\\\~", "s/\\\\U", "s/\\\\L", "s/\\\\1", "s/\\\\2", "s/\\\\3", "s/\\\\9"}; diff --git a/src/nvim/version.c b/src/nvim/version.c index 26e15e3b15..d511ccb0d6 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -941,7 +941,7 @@ static const int included_patches[] = { // 318, // 317, // 316, - // 315, + 315, 314, // 313, // 312, -- cgit From d2c01d529fb618889ffc25083ba14461ef22f57a Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 23 Dec 2017 15:47:04 +0300 Subject: regexp: Fix linter errors --- src/nvim/regexp.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index a1332a43dd..ddc3681867 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -232,17 +232,17 @@ #define LAST_NL NUPPER + ADD_NL #define WITH_NL(op) ((op) >= FIRST_NL && (op) <= LAST_NL) -#define MOPEN 80 /* -89 Mark this point in input as start of - * \( subexpr. MOPEN + 0 marks start of - * match. */ -#define MCLOSE 90 /* -99 Analogous to MOPEN. MCLOSE + 0 marks - * end of match. */ -#define BACKREF 100 /* -109 node Match same string again \1-\9 */ - -# define ZOPEN 110 /* -119 Mark this point in input as start of - * \z( subexpr. */ -# define ZCLOSE 120 /* -129 Analogous to ZOPEN. */ -# define ZREF 130 /* -139 node Match external submatch \z1-\z9 */ +#define MOPEN 80 // -89 Mark this point in input as start of + // \( … \) subexpr. MOPEN + 0 marks start of + // match. +#define MCLOSE 90 // -99 Analogous to MOPEN. MCLOSE + 0 marks + // end of match. +#define BACKREF 100 // -109 node Match same string again \1-\9. + +# define ZOPEN 110 // -119 Mark this point in input as start of + // \z( … \) subexpr. +# define ZCLOSE 120 // -129 Analogous to ZOPEN. +# define ZREF 130 // -139 node Match external submatch \z1-\z9 #define BRACE_COMPLEX 140 /* -149 node Match nodes between m & n times */ @@ -462,11 +462,11 @@ static int toggle_Magic(int x) #define IEMSG_RET_NULL(m) return (IEMSG(m), rc_did_emsg = true, (void *)NULL) #define EMSG_RET_FAIL(m) return (EMSG(m), rc_did_emsg = true, FAIL) #define EMSG2_RET_NULL(m, c) \ - return (EMSG2((m), (c) ? "" : "\\"), rc_did_emsg = true, (void *)NULL) + return (EMSG2((m), (c) ? "" : "\\"), rc_did_emsg = true, (void *)NULL) #define EMSG2_RET_FAIL(m, c) \ - return (EMSG2((m), (c) ? "" : "\\"), rc_did_emsg = true, FAIL) + return (EMSG2((m), (c) ? "" : "\\"), rc_did_emsg = true, FAIL) #define EMSG_ONE_RET_NULL EMSG2_RET_NULL(_( \ - "E369: invalid item in %s%%[]"), reg_magic == MAGIC_ALL) + "E369: invalid item in %s%%[]"), reg_magic == MAGIC_ALL) #define MAX_LIMIT (32767L << 16L) -- cgit From 5cb7a709e7f60b0e7bcde70a1aa9fea5f060fe0f Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 23 Dec 2017 15:47:23 +0300 Subject: clint: Make linter report line where it found opening brace --- src/clint.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/clint.py b/src/clint.py index 8426807c80..79ab91ebe1 100755 --- a/src/clint.py +++ b/src/clint.py @@ -2124,8 +2124,10 @@ def CheckExpressionAlignment(filename, clean_lines, linenum, error, startpos=0): + (level_starts[depth][2] == '{')): if depth not in ignore_error_levels: error(filename, linenum, 'whitespace/alignment', 2, - 'Inner expression should be aligned ' - 'as opening brace + 1 (+ 2 in case of {)') + ('Inner expression should be aligned ' + 'as opening brace + 1 (+ 2 in case of {{). ' + 'Relevant opening is on line {0!r}').format( + level_starts[depth][3])) prev_line_start = pos elif brace == 'e': pass @@ -2142,7 +2144,8 @@ def CheckExpressionAlignment(filename, clean_lines, linenum, error, startpos=0): ignore_error_levels.add(depth) line_ended_with_opening = ( pos == len(line) - 2 * (line.endswith(' \\')) - 1) - level_starts[depth] = (pos, line_ended_with_opening, brace) + level_starts[depth] = (pos, line_ended_with_opening, brace, + linenum) if line_ended_with_opening: depth_line_starts[depth] = (prev_line_start, brace) else: -- cgit From 308dd53783314103710a37c9809eddb78279eab4 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Mon, 27 Nov 2017 13:01:49 +0100 Subject: channel: check for existance before trying to set key This avoids an error message in async context, where it is not safe. --- src/nvim/channel.c | 23 +++++++++++++++++++---- src/nvim/globals.h | 3 +++ 2 files changed, 22 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 40af470bde..3807f2b3cd 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -599,6 +599,7 @@ static void on_stdio_input(Stream *stream, RBuffer *buf, size_t count, on_channel_output(stream, chan, buf, count, eof, &chan->on_stdout, "stdin"); } +/// @param type must have static lifetime static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, size_t count, bool eof, CallbackReader *reader, const char *type) @@ -613,14 +614,20 @@ static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, if (reader->cb.type != kCallbackNone) { process_channel_event(chan, &reader->cb, type, reader->buffer.ga_data, (size_t)reader->buffer.ga_len, 0); - ga_clear(&reader->buffer); } else if (reader->self) { - list_T *data = buffer_to_tv_list(reader->buffer.ga_data, - (size_t)reader->buffer.ga_len); - tv_dict_add_list(reader->self, type, strlen(type), data); + if (tv_dict_find(reader->self, type, -1) == NULL) { + list_T *data = buffer_to_tv_list(reader->buffer.ga_data, + (size_t)reader->buffer.ga_len); + tv_dict_add_list(reader->self, type, strlen(type), data); + } else { + // can't display error message now, defer it. + channel_incref(chan); + multiqueue_put(chan->events, on_buffered_error, 2, chan, type); + } } else { abort(); } + ga_clear(&reader->buffer); } else if (reader->cb.type != kCallbackNone) { process_channel_event(chan, &reader->cb, type, ptr, 0, 0); } @@ -641,6 +648,14 @@ static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, } } +static void on_buffered_error(void **args) +{ + Channel *chan = (Channel *)args[0]; + const char *stream = (const char *)args[1]; + EMSG3(_(e_streamkey), stream, chan->id); + channel_decref(chan); +} + static void channel_process_exit_cb(Process *proc, int status, void *data) { Channel *chan = data; diff --git a/src/nvim/globals.h b/src/nvim/globals.h index dd216f177f..3df201b6bf 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -1063,6 +1063,9 @@ EXTERN char_u e_stdiochan2[] INIT(= N_( EXTERN char_u e_invstream[] INIT(= N_("E906: invalid stream for channel")); EXTERN char_u e_invstreamrpc[] INIT(= N_( "E906: invalid stream for rpc channel, use 'rpc'")); +EXTERN char_u e_streamkey[] INIT(= N_( + "E5210: dict key '%s' already set for buffered stream in channel %" + PRIu64)); EXTERN char_u e_libcall[] INIT(= N_("E364: Library call failed for \"%s()\"")); EXTERN char_u e_mkdir[] INIT(= N_("E739: Cannot create directory %s: %s")); EXTERN char_u e_markinval[] INIT(= N_("E19: Mark has invalid line number")); -- cgit From 6b45dbca0429c52cb70908497bc7a0fed9db2d8d Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 24 Dec 2017 00:29:38 +0300 Subject: mark: Make sure that jumplist item will not have zero lnum Fixes #7169 --- src/nvim/mark.c | 4 ++++ src/nvim/shada.c | 6 ++++++ 2 files changed, 10 insertions(+) (limited to 'src') diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 1ba400972c..3cd26a5bf7 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -171,6 +171,10 @@ void setpcmark(void) curwin->w_prev_pcmark = curwin->w_pcmark; curwin->w_pcmark = curwin->w_cursor; + if (curwin->w_pcmark.lnum == 0) { + curwin->w_pcmark.lnum = 1; + } + /* If jumplist is full: remove oldest entry */ if (++curwin->w_jumplistlen > JUMPLISTSIZE) { curwin->w_jumplistlen = JUMPLISTSIZE; diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 6bf816bb74..ce9303f14d 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -2557,6 +2557,12 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, xfmark_T fm; jump_iter = mark_jumplist_iter(jump_iter, curwin, &fm); + if (fm.fmark.mark.lnum == 0) { + iemsgf("ShaDa: mark lnum zero (ji:%p, js:%p, len:%i)", + (void *)jump_iter, (void *)&curwin->w_jumplist[0], + curwin->w_jumplistlen); + continue; + } const buf_T *const buf = (fm.fmark.fnum == 0 ? NULL : buflist_findnr(fm.fmark.fnum)); -- cgit From 0c533a488fbe453ae39017a586eff7813da8ed8a Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 24 Dec 2017 01:43:42 +0300 Subject: *: Remove most calls to tv_list_item_alloc Still left calls in eval/typval.c and test/unit/eval/helpers.lua. Latter is the only reason why function did not receive `static` modifier. --- src/nvim/api/private/helpers.c | 8 ++--- src/nvim/eval.c | 75 +++++++++++++++++------------------------ src/nvim/eval/decode.c | 40 ++++++++++------------ src/nvim/eval/typval.c | 76 ++++++++++++++++++++++++------------------ src/nvim/lua/converter.c | 36 ++++++++++++++------ 5 files changed, 119 insertions(+), 116 deletions(-) (limited to 'src') diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 4492f8bb93..26ad7ac1a6 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -787,16 +787,14 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) for (uint32_t i = 0; i < obj.data.array.size; i++) { Object item = obj.data.array.items[i]; - listitem_T *li = tv_list_item_alloc(); + typval_T li_tv; - if (!object_to_vim(item, TV_LIST_ITEM_TV(li), err)) { - // cleanup - tv_list_item_free(li); + if (!object_to_vim(item, &li_tv, err)) { tv_list_free(list); return false; } - tv_list_append(list, li); + tv_list_append_owned_tv(list, li_tv); } tv_list_ref(list); diff --git a/src/nvim/eval.c b/src/nvim/eval.c index e6cb8cdec0..fac15770a6 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -4874,8 +4874,6 @@ void partial_unref(partial_T *pt) static int get_list_tv(char_u **arg, typval_T *rettv, int evaluate) { list_T *l = NULL; - typval_T tv; - listitem_T *item; if (evaluate) { l = tv_list_alloc(); @@ -4883,13 +4881,13 @@ static int get_list_tv(char_u **arg, typval_T *rettv, int evaluate) *arg = skipwhite(*arg + 1); while (**arg != ']' && **arg != NUL) { - if (eval1(arg, &tv, evaluate) == FAIL) /* recursive! */ + typval_T tv; + if (eval1(arg, &tv, evaluate) == FAIL) { // Recursive! goto failret; + } if (evaluate) { - item = tv_list_item_alloc(); - *TV_LIST_ITEM_TV(item) = tv; - TV_LIST_ITEM_TV(item)->v_lock = VAR_UNLOCKED; - tv_list_append(l, item); + tv.v_lock = VAR_UNLOCKED; + tv_list_append_owned_tv(l, tv); } if (**arg == ']') { @@ -11441,40 +11439,38 @@ static void dict_list(typval_T *const tv, typval_T *const rettv, tv_list_alloc_ret(rettv); TV_DICT_ITER(tv->vval.v_dict, di, { - listitem_T *const li = tv_list_item_alloc(); - tv_list_append(rettv->vval.v_list, li); + typval_T tv = { .v_lock = VAR_UNLOCKED }; switch (what) { case kDictListKeys: { - TV_LIST_ITEM_TV(li)->v_type = VAR_STRING; - TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; - TV_LIST_ITEM_TV(li)->vval.v_string = vim_strsave(di->di_key); + tv.v_type = VAR_STRING; + tv.vval.v_string = vim_strsave(di->di_key); break; } case kDictListValues: { - tv_copy(&di->di_tv, TV_LIST_ITEM_TV(li)); + tv_copy(&di->di_tv, &tv); break; } case kDictListItems: { // items() list_T *const sub_l = tv_list_alloc(); - TV_LIST_ITEM_TV(li)->v_type = VAR_LIST; - TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; - TV_LIST_ITEM_TV(li)->vval.v_list = sub_l; + tv.v_type = VAR_LIST; + tv.vval.v_list = sub_l; tv_list_ref(sub_l); - listitem_T *sub_li = tv_list_item_alloc(); - tv_list_append(sub_l, sub_li); - TV_LIST_ITEM_TV(sub_li)->v_type = VAR_STRING; - TV_LIST_ITEM_TV(sub_li)->v_lock = VAR_UNLOCKED; - TV_LIST_ITEM_TV(sub_li)->vval.v_string = vim_strsave(di->di_key); + tv_list_append_owned_tv(sub_l, (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = (char_u *)xstrdup((const char *)di->di_key), + }); + + tv_list_append_tv(sub_l, &di->di_tv); - sub_li = tv_list_item_alloc(); - tv_list_append(sub_l, sub_li); - tv_copy(&di->di_tv, TV_LIST_ITEM_TV(sub_li)); break; } } + + tv_list_append_owned_tv(rettv->vval.v_list, tv); }); } @@ -12762,13 +12758,12 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) goto f_msgpackparse_exit; } if (result == MSGPACK_UNPACK_SUCCESS) { - listitem_T *li = tv_list_item_alloc(); - TV_LIST_ITEM_TV(li)->v_type = VAR_UNKNOWN; - tv_list_append(ret_list, li); - if (msgpack_to_vim(unpacked.data, TV_LIST_ITEM_TV(li)) == FAIL) { + typval_T tv = { .v_type = VAR_UNKNOWN }; + if (msgpack_to_vim(unpacked.data, &tv) == FAIL) { EMSG2(_(e_invarg2), "Failed to convert msgpack string"); goto f_msgpackparse_exit; } + tv_list_append_owned_tv(ret_list, tv); } if (result == MSGPACK_UNPACK_CONTINUE) { if (rlret == OK) { @@ -13030,7 +13025,6 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary)); ++p) { if (*p == '\n' || readlen <= 0) { - listitem_T *li; char_u *s = NULL; size_t len = p - start; @@ -13057,11 +13051,11 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) prevlen = prevsize = 0; } - li = tv_list_item_alloc(); - TV_LIST_ITEM_TV(li)->v_type = VAR_STRING; - TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; - TV_LIST_ITEM_TV(li)->vval.v_string = s; - tv_list_append(rettv->vval.v_list, li); + tv_list_append_owned_tv(rettv->vval.v_list, (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = s, + }); start = p + 1; /* step over newline */ if ((++cnt >= maxline && maxline >= 0) || readlen <= 0) @@ -14310,11 +14304,7 @@ static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) // Copy addrs into a linked list. list_T *l = tv_list_alloc_ret(rettv); for (size_t i = 0; i < n; i++) { - listitem_T *li = tv_list_item_alloc(); - TV_LIST_ITEM_TV(li)->v_type = VAR_STRING; - TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; - TV_LIST_ITEM_TV(li)->vval.v_string = (char_u *)addrs[i]; - tv_list_append(l, li); + tv_list_append_allocated_string(l, addrs[i]); } xfree(addrs); } @@ -15619,12 +15609,7 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) for (int i = 0; i < ga.ga_len; i++) { char *p = ((char **)ga.ga_data)[i]; - - listitem_T *const li = tv_list_item_alloc(); - TV_LIST_ITEM_TV(li)->v_type = VAR_STRING; - TV_LIST_ITEM_TV(li)->v_lock = VAR_LOCKED; - TV_LIST_ITEM_TV(li)->vval.v_string = (char_u *)p; - tv_list_append(rettv->vval.v_list, li); + tv_list_append_allocated_string(rettv->vval.v_list, p); } ga_clear(&ga); } diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index d5c65ebe81..af4e055d23 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -127,9 +127,7 @@ static inline int json_decoder_pop(ValuesStackItem obj, return FAIL; } assert(last_container.special_val == NULL); - listitem_T *obj_li = tv_list_item_alloc(); - *TV_LIST_ITEM_TV(obj_li) = obj.val; - tv_list_append(last_container.container.vval.v_list, obj_li); + tv_list_append_owned_tv(last_container.container.vval.v_list, obj.val); } else if (last_container.stack_index == kv_size(*stack) - 2) { if (!obj.didcolon) { EMSG2(_("E474: Expected colon before dictionary value: %s"), @@ -154,12 +152,8 @@ static inline int json_decoder_pop(ValuesStackItem obj, } else { list_T *const kv_pair = tv_list_alloc(); tv_list_append_list(last_container.special_val, kv_pair); - listitem_T *const key_li = tv_list_item_alloc(); - *TV_LIST_ITEM_TV(key_li) = key.val; - tv_list_append(kv_pair, key_li); - listitem_T *const val_li = tv_list_item_alloc(); - *TV_LIST_ITEM_TV(val_li) = obj.val; - tv_list_append(kv_pair, val_li); + tv_list_append_owned_tv(kv_pair, key.val); + tv_list_append_owned_tv(kv_pair, obj.val); } } else { // Object with key only @@ -1047,10 +1041,10 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) .vval = { .v_list = list }, }; for (size_t i = 0; i < mobj.via.array.size; i++) { - listitem_T *const li = tv_list_item_alloc(); - TV_LIST_ITEM_TV(li)->v_type = VAR_UNKNOWN; - tv_list_append(list, li); - if (msgpack_to_vim(mobj.via.array.ptr[i], TV_LIST_ITEM_TV(li)) + // Not populated yet, need to create list item to push. + tv_list_append_owned_tv(list, (typval_T) { .v_type = VAR_UNKNOWN }); + if (msgpack_to_vim(mobj.via.array.ptr[i], + TV_LIST_ITEM_TV(tv_list_last(list))) == FAIL) { return FAIL; } @@ -1095,20 +1089,20 @@ msgpack_to_vim_generic_map: {} for (size_t i = 0; i < mobj.via.map.size; i++) { list_T *const kv_pair = tv_list_alloc(); tv_list_append_list(list, kv_pair); - listitem_T *const key_li = tv_list_item_alloc(); - TV_LIST_ITEM_TV(key_li)->v_type = VAR_UNKNOWN; - tv_list_append(kv_pair, key_li); - listitem_T *const val_li = tv_list_item_alloc(); - TV_LIST_ITEM_TV(val_li)->v_type = VAR_UNKNOWN; - tv_list_append(kv_pair, val_li); - if (msgpack_to_vim(mobj.via.map.ptr[i].key, TV_LIST_ITEM_TV(key_li)) - == FAIL) { + + typval_T key_tv = { .v_type = VAR_UNKNOWN }; + if (msgpack_to_vim(mobj.via.map.ptr[i].key, &key_tv) == FAIL) { + tv_clear(&key_tv); return FAIL; } - if (msgpack_to_vim(mobj.via.map.ptr[i].val, TV_LIST_ITEM_TV(val_li)) - == FAIL) { + tv_list_append_owned_tv(kv_pair, key_tv); + + typval_T val_tv = { .v_type = VAR_UNKNOWN }; + if (msgpack_to_vim(mobj.via.map.ptr[i].val, &val_tv) == FAIL) { + tv_clear(&val_tv); return FAIL; } + tv_list_append_owned_tv(kv_pair, val_tv); } break; } diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 53c56d0ffd..0755507f91 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -393,19 +393,30 @@ void tv_list_append_tv(list_T *const l, typval_T *const tv) tv_list_append(l, li); } +/// Like tv_list_append_tv(), but tv is moved to a list +/// +/// This means that it is no longer valid to use contents of the typval_T after +/// function exits. +void tv_list_append_owned_tv(list_T *const l, typval_T tv) + FUNC_ATTR_NONNULL_ALL +{ + listitem_T *const li = tv_list_item_alloc(); + *TV_LIST_ITEM_TV(li) = tv; + tv_list_append(l, li); +} + /// Append a list to a list as one item /// /// @param[out] l List to append to. /// @param[in,out] itemlist List to append. Reference count is increased. -void tv_list_append_list(list_T *const list, list_T *const itemlist) +void tv_list_append_list(list_T *const l, list_T *const itemlist) FUNC_ATTR_NONNULL_ARG(1) { - listitem_T *const li = tv_list_item_alloc(); - - TV_LIST_ITEM_TV(li)->v_type = VAR_LIST; - TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; - TV_LIST_ITEM_TV(li)->vval.v_list = itemlist; - tv_list_append(list, li); + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_LIST, + .v_lock = VAR_UNLOCKED, + .vval.v_list = itemlist, + }); tv_list_ref(itemlist); } @@ -413,15 +424,14 @@ void tv_list_append_list(list_T *const list, list_T *const itemlist) /// /// @param[out] l List to append to. /// @param[in,out] dict Dictionary to append. Reference count is increased. -void tv_list_append_dict(list_T *const list, dict_T *const dict) +void tv_list_append_dict(list_T *const l, dict_T *const dict) FUNC_ATTR_NONNULL_ARG(1) { - listitem_T *const li = tv_list_item_alloc(); - - TV_LIST_ITEM_TV(li)->v_type = VAR_DICT; - TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; - TV_LIST_ITEM_TV(li)->vval.v_dict = dict; - tv_list_append(list, li); + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_DICT, + .v_lock = VAR_UNLOCKED, + .vval.v_dict = dict, + }); if (dict != NULL) { dict->dv_refcount++; } @@ -438,14 +448,15 @@ void tv_list_append_string(list_T *const l, const char *const str, const ssize_t len) FUNC_ATTR_NONNULL_ARG(1) { - if (str == NULL) { - assert(len == 0 || len == -1); - tv_list_append_allocated_string(l, NULL); - } else { - tv_list_append_allocated_string(l, (len >= 0 - ? xmemdupz(str, (size_t)len) - : xstrdup(str))); - } + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = (str == NULL + ? NULL + : (len >= 0 + ? xmemdupz(str, (size_t)len) + : xstrdup(str))), + }); } /// Append given string to the list @@ -457,12 +468,11 @@ void tv_list_append_string(list_T *const l, const char *const str, void tv_list_append_allocated_string(list_T *const l, char *const str) FUNC_ATTR_NONNULL_ARG(1) { - listitem_T *const li = tv_list_item_alloc(); - - tv_list_append(l, li); - TV_LIST_ITEM_TV(li)->v_type = VAR_STRING; - TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; - TV_LIST_ITEM_TV(li)->vval.v_string = (char_u *)str; + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = (char_u *)str, + }); } /// Append number to the list @@ -472,11 +482,11 @@ void tv_list_append_allocated_string(list_T *const l, char *const str) /// listitem_T. void tv_list_append_number(list_T *const l, const varnumber_T n) { - listitem_T *const li = tv_list_item_alloc(); - TV_LIST_ITEM_TV(li)->v_type = VAR_NUMBER; - TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; - TV_LIST_ITEM_TV(li)->vval.v_number = n; - tv_list_append(l, li); + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_NUMBER, + .v_lock = VAR_UNLOCKED, + .vval.v_number = n, + }); } //{{{2 Operations on the whole list diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 8303ecdd37..89fedae73a 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -212,19 +212,27 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) const char *s = lua_tolstring(lstate, -2, &len); if (cur.special) { list_T *const kv_pair = tv_list_alloc(); + tv_list_append_list(cur.tv->vval.v_list, kv_pair); - listitem_T *const key = tv_list_item_alloc(); - *TV_LIST_ITEM_TV(key) = decode_string(s, len, kTrue, false, false); - tv_list_append(kv_pair, key); - if (TV_LIST_ITEM_TV(key)->v_type == VAR_UNKNOWN) { + typval_T s_tv = decode_string(s, len, kTrue, false, false); + if (s_tv.v_type == VAR_UNKNOWN) { ret = false; tv_list_unref(kv_pair); continue; } - listitem_T *const val = tv_list_item_alloc(); - tv_list_append(kv_pair, val); + tv_list_append_owned_tv(kv_pair, s_tv); + + // Value: not populated yet, need to create list item to push. + tv_list_append_owned_tv(kv_pair, (typval_T) { + .v_type = VAR_UNKNOWN, + }); kv_push(stack, cur); - cur = (TVPopStackItem) { TV_LIST_ITEM_TV(val), false, false, 0 }; + cur = (TVPopStackItem) { + .tv = TV_LIST_ITEM_TV(tv_list_last(kv_pair)), + .container = false, + .special = false, + .idx = 0, + }; } else { dictitem_T *const di = tv_dict_item_alloc_len(s, len); if (tv_dict_add(cur.tv->vval.v_dict, di) == FAIL) { @@ -244,10 +252,18 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) lua_pop(lstate, 2); continue; } - listitem_T *const li = tv_list_item_alloc(); - tv_list_append(cur.tv->vval.v_list, li); + // Not populated yet, need to create list item to push. + tv_list_append_owned_tv(cur.tv->vval.v_list, (typval_T) { + .v_type = VAR_UNKNOWN, + }); kv_push(stack, cur); - cur = (TVPopStackItem) { TV_LIST_ITEM_TV(li), false, false, 0 }; + // TODO(ZyX-I): Use indexes, here list item *will* be reallocated. + cur = (TVPopStackItem) { + .tv = TV_LIST_ITEM_TV(tv_list_last(cur.tv->vval.v_list)), + .container = false, + .special = false, + .idx = 0, + }; } } assert(!cur.container); -- cgit From 6bf3dc77c484f757d37c22694e1278abc014278f Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 24 Dec 2017 01:52:11 +0300 Subject: eval/typval: Make tv_list_item_alloc static Better write this bit in lua then make reviewers or clint filter out tv_list_item_alloc(). --- src/nvim/eval/typval.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 0755507f91..751f1e7aec 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -52,7 +52,7 @@ const char *const tv_empty_string = ""; /// and specifically set lv_lock. /// /// @return [allocated] new list item. -listitem_T *tv_list_item_alloc(void) +static listitem_T *tv_list_item_alloc(void) FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC { return xmalloc(sizeof(listitem_T)); -- cgit From 608c3d7baf2745b188bc42f9f45ad1bb188a4828 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 24 Dec 2017 02:11:46 +0300 Subject: eval/typval: Remove tv_list_item_free() as it is unused --- src/nvim/eval/typval.c | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 751f1e7aec..a49c34e957 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -58,18 +58,6 @@ static listitem_T *tv_list_item_alloc(void) return xmalloc(sizeof(listitem_T)); } -/// Free a list item -/// -/// Also clears the value. Does not touch watchers. -/// -/// @param[out] item Item to free. -void tv_list_item_free(listitem_T *const item) - FUNC_ATTR_NONNULL_ALL -{ - tv_clear(TV_LIST_ITEM_TV(item)); - xfree(item); -} - /// Remove a list item from a List and free it /// /// Also clears the value. @@ -80,7 +68,8 @@ void tv_list_item_remove(list_T *const l, listitem_T *const item) FUNC_ATTR_NONNULL_ALL { tv_list_remove_items(l, item, item); - tv_list_item_free(item); + tv_clear(TV_LIST_ITEM_TV(item)); + xfree(item); } //{{{2 List watchers -- cgit From ac55558c97d02f18b9a99cf2dd279451481c4a2f Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 24 Dec 2017 02:41:34 +0300 Subject: eval/typval: Make tv_list_item_remove return pointer to the next item --- src/nvim/eval.c | 19 ++++++++----------- src/nvim/eval/typval.c | 7 ++++++- 2 files changed, 14 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index fac15770a6..0080eded98 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2904,9 +2904,7 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit) // Delete a range of List items. while (lp->ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) { - li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li); - tv_list_item_remove(lp->ll_list, lp->ll_li); - lp->ll_li = li; + lp->ll_li = tv_list_item_remove(lp->ll_list, lp->ll_li); lp->ll_n1++; } } else { @@ -8467,7 +8465,6 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) static void filter_map(typval_T *argvars, typval_T *rettv, int map) { typval_T *expr; - listitem_T *li, *nli; list_T *l = NULL; dictitem_T *di; hashtab_T *ht; @@ -8551,20 +8548,21 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) } else { vimvars[VV_KEY].vv_type = VAR_NUMBER; - for (li = tv_list_first(l); li != NULL; li = nli) { + for (listitem_T *li = tv_list_first(l); li != NULL;) { if (map && tv_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, TV_TRANSLATE)) { break; } - nli = TV_LIST_ITEM_NEXT(l, li); vimvars[VV_KEY].vv_nr = idx; if (filter_map_one(TV_LIST_ITEM_TV(li), expr, map, &rem) == FAIL || did_emsg) { break; } if (!map && rem) { - tv_list_item_remove(l, li); + li = tv_list_item_remove(l, li); + } else { + li = TV_LIST_ITEM_NEXT(l, li); } idx++; } @@ -15440,18 +15438,17 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) int idx = 0; for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l)) - ; li != NULL - ; li = TV_LIST_ITEM_NEXT(l, li)) { + ; li != NULL;) { listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li); if (item_compare_func_ptr(&prev_li, &li) == 0) { if (info.item_compare_func_err) { EMSG(_("E882: Uniq compare function failed")); break; } - tv_list_item_remove(l, li); - li = tv_list_find(l, idx); + li = tv_list_item_remove(l, li); } else { idx++; + li = TV_LIST_ITEM_NEXT(l, li); } } } diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index a49c34e957..4ebe12104f 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -64,12 +64,17 @@ static listitem_T *tv_list_item_alloc(void) /// /// @param[out] l List to remove item from. /// @param[in,out] item Item to remove. -void tv_list_item_remove(list_T *const l, listitem_T *const item) +/// +/// @return Pointer to the list item just after removed one, NULL if removed +/// item was the last one. +listitem_T *tv_list_item_remove(list_T *const l, listitem_T *const item) FUNC_ATTR_NONNULL_ALL { + listitem_T *const next_item = TV_LIST_ITEM_NEXT(l, item); tv_list_remove_items(l, item, item); tv_clear(TV_LIST_ITEM_TV(item)); xfree(item); + return next_item; } //{{{2 List watchers -- cgit From 67fa9e5237526b092c1ac672504e259db7f14126 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 24 Dec 2017 16:38:30 +0300 Subject: eval: Rename tv_list_remove_items() to tv_list_drop_items() tv_list_remove_items() may cause confusion with tv_list_item_remove() --- src/nvim/eval.c | 10 +++++----- src/nvim/eval/typval.c | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 0080eded98..c5f4b78198 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1534,8 +1534,8 @@ ex_let_vars ( EMSG(_("E688: More targets than List items")); return FAIL; } - // l may actually be NULL, but it should fail with E688 or even earlier if you - // try to do ":let [] = v:_null_list". + // lt may actually be NULL, but it should fail with E688 or even earlier if + // you try to do ":let [] = v:_null_list". assert(l != NULL); listitem_T *item = tv_list_first(l); @@ -1543,11 +1543,11 @@ ex_let_vars ( arg = skipwhite(arg + 1); arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, (const char_u *)",;]", nextchars); - item = TV_LIST_ITEM_NEXT(l, item); if (arg == NULL) { return FAIL; } + item = TV_LIST_ITEM_NEXT(l, item); arg = skipwhite(arg); if (*arg == ';') { /* Put the rest of the list (may be empty) in the var after ';'. @@ -1559,7 +1559,7 @@ ex_let_vars ( } ltv.v_type = VAR_LIST; - ltv.v_lock = 0; + ltv.v_lock = VAR_UNLOCKED; ltv.vval.v_list = rest_list; tv_list_ref(rest_list); @@ -13283,7 +13283,7 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else { if (argvars[2].v_type == VAR_UNKNOWN) { // Remove one item, return its value. - tv_list_remove_items(l, item, item); + tv_list_drop_items(l, item, item); *rettv = *TV_LIST_ITEM_TV(item); xfree(item); } else { diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 4ebe12104f..4f717250cd 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -71,7 +71,7 @@ listitem_T *tv_list_item_remove(list_T *const l, listitem_T *const item) FUNC_ATTR_NONNULL_ALL { listitem_T *const next_item = TV_LIST_ITEM_NEXT(l, item); - tv_list_remove_items(l, item, item); + tv_list_drop_items(l, item, item); tv_clear(TV_LIST_ITEM_TV(item)); xfree(item); return next_item; @@ -261,8 +261,8 @@ void tv_list_unref(list_T *const l) /// @param[out] l List to remove from. /// @param[in] item First item to remove. /// @param[in] item2 Last item to remove. -void tv_list_remove_items(list_T *const l, listitem_T *const item, - listitem_T *const item2) +void tv_list_drop_items(list_T *const l, listitem_T *const item, + listitem_T *const item2) FUNC_ATTR_NONNULL_ALL { // Notify watchers. @@ -296,7 +296,7 @@ void tv_list_move_items(list_T *const l, listitem_T *const item, const int cnt) FUNC_ATTR_NONNULL_ALL { - tv_list_remove_items(l, item, item2); + tv_list_drop_items(l, item, item2); item->li_prev = tgt_l->lv_last; item2->li_next = NULL; if (tgt_l->lv_last == NULL) { -- cgit From 7997147245da8c9e9fca4e1862d71bd6b28e1c06 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 24 Dec 2017 17:52:04 +0300 Subject: eval: Replace some tv_list_item_remove() calls There is nothing wrong with them, just it is generally better to remove a range then to remove items individually. --- src/nvim/eval.c | 44 ++++++++++++++++++++------------------------ src/nvim/eval/typval.c | 17 +++++++++++++++++ 2 files changed, 37 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index c5f4b78198..c878f0481d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2887,26 +2887,25 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit) lp->ll_name_len))) { return FAIL; } else if (lp->ll_range) { - listitem_T *li; - listitem_T *ll_li = lp->ll_li; - int ll_n1 = lp->ll_n1; - - while (ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= ll_n1)) { - li = TV_LIST_ITEM_NEXT(lp->ll_list, ll_li); - if (tv_check_lock(TV_LIST_ITEM_TV(ll_li)->v_lock, + // Delete a range of List items. + listitem_T *const first_li = lp->ll_li; + listitem_T *last_li = first_li; + for (;;) { + listitem_T *const li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li); + if (tv_check_lock(TV_LIST_ITEM_TV(lp->ll_li)->v_lock, (const char *)lp->ll_name, lp->ll_name_len)) { return false; } - ll_li = li; - ll_n1++; - } - - // Delete a range of List items. - while (lp->ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) { - lp->ll_li = tv_list_item_remove(lp->ll_list, lp->ll_li); + lp->ll_li = li; lp->ll_n1++; + if (lp->ll_li == NULL || (!lp->ll_empty2 && lp->ll_n2 < lp->ll_n1)) { + break; + } else { + last_li = lp->ll_li; + } } + tv_list_remove_items(lp->ll_list, first_li, last_li); } else { if (lp->ll_list != NULL) { // unlet a List item. @@ -13123,16 +13122,13 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } /* while */ - /* - * For a negative line count use only the lines at the end of the file, - * free the rest. - */ - if (maxline < 0) - while (cnt > -maxline) { - tv_list_item_remove(rettv->vval.v_list, - tv_list_first(rettv->vval.v_list)); - cnt--; - } + // For a negative line count use only the lines at the end of the file, + // free the rest. + if (maxline < -tv_list_len(rettv->vval.v_list)) { + listitem_T *const first_li = tv_list_find(rettv->vval.v_list, maxline); + listitem_T *const last_li = tv_list_last(rettv->vval.v_list); + tv_list_remove_items(rettv->vval.v_list, first_li, last_li); + } xfree(prev); fclose(fd); diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 4f717250cd..5c9005595d 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -284,6 +284,23 @@ void tv_list_drop_items(list_T *const l, listitem_T *const item, l->lv_idx_item = NULL; } +/// Like tv_list_drop_items, but also frees all removed items +void tv_list_remove_items(list_T *const l, listitem_T *const item, + listitem_T *const item2) + FUNC_ATTR_NONNULL_ALL +{ + tv_list_drop_items(l, item, item2); + for(listitem_T *li = item;;) { + tv_clear(TV_LIST_ITEM_TV(li)); + listitem_T *const nli = li->li_next; + xfree(li); + if (li == item2) { + break; + } + li = nli; + } +} + /// Move items "item" to "item2" from list "l" to the end of the list "tgt_l" /// /// @param[out] l List to move from. -- cgit From bc52ec61105160eb2b648af239e44cc594529fbf Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 24 Dec 2017 23:09:26 +0300 Subject: *: Fix linter errors --- src/nvim/eval/typval.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 5c9005595d..fba9e9c843 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -290,7 +290,7 @@ void tv_list_remove_items(list_T *const l, listitem_T *const item, FUNC_ATTR_NONNULL_ALL { tv_list_drop_items(l, item, item2); - for(listitem_T *li = item;;) { + for (listitem_T *li = item;;) { tv_clear(TV_LIST_ITEM_TV(li)); listitem_T *const nli = li->li_next; xfree(li); -- cgit From 6ab5eb347bb437c4aed82e329e65244c63566530 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 25 Dec 2017 01:08:58 +0300 Subject: eval: Remove magic numbers from find_some_match() type argument --- src/nvim/eval.c | 155 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 93 insertions(+), 62 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index c878f0481d..d0f8ced18b 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -215,6 +215,15 @@ static garray_T ga_scripts = {0, 0, sizeof(scriptvar_T *), 4, NULL}; static int echo_attr = 0; /* attributes used for ":echo" */ +/// Describe data to return from find_some_match() +typedef enum { + kSomeMatch, ///< Data for match(). + kSomeMatchEnd, ///< Data for matchend(). + kSomeMatchList, ///< Data for matchlist(). + kSomeMatchStr, ///< Data for matchstr(). + kSomeMatchStrPos, ///< Data for matchstrpos(). +} SomeMatchType; + /// trans_function_name() flags typedef enum { TFN_INT = 1, ///< May use internal function name @@ -12183,7 +12192,8 @@ static void f_mapcheck(typval_T *argvars, typval_T *rettv, FunPtr fptr) } -static void find_some_match(typval_T *argvars, typval_T *rettv, int type) +static void find_some_match(typval_T *const argvars, typval_T *const rettv, + const SomeMatchType type) { char_u *str = NULL; long len = 0; @@ -12204,24 +12214,36 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) p_cpo = (char_u *)""; rettv->vval.v_number = -1; - if (type == 3 || type == 4) { - // type 3: return empty list when there are no matches. - // type 4: return ["", -1, -1, -1] - tv_list_alloc_ret(rettv); - if (type == 4) { + switch (type) { + // matchlist(): return empty list when there are no matches. + case kSomeMatchList: { + tv_list_alloc_ret(rettv); + FALLTHROUGH; + } + // matchstrpos(): return ["", -1, -1, -1] + case kSomeMatchStrPos: { tv_list_append_string(rettv->vval.v_list, "", 0); tv_list_append_number(rettv->vval.v_list, -1); tv_list_append_number(rettv->vval.v_list, -1); tv_list_append_number(rettv->vval.v_list, -1); + break; + } + case kSomeMatchStr: { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + break; + } + case kSomeMatch: + case kSomeMatchEnd: { + // Do nothing: zero is default. + break; } - } else if (type == 2) { - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; } if (argvars[0].v_type == VAR_LIST) { - if ((l = argvars[0].vval.v_list) == NULL) + if ((l = argvars[0].vval.v_list) == NULL) { goto theend; + } li = tv_list_first(l); } else { expr = str = (char_u *)tv_get_string(&argvars[0]); @@ -12311,63 +12333,72 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) } if (match) { - if (type == 4) { - list_T *const ret_l = rettv->vval.v_list; - listitem_T *li1 = tv_list_first(ret_l); - listitem_T *li2 = TV_LIST_ITEM_NEXT(ret_l, li1); - listitem_T *li3 = TV_LIST_ITEM_NEXT(ret_l, li2); - listitem_T *li4 = TV_LIST_ITEM_NEXT(ret_l, li3); - xfree(TV_LIST_ITEM_TV(li1)->vval.v_string); - - const size_t rd = (size_t)(regmatch.endp[0] - regmatch.startp[0]); - TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz( - (const char *)regmatch.startp[0], rd); - TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)( - regmatch.startp[0] - expr); - TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)( - regmatch.endp[0] - expr); - if (l != NULL) { - TV_LIST_ITEM_TV(li2)->vval.v_number = (varnumber_T)idx; + switch (type) { + case kSomeMatchStrPos: { + list_T *const ret_l = rettv->vval.v_list; + listitem_T *li1 = tv_list_first(ret_l); + listitem_T *li2 = TV_LIST_ITEM_NEXT(ret_l, li1); + listitem_T *li3 = TV_LIST_ITEM_NEXT(ret_l, li2); + listitem_T *li4 = TV_LIST_ITEM_NEXT(ret_l, li3); + xfree(TV_LIST_ITEM_TV(li1)->vval.v_string); + + const size_t rd = (size_t)(regmatch.endp[0] - regmatch.startp[0]); + TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz( + (const char *)regmatch.startp[0], rd); + TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)( + regmatch.startp[0] - expr); + TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)( + regmatch.endp[0] - expr); + if (l != NULL) { + TV_LIST_ITEM_TV(li2)->vval.v_number = (varnumber_T)idx; + } + break; } - } else if (type == 3) { - int i; - - /* return list with matched string and submatches */ - for (i = 0; i < NSUBEXP; ++i) { - if (regmatch.endp[i] == NULL) { - tv_list_append_string(rettv->vval.v_list, NULL, 0); - } else { - tv_list_append_string(rettv->vval.v_list, - (const char *)regmatch.startp[i], - (regmatch.endp[i] - regmatch.startp[i])); + case kSomeMatchList: { + // Return list with matched string and submatches. + for (int i = 0; i < NSUBEXP; ++i) { + if (regmatch.endp[i] == NULL) { + tv_list_append_string(rettv->vval.v_list, NULL, 0); + } else { + tv_list_append_string(rettv->vval.v_list, + (const char *)regmatch.startp[i], + (regmatch.endp[i] - regmatch.startp[i])); + } } + break; } - } else if (type == 2) { - // Return matched string. - if (l != NULL) { - tv_copy(TV_LIST_ITEM_TV(li), rettv); - } else { - rettv->vval.v_string = (char_u *)xmemdupz( - (const char *)regmatch.startp[0], - (size_t)(regmatch.endp[0] - regmatch.startp[0])); + case kSomeMatchStr: { + // Return matched string. + if (l != NULL) { + tv_copy(TV_LIST_ITEM_TV(li), rettv); + } else { + rettv->vval.v_string = (char_u *)xmemdupz( + (const char *)regmatch.startp[0], + (size_t)(regmatch.endp[0] - regmatch.startp[0])); + } + break; } - } else if (l != NULL) { - rettv->vval.v_number = idx; - } else { - if (type != 0) { - rettv->vval.v_number = - (varnumber_T)(regmatch.startp[0] - str); - } else { - rettv->vval.v_number = - (varnumber_T)(regmatch.endp[0] - str); + case kSomeMatch: + case kSomeMatchEnd: { + if (l != NULL) { + rettv->vval.v_number = idx; + } else { + if (type == kSomeMatchEnd) { + rettv->vval.v_number = + (varnumber_T)(regmatch.startp[0] - str); + } else { + rettv->vval.v_number = + (varnumber_T)(regmatch.endp[0] - str); + } + rettv->vval.v_number += (varnumber_T)(str - expr); + } } - rettv->vval.v_number += (varnumber_T)(str - expr); } } vim_regfree(regmatch.regprog); } - if (type == 4 && l == NULL) { + if (type == kSomeMatchStrPos && l == NULL) { // matchstrpos() without a list: drop the second item list_T *const ret_l = rettv->vval.v_list; tv_list_item_remove(ret_l, TV_LIST_ITEM_NEXT(ret_l, tv_list_first(ret_l))); @@ -12383,7 +12414,7 @@ theend: */ static void f_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - find_some_match(argvars, rettv, 1); + find_some_match(argvars, rettv, kSomeMatch); } /* @@ -12528,7 +12559,7 @@ static void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - find_some_match(argvars, rettv, 0); + find_some_match(argvars, rettv, kSomeMatchEnd); } /* @@ -12536,7 +12567,7 @@ static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - find_some_match(argvars, rettv, 3); + find_some_match(argvars, rettv, kSomeMatchList); } /* @@ -12544,13 +12575,13 @@ static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_matchstr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - find_some_match(argvars, rettv, 2); + find_some_match(argvars, rettv, kSomeMatchStr); } /// "matchstrpos()" function static void f_matchstrpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - find_some_match(argvars, rettv, 4); + find_some_match(argvars, rettv, kSomeMatchStrPos); } /// Get maximal/minimal number value in a list or dictionary -- cgit From b6ee90a2433276175462b81106378009b4893e04 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 25 Dec 2017 01:44:44 +0300 Subject: eval: Refactor some potentially dangerous list appends --- src/nvim/eval.c | 71 ++++++++++++++++---------------------------------- src/nvim/eval/typval.c | 42 +++++++++++++++++++++++++++++ src/nvim/eval/typval.h | 22 ++++++---------- 3 files changed, 73 insertions(+), 62 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index d0f8ced18b..370d4f0c0b 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2421,9 +2421,11 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, if (TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li) == NULL) { // Need to add an empty item. tv_list_append_number(lp->ll_list, 0); - assert(TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li)); + // ll_li may have become invalid after append, don’t use it. + lp->ll_li = tv_list_last(lp->ll_list); // Valid again. + } else { + lp->ll_li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li); } - lp->ll_li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li); lp->ll_n1++; } if (ri != NULL) { @@ -4528,7 +4530,7 @@ eval_index ( item = tv_list_find(rettv->vval.v_list, n1); while (n1++ <= n2) { tv_list_append_tv(l, TV_LIST_ITEM_TV(item)); - item = TV_LIST_ITEM_NEXT(l, item); + item = TV_LIST_ITEM_NEXT(rettv->vval.v_list, item); } tv_clear(rettv); rettv->v_type = VAR_LIST; @@ -12529,12 +12531,12 @@ static void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tv_list_alloc_ret(rettv); - int id = tv_get_number(&argvars[0]); + const int id = tv_get_number(&argvars[0]); if (id >= 1 && id <= 3) { - matchitem_T *m; + matchitem_T *const m = (matchitem_T *)get_match(curwin, id); - if ((m = (matchitem_T *)get_match(curwin, id)) != NULL) { + if (m != NULL) { tv_list_append_string(rettv->vval.v_list, (const char *)syn_id2name(m->hlg_id), -1); tv_list_append_string(rettv->vval.v_list, (const char *)m->pattern, -1); @@ -15135,12 +15137,6 @@ static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; } -/// struct used in the array that's given to qsort() -typedef struct { - listitem_T *item; - int idx; -} sortItem_T; - /// struct storing information about current sort typedef struct { int item_compare_ic; @@ -15161,8 +15157,8 @@ static sortinfo_T *sortinfo = NULL; */ static int item_compare(const void *s1, const void *s2, bool keep_zero) { - sortItem_T *const si1 = (sortItem_T *)s1; - sortItem_T *const si2 = (sortItem_T *)s2; + ListSortItem *const si1 = (ListSortItem *)s1; + ListSortItem *const si2 = (ListSortItem *)s2; typval_T *const tv1 = TV_LIST_ITEM_TV(si1->item); typval_T *const tv2 = TV_LIST_ITEM_TV(si2->item); @@ -15256,7 +15252,7 @@ static int item_compare_not_keeping_zero(const void *s1, const void *s2) static int item_compare2(const void *s1, const void *s2, bool keep_zero) { - sortItem_T *si1, *si2; + ListSortItem *si1, *si2; int res; typval_T rettv; typval_T argv[3]; @@ -15269,8 +15265,8 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) return 0; } - si1 = (sortItem_T *)s1; - si2 = (sortItem_T *)s2; + si1 = (ListSortItem *)s1; + si2 = (ListSortItem *)s2; if (partial == NULL) { func_name = sortinfo->item_compare_func; @@ -15327,7 +15323,7 @@ static int item_compare2_not_keeping_zero(const void *s1, const void *s2) */ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) { - sortItem_T *ptrs; + ListSortItem *ptrs; long len; long i; @@ -15417,42 +15413,21 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) } /* Make an array with each entry pointing to an item in the List. */ - ptrs = xmalloc((size_t)(len * sizeof (sortItem_T))); + ptrs = xmalloc((size_t)(len * sizeof(ListSortItem))); - i = 0; if (sort) { - // sort(): ptrs will be the list to sort. - TV_LIST_ITER(l, li, { - ptrs[i].item = li; - ptrs[i].idx = i; - i++; - }); - info.item_compare_func_err = false; - // Test the compare function. - if ((info.item_compare_func != NULL - || info.item_compare_partial != NULL) - && item_compare2_not_keeping_zero(&ptrs[0], &ptrs[1]) - == ITEM_COMPARE_FAIL) { + tv_list_item_sort(l, ptrs, + ((info.item_compare_func == NULL + && info.item_compare_partial == NULL) + ? item_compare_not_keeping_zero + : item_compare2_not_keeping_zero), + &info.item_compare_func_err); + if (info.item_compare_func_err) { EMSG(_("E702: Sort compare function failed")); - } else { - // Sort the array with item pointers. - qsort(ptrs, (size_t)len, sizeof (sortItem_T), - (info.item_compare_func == NULL - && info.item_compare_partial == NULL ? - item_compare_not_keeping_zero : - item_compare2_not_keeping_zero)); - - if (!info.item_compare_func_err) { - // Clear the list and append the items in the sorted order. - tv_list_clear(l); - for (i = 0; i < len; i++) { - tv_list_append(l, ptrs[i].item); - } - } } } else { - int (*item_compare_func_ptr)(const void *, const void *); + ListSorter item_compare_func_ptr; // f_uniq(): ptrs will be a stack of items to remove. info.item_compare_func_err = false; diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index fba9e9c843..21bb84a945 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -757,6 +758,47 @@ void tv_list_reverse(list_T *const l) l->lv_idx = l->lv_len - l->lv_idx - 1; } +// FIXME Add unit tests for tv_list_item_sort(). + +/// Sort list using libc qsort +/// +/// @param[in,out] l List to sort, will be sorted in-place. +/// @param ptrs Preallocated array of items to sort, must have at least +/// tv_list_len(l) entries. Should not be initialized. +/// @param[in] item_compare_func Function used to compare list items. +/// @param errp Location where information about whether error occurred is +/// saved by item_compare_func. If boolean there appears to be +/// true list will not be modified. Must be initialized to false +/// by the caller. +void tv_list_item_sort(list_T *const l, ListSortItem *const ptrs, + const ListSorter item_compare_func, + bool *errp) + FUNC_ATTR_NONNULL_ARG(3, 4) +{ + const int len = tv_list_len(l); + if (len <= 1) { + return; + } + int i = 0; + TV_LIST_ITER(l, li, { + ptrs[i].item = li; + ptrs[i].idx = i; + i++; + }); + // Sort the array with item pointers. + qsort(ptrs, (size_t)len, sizeof(ListSortItem), item_compare_func); + if (!(*errp)) { + // Clear the list and append the items in the sorted order. + l->lv_first = NULL; + l->lv_last = NULL; + l->lv_idx_item = NULL; + l->lv_len = 0; + for (i = 0; i < len; i++) { + tv_list_append(l, ptrs[i].item); + } + } +} + //{{{2 Indexing/searching /// Locate item with a given index in a list and return it diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 2bce7bd6b2..c9a9a3e7e8 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -296,6 +296,14 @@ typedef struct list_stack_S { struct list_stack_S *prev; } list_stack_T; +/// Structure representing one list item, used for sort array. +typedef struct { + listitem_T *item; ///< Sorted list item. + int idx; ///< Sorted list item index. +} ListSortItem; + +typedef int (*ListSorter)(const void *, const void *); + // In a hashtab item "hi_key" points to "di_key" in a dictitem. // This avoids adding a pointer to the hashtab item. @@ -403,20 +411,6 @@ static inline list_T *tv_list_latest_copy(const list_T *const l) return l->lv_copylist; } -/// Clear the list without freeing anything at all -/// -/// For use in sort() which saves items to a separate array and readds them back -/// after sorting via a number of tv_list_append() calls. -/// -/// @param[out] l List to clear. -static inline void tv_list_clear(list_T *const l) -{ - l->lv_first = NULL; - l->lv_last = NULL; - l->lv_idx_item = NULL; - l->lv_len = 0; -} - static inline int tv_list_uidx(const list_T *const l, int n) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; -- cgit From fe60fa9faafd90cfe756c80119abae6f8906fc4b Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 16 Dec 2017 21:33:59 +0100 Subject: doc vim-patch:8.0.1206: no autocmd for entering or leaving the command line (commit a4f6cec7a31ff8dbfa089b9e22227afbeb951e9b) NA patches: vim-patch:8.0.0320: warning for unused variable with small build --- src/nvim/api/vim.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index c0daac8085..172f2ce18e 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -929,7 +929,7 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; /// starting column and ending column (latter exclusive: /// one should highlight region [start_col, end_col)). /// -/// @return AST: top-level dictionary holds keys +/// @return AST: top-level dictionary with these keys: /// /// "error": Dictionary with error, present only if parser saw some /// error. Contains the following keys: -- cgit From ac2f90f2e1d63e1aa16f377580c815059618fe94 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 26 Dec 2017 03:49:00 +0100 Subject: version.c: update --- src/nvim/version.c | 455 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 317 insertions(+), 138 deletions(-) (limited to 'src') diff --git a/src/nvim/version.c b/src/nvim/version.c index 8ab9fc1a4b..e35b803b4e 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -146,7 +146,7 @@ static const int included_patches[] = { // 1292, // 1291, // 1290, - // 1289, + 1289, // 1288, // 1287, // 1286, @@ -228,8 +228,187 @@ static const int included_patches[] = { // 1210, // 1209, // 1208, - // 1207, + 1207, 1206, + // 1205, + // 1204, + // 1203, + // 1202, + // 1201, + // 1200, + // 1199, + // 1198, + // 1197, + // 1196, + // 1195, + // 1194, + // 1193, + // 1192, + // 1191, + // 1190, + 1189, + // 1188, + // 1187, + // 1186, + // 1185, + // 1184, + // 1183, + // 1182, + // 1181, + // 1180, + // 1179, + // 1178, + // 1177, + // 1176, + // 1175, + // 1174, + // 1173, + // 1172, + // 1171, + // 1170, + // 1169, + // 1168, + // 1167, + // 1166, + // 1165, + // 1164, + // 1163, + // 1162, + // 1161, + // 1160, + // 1159, + // 1158, + // 1157, + // 1156, + // 1155, + // 1154, + // 1153, + // 1152, + // 1151, + // 1150, + // 1149, + // 1148, + // 1147, + // 1146, + // 1145, + // 1144, + // 1143, + // 1142, + // 1141, + // 1140, + // 1139, + // 1138, + // 1137, + // 1136, + // 1135, + // 1134, + // 1133, + // 1132, + // 1131, + // 1130, + // 1129, + // 1128, + // 1127, + // 1126, + // 1125, + // 1124, + // 1123, + // 1122, + // 1121, + // 1120, + // 1119, + // 1118, + // 1117, + // 1116, + // 1115, + // 1114, + // 1113, + // 1112, + // 1111, + // 1110, + // 1109, + 1108, + // 1107, + // 1106, + // 1105, + // 1104, + // 1103, + // 1102, + // 1101, + // 1100, + // 1099, + // 1098, + // 1097, + // 1096, + // 1095, + // 1094, + // 1093, + // 1092, + // 1091, + // 1090, + // 1089, + // 1088, + // 1087, + // 1086, + // 1085, + // 1084, + // 1083, + // 1082, + // 1081, + // 1080, + // 1079, + // 1078, + // 1077, + // 1076, + // 1075, + // 1074, + // 1073, + // 1072, + // 1071, + // 1070, + // 1069, + // 1068, + // 1067, + // 1066, + // 1065, + // 1064, + // 1063, + // 1062, + // 1061, + // 1060, + // 1059, + // 1058, + // 1057, + // 1056, + // 1055, + // 1054, + // 1053, + // 1052, + // 1051, + // 1050, + // 1049, + // 1048, + // 1047, + // 1046, + // 1045, + // 1044, + // 1043, + // 1042, + // 1041, + // 1040, + // 1039, + // 1038, + // 1037, + // 1036, + // 1035, + // 1034, + // 1033, + // 1032, + // 1031, + // 1030, + // 1029, + // 1028, + // 1027, // 1026, 1025, 1024, @@ -237,7 +416,7 @@ static const int included_patches[] = { // 1022, // 1021, // 1020, - // 1019, + 1019, // 1018, // 1017, // 1016, @@ -294,7 +473,7 @@ static const int included_patches[] = { // 965, // 964, // 963, - // 962, + 962, // 961, // 960, // 959, @@ -650,7 +829,7 @@ static const int included_patches[] = { // 609, // 608, 607, - // 606, + 606, 605, // 604, // 603, @@ -659,30 +838,30 @@ static const int included_patches[] = { // 600, // 599, // 598, - // 597, + 597, // 596, - // 595, + 595, // 594, // 593, // 592, // 591, - // 590, + 590, // 589, // 588, // 587, // 586, // 585, - // 584, + 584, // 583, // 582, // 581, - // 580, - // 579, + 580, + 579, // 578, // 577, // 576, // 575, - // 574, + 574, // 573, // 572, 571, @@ -691,7 +870,7 @@ static const int included_patches[] = { // 568, // 567, // 566, - // 565, + 565, // 564, // 563, // 562, @@ -720,7 +899,7 @@ static const int included_patches[] = { // 539, // 538, // 537, - // 536, + 536, // 535, // 534, // 533, @@ -739,7 +918,7 @@ static const int included_patches[] = { // 520, // 519, 518, - // 517, + 517, // 516, // 515, // 514, @@ -772,7 +951,7 @@ static const int included_patches[] = { 487, 486, 485, - // 484, + 484, 483, 482, // 481, @@ -836,7 +1015,7 @@ static const int included_patches[] = { // 423, // 422, // 421, - // 420, + 420, // 419, // 418, // 417, @@ -851,12 +1030,12 @@ static const int included_patches[] = { 408, 407, // 406, - // 405 NA - // 404, + 405, + 404, // 403, // 402, // 401, - // 400 NA + 400, // 399, // 398, // 397, @@ -877,7 +1056,7 @@ static const int included_patches[] = { // 382, // 381, // 380, - // 379, + 379, 378, 377, 376, @@ -936,7 +1115,7 @@ static const int included_patches[] = { // 323, 322, // 321, - // 320, + 320, 319, // 318, // 317, @@ -946,18 +1125,18 @@ static const int included_patches[] = { // 313, // 312, 311, - // 310, - // 309, + 310, + 309, 308, 307, 306, 305, // 304, // 303, - // 302 NA + 302, // 301, 300, - // 299, + 299, 298, 297, // 296, @@ -968,38 +1147,38 @@ static const int included_patches[] = { 291, 290, 289, - // 288 NA + 288, 287, // 286, - // 285 NA - // 284 NA + 285, + 284, 283, 282, - // 281 NA + 281, 280, - // 279 NA - // 278 NA - // 277 NA - // 276 NA + 279, + 278, + 277, + 276, 275, 274, - // 273 NA - // 272 NA - // 271 NA - // 270 NA - // 269 NA - // 268 NA - // 267 NA + 273, + 272, + 271, + 270, + 269, + 268, + 267, 266, // 265, // 264, // 263, // 262, // 261, - // 260 NA + 260, 259, 258, - // 257 NA + 257, // 256, // 255, // 254, @@ -1007,45 +1186,45 @@ static const int included_patches[] = { // 252, // 251, 250, - // 249 NA - // 248 NA + 249, + 248, 247, - // 246 NA + 246, 245, - // 244 NA + 244, 243, 242, - // 241 NA - // 240 NA - // 239 NA + 241, + 240, + 239, // 238, 237, // 236, 235, // 234, // 233, - // 232 NA + 232, // 231, // 230, 229, // 228, - // 227, + 227, 226, // 225, 224, 223, // 222, - // 221 NA + 221, // 220, 219, 218, - // 217 NA + 217, // 216, - // 215 NA + 215, // 214, - // 213 NA + 213, // 212, - // 211 NA + 211, // 210, 209, 208, @@ -1053,49 +1232,49 @@ static const int included_patches[] = { 206, 205, // 204, - // 203 NA + 203, // 202, // 201, // 200, - // 199 NA + 199, // 198, // 197, 196, 195, 194, - // 193 NA - // 192 NA - // 191 NA + 193, + 192, + 191, 190, 189, 188, - // 187 NA + 187, 186, // 185, // 184, - // 183 NA + 183, 182, 181, - // 180 NA + 180, 179, 178, 177, 176, // 175, 174, - // 173 NA + 173, 172, - // 171 NA - // 170 NA - // 169 NA + 171, + 170, + 169, 168, 167, - // 166 NA + 166, 165, 164, - // 163 NA - // 162 NA - // 161 NA + 163, + 162, + 161, // 160, 159, 158, @@ -1104,21 +1283,21 @@ static const int included_patches[] = { 155, // 154, // 153, - // 152 NA + 152, // 151, 150, 149, 148, 147, 146, - // 145 NA - // 144 NA + 145, + 144, 143, 142, - // 141 NA + 141, 140, - // 139 NA - // 138 NA + 139, + 138, 137, 136, 135, @@ -1126,137 +1305,137 @@ static const int included_patches[] = { 133, 132, 131, - // 130 NA - // 129 NA + 130, + 129, 128, 127, 126, 125, 124, - // 123 NA - // 122 NA + 123, + 122, 121, - // 120 NA + 120, 119, 118, - // 117 NA + 117, 116, - // 115 NA - // 114 NA - // 113 NA + 115, + 114, + 113, 112, 111, 110, - // 109 NA - // 108 NA - // 107 NA + 109, + 108, + 107, 106, - // 105 NA + 105, 104, - // 103 NA + 103, 102, 101, 100, 99, - // 98 NA - // 97 NA + 98, + 97, 96, - // 95 NA - // 94 NA - // 93 NA + 95, + 94, + 93, 92, 91, 90, - // 89 NA + 89, 88, - // 87 NA + 87, 86, 85, 84, 83, - // 82 NA + 82, 81, - // 80 NA + 80, 79, 78, - // 77 NA - // 76 NA + 77, + 76, 75, 74, 73, - // 72 NA - // 71 NA - // 70 NA + 72, + 71, + 70, 69, 68, - // 67 NA + 67, 66, - // 65 NA + 65, 64, - // 63 NA + 63, 62, - // 61 NA + 61, 60, - // 59 NA + 59, 58, 57, 56, - // 55 NA - // 54 NA + 55, + 54, 53, 52, - // 51 NA - // 50 NA + 51, + 50, 49, - // 48 NA + 48, 47, 46, - // 45 NA + 45, 44, 43, 42, 41, 40, - // 39 NA + 39, 38, 37, - // 36 NA + 36, 35, 34, 33, 32, 31, - // 30 NA - // 29 NA - // 28 NA - // 27 NA + 30, + 29, + 28, + 27, 26, 25, - // 24 NA + 24, 23, - // 22 NA - // 21 NA + 22, + 21, 20, 19, - // 18 NA + 18, 17, - // 16 NA - // 15 NA - // 14 NA - // 13 NA + 16, + 15, + 14, + 13, 12, - // 11 NA - // 10 NA - // 9 NA + 11, + 10, + 9, 8, - // 7 NA + 7, 6, - // 5 NA + 5, 4, 3, 2, 1, - 0 + 0, }; // clang-format on -- cgit From 2c436b33621a3064aae94e88db90095ae69aabc8 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 29 Dec 2017 16:56:14 +0100 Subject: Fix TabClose autocommand via close_windows Fixes https://github.com/neovim/neovim/issues/7781 --- src/nvim/window.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/window.c b/src/nvim/window.c index 5e85a9bede..bbdf40b874 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1741,6 +1741,11 @@ void close_windows(buf_T *buf, int keep_curwin) } else wp = wp->w_next; } + if (count != tabpage_index(NULL)) { + char_u prev_idx[NUMBUFLEN]; + sprintf((char *)prev_idx, "%i", tabpage_index(curtab)); + apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, curbuf); + } /* Also check windows in other tab pages. */ for (tp = first_tabpage; tp != NULL; tp = nexttp) { @@ -1757,15 +1762,16 @@ void close_windows(buf_T *buf, int keep_curwin) break; } } + if (count != tabpage_index(NULL)) { + char_u prev_idx[NUMBUFLEN]; + sprintf((char *)prev_idx, "%i", tabpage_index(tp)); + apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, curbuf); + } } } --RedrawingDisabled; - if (count != tabpage_index(NULL)) { - apply_autocmds(EVENT_TABCLOSED, NULL, NULL, false, curbuf); - } - redraw_tabline = true; if (h != tabline_height()) { shell_new_rows(); -- cgit From e84e1b68c13443efc3e0c39a16c6a6945fbde20b Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 29 Dec 2017 20:35:52 +0100 Subject: Move applying of TabClosed to win_close_othertab --- src/nvim/ex_docmd.c | 1 - src/nvim/window.c | 18 ++++++------------ 2 files changed, 6 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index f6a5f59676..a0ede4f3c5 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -6230,7 +6230,6 @@ void tabpage_close_other(tabpage_T *tp, int forceit) if (!valid_tabpage(tp) || tp->tp_firstwin == wp) break; } - apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, FALSE, curbuf); redraw_tabline = TRUE; if (h != tabline_height()) diff --git a/src/nvim/window.c b/src/nvim/window.c index bbdf40b874..81e986c5f2 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1724,7 +1724,6 @@ void close_windows(buf_T *buf, int keep_curwin) { tabpage_T *tp, *nexttp; int h = tabline_height(); - int count = tabpage_index(NULL); ++RedrawingDisabled; @@ -1741,11 +1740,6 @@ void close_windows(buf_T *buf, int keep_curwin) } else wp = wp->w_next; } - if (count != tabpage_index(NULL)) { - char_u prev_idx[NUMBUFLEN]; - sprintf((char *)prev_idx, "%i", tabpage_index(curtab)); - apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, curbuf); - } /* Also check windows in other tab pages. */ for (tp = first_tabpage; tp != NULL; tp = nexttp) { @@ -1762,11 +1756,6 @@ void close_windows(buf_T *buf, int keep_curwin) break; } } - if (count != tabpage_index(NULL)) { - char_u prev_idx[NUMBUFLEN]; - sprintf((char *)prev_idx, "%i", tabpage_index(tp)); - apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, curbuf); - } } } @@ -1854,7 +1843,6 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, // Since goto_tabpage_tp above did not trigger *Enter autocommands, do // that now. - apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, curbuf); apply_autocmds(EVENT_WINENTER, NULL, NULL, false, curbuf); apply_autocmds(EVENT_TABENTER, NULL, NULL, false, curbuf); if (old_curbuf != curbuf) { @@ -2114,6 +2102,10 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) /* When closing the last window in a tab page remove the tab page. */ if (tp->tp_firstwin == tp->tp_lastwin) { + // save index for tabclosed event + char_u prev_idx[NUMBUFLEN]; + sprintf((char *)prev_idx, "%i", tabpage_index(tp)); + if (tp == first_tabpage) first_tabpage = tp->tp_next; else { @@ -2127,6 +2119,8 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) ptp->tp_next = tp->tp_next; } free_tp = TRUE; + + apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, win->w_buffer); } /* Free the memory used for the window. */ -- cgit From 5dd2ca767f3163cb9d051a9a2676ac88f600ebad Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 29 Dec 2017 20:52:56 +0100 Subject: use snprintf and has_event --- src/nvim/window.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/window.c b/src/nvim/window.c index 81e986c5f2..2e1507c0ee 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -2102,9 +2102,10 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) /* When closing the last window in a tab page remove the tab page. */ if (tp->tp_firstwin == tp->tp_lastwin) { - // save index for tabclosed event char_u prev_idx[NUMBUFLEN]; - sprintf((char *)prev_idx, "%i", tabpage_index(tp)); + if (has_event(EVENT_TABCLOSED)) { + vim_snprintf((char *)prev_idx, NUMBUFLEN, "%i", tabpage_index(tp)); + } if (tp == first_tabpage) first_tabpage = tp->tp_next; @@ -2120,7 +2121,9 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) } free_tp = TRUE; - apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, win->w_buffer); + if (has_event(EVENT_TABCLOSED)) { + apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, win->w_buffer); + } } /* Free the memory used for the window. */ -- cgit From 697fb05c580ab01cfd97f84632e608e951b80cbf Mon Sep 17 00:00:00 2001 From: James McCoy Date: Sat, 30 Dec 2017 01:17:31 -0500 Subject: vim-patch:8.0.0608: cannot manipulate other than the current quickfix list Problem: Cannot manipulate other than the current quickfix list. Solution: Pass the list index to quickfix functions. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/a3921f48c6b31a035c80fda49925dd3b42df0dec --- src/nvim/quickfix.c | 67 +++++++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index f85009dca8..bf2993b320 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -1121,6 +1121,7 @@ qf_init_ext( } if (qf_add_entry(qi, + qi->qf_curlist, qi->qf_directory, (*fields.namebuf || qi->qf_directory) ? fields.namebuf : ((qi->qf_currfile && fields.valid) @@ -1182,12 +1183,12 @@ qf_init_end: return retval; } -static void qf_store_title(qf_info_T *qi, char_u *title) +static void qf_store_title(qf_info_T *qi, int qf_idx, char_u *title) { if (title != NULL) { char_u *p = xmalloc(STRLEN(title) + 2); - qi->qf_lists[qi->qf_curlist].qf_title = p; + qi->qf_lists[qf_idx].qf_title = p; sprintf((char *)p, ":%s", (char *)title); } } @@ -1217,7 +1218,7 @@ static void qf_new_list(qf_info_T *qi, char_u *qf_title) } else qi->qf_curlist = qi->qf_listcount++; memset(&qi->qf_lists[qi->qf_curlist], 0, (size_t)(sizeof(qf_list_T))); - qf_store_title(qi, qf_title); + qf_store_title(qi, qi->qf_curlist, qf_title); } /* @@ -1260,6 +1261,7 @@ void qf_free_all(win_T *wp) /// Add an entry to the end of the list of errors. /// /// @param qi quickfix list +/// @param qf_idx list index /// @param dir optional directory name /// @param fname file name or NULL /// @param bufnum buffer number or zero @@ -1273,9 +1275,10 @@ void qf_free_all(win_T *wp) /// @param valid valid entry /// /// @returns OK or FAIL. -static int qf_add_entry(qf_info_T *qi, char_u *dir, char_u *fname, int bufnum, - char_u *mesg, long lnum, int col, char_u vis_col, - char_u *pattern, int nr, char_u type, char_u valid) +static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, + int bufnum, char_u *mesg, long lnum, int col, + char_u vis_col, char_u *pattern, int nr, char_u type, + char_u valid) { qfline_T *qfp = xmalloc(sizeof(qfline_T)); qfline_T **lastp; // pointer to qf_last or NULL @@ -1306,12 +1309,12 @@ static int qf_add_entry(qf_info_T *qi, char_u *dir, char_u *fname, int bufnum, qfp->qf_type = (char_u)type; qfp->qf_valid = valid; - lastp = &qi->qf_lists[qi->qf_curlist].qf_last; - if (qi->qf_lists[qi->qf_curlist].qf_count == 0) { + lastp = &qi->qf_lists[qf_idx].qf_last; + if (qi->qf_lists[qf_idx].qf_count == 0) { /* first element in the list */ - qi->qf_lists[qi->qf_curlist].qf_start = qfp; - qi->qf_lists[qi->qf_curlist].qf_ptr = qfp; - qi->qf_lists[qi->qf_curlist].qf_index = 0; + qi->qf_lists[qf_idx].qf_start = qfp; + qi->qf_lists[qf_idx].qf_ptr = qfp; + qi->qf_lists[qf_idx].qf_index = 0; qfp->qf_prev = NULL; } else { assert(*lastp); @@ -1321,12 +1324,11 @@ static int qf_add_entry(qf_info_T *qi, char_u *dir, char_u *fname, int bufnum, qfp->qf_next = NULL; qfp->qf_cleared = false; *lastp = qfp; - qi->qf_lists[qi->qf_curlist].qf_count++; - if (qi->qf_lists[qi->qf_curlist].qf_index == 0 && qfp->qf_valid) { + qi->qf_lists[qf_idx].qf_count++; + if (qi->qf_lists[qf_idx].qf_index == 0 && qfp->qf_valid) { /* first valid entry */ - qi->qf_lists[qi->qf_curlist].qf_index = - qi->qf_lists[qi->qf_curlist].qf_count; - qi->qf_lists[qi->qf_curlist].qf_ptr = qfp; + qi->qf_lists[qf_idx].qf_index = qi->qf_lists[qf_idx].qf_count; + qi->qf_lists[qf_idx].qf_ptr = qfp; } return OK; @@ -1429,6 +1431,7 @@ void copy_loclist(win_T *from, win_T *to) i < from_qfl->qf_count && from_qfp != NULL; i++, from_qfp = from_qfp->qf_next) { if (qf_add_entry(to->w_llist, + to->w_llist->qf_curlist, NULL, NULL, 0, @@ -3704,6 +3707,7 @@ void ex_vimgrep(exarg_T *eap) // dummy buffer, unless duplicate_name is set, then the // buffer will be wiped out below. if (qf_add_entry(qi, + qi->qf_curlist, NULL, // dir fname, duplicate_name ? 0 : buf->b_fnum, @@ -4164,23 +4168,24 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) /// Add list of entries to quickfix/location list. Each list entry is /// a dictionary with item information. -static int qf_add_entries(qf_info_T *qi, list_T *list, char_u *title, - int action) +static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, + char_u *title, int action) { dict_T *d; qfline_T *old_last = NULL; int retval = OK; bool did_bufnr_emsg = false; - if (action == ' ' || qi->qf_curlist == qi->qf_listcount) { + if (action == ' ' || qf_idx == qi->qf_listcount) { // make place for a new list qf_new_list(qi, title); - } else if (action == 'a' && qi->qf_lists[qi->qf_curlist].qf_count > 0) { + qf_idx = qi->qf_curlist; + } else if (action == 'a' && qi->qf_lists[qf_idx].qf_count > 0) { // Adding to existing list, use last entry. - old_last = qi->qf_lists[qi->qf_curlist].qf_last; + old_last = qi->qf_lists[qf_idx].qf_last; } else if (action == 'r') { - qf_free(qi, qi->qf_curlist); - qf_store_title(qi, title); + qf_free(qi, qf_idx); + qf_store_title(qi, qf_idx, title); } TV_LIST_ITER_CONST(list, li, { @@ -4228,6 +4233,7 @@ static int qf_add_entries(qf_info_T *qi, list_T *list, char_u *title, } int status = qf_add_entry(qi, + qf_idx, NULL, // dir (char_u *)filename, bufnum, @@ -4250,16 +4256,16 @@ static int qf_add_entries(qf_info_T *qi, list_T *list, char_u *title, } }); - if (qi->qf_lists[qi->qf_curlist].qf_index == 0) { + if (qi->qf_lists[qf_idx].qf_index == 0) { // no valid entry - qi->qf_lists[qi->qf_curlist].qf_nonevalid = true; + qi->qf_lists[qf_idx].qf_nonevalid = true; } else { - qi->qf_lists[qi->qf_curlist].qf_nonevalid = false; + qi->qf_lists[qf_idx].qf_nonevalid = false; } if (action != 'a') { - qi->qf_lists[qi->qf_curlist].qf_ptr = qi->qf_lists[qi->qf_curlist].qf_start; - if (qi->qf_lists[qi->qf_curlist].qf_count > 0) { - qi->qf_lists[qi->qf_curlist].qf_index = 1; + qi->qf_lists[qf_idx].qf_ptr = qi->qf_lists[qf_idx].qf_start; + if (qi->qf_lists[qf_idx].qf_count > 0) { + qi->qf_lists[qf_idx].qf_index = 1; } } @@ -4400,7 +4406,7 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title, } else if (what != NULL) { retval = qf_set_properties(qi, what, action); } else { - retval = qf_add_entries(qi, list, title, action); + retval = qf_add_entries(qi, qi->qf_curlist, list, title, action); } return retval; @@ -4718,6 +4724,7 @@ void ex_helpgrep(exarg_T *eap) line[--l] = NUL; if (qf_add_entry(qi, + qi->qf_curlist, NULL, // dir fnames[fi], 0, -- cgit From caf94c72c51b3595676907c66e6259785a87420b Mon Sep 17 00:00:00 2001 From: James McCoy Date: Sat, 30 Dec 2017 07:56:12 -0500 Subject: lint --- src/nvim/quickfix.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index bf2993b320..120a449690 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -1186,10 +1186,11 @@ qf_init_end: static void qf_store_title(qf_info_T *qi, int qf_idx, char_u *title) { if (title != NULL) { - char_u *p = xmalloc(STRLEN(title) + 2); + size_t len = STRLEN(title) + 1; + char_u *p = xmallocz(len); qi->qf_lists[qf_idx].qf_title = p; - sprintf((char *)p, ":%s", (char *)title); + snprintf((char *)p, len + 1, ":%s", (char *)title); } } @@ -1311,7 +1312,7 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, lastp = &qi->qf_lists[qf_idx].qf_last; if (qi->qf_lists[qf_idx].qf_count == 0) { - /* first element in the list */ + // first element in the list qi->qf_lists[qf_idx].qf_start = qfp; qi->qf_lists[qf_idx].qf_ptr = qfp; qi->qf_lists[qf_idx].qf_index = 0; @@ -1326,7 +1327,7 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, *lastp = qfp; qi->qf_lists[qf_idx].qf_count++; if (qi->qf_lists[qf_idx].qf_index == 0 && qfp->qf_valid) { - /* first valid entry */ + // first valid entry qi->qf_lists[qf_idx].qf_index = qi->qf_lists[qf_idx].qf_count; qi->qf_lists[qf_idx].qf_ptr = qfp; } -- cgit From 46f432074e739a0eca9bb204e9c7769935669dbd Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 29 Dec 2017 21:13:21 +0100 Subject: tests: termclose_spec: fix flaky SIGTERM test #7787 Followup to https://github.com/neovim/neovim/pull/7217. Build failure: https://travis-ci.org/neovim/neovim/jobs/322930672#L2958. --- src/nvim/window.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/nvim/window.c b/src/nvim/window.c index 2e1507c0ee..b687781dfb 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -2104,25 +2104,26 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) if (tp->tp_firstwin == tp->tp_lastwin) { char_u prev_idx[NUMBUFLEN]; if (has_event(EVENT_TABCLOSED)) { - vim_snprintf((char *)prev_idx, NUMBUFLEN, "%i", tabpage_index(tp)); + vim_snprintf((char *)prev_idx, NUMBUFLEN, "%i", tabpage_index(tp)); } - if (tp == first_tabpage) + if (tp == first_tabpage) { first_tabpage = tp->tp_next; - else { + } else { for (ptp = first_tabpage; ptp != NULL && ptp->tp_next != tp; - ptp = ptp->tp_next) - ; + ptp = ptp->tp_next) { + // loop + } if (ptp == NULL) { internal_error("win_close_othertab()"); return; } ptp->tp_next = tp->tp_next; } - free_tp = TRUE; + free_tp = true; if (has_event(EVENT_TABCLOSED)) { - apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, win->w_buffer); + apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, win->w_buffer); } } -- cgit From c55cf5f4c181856d7ebb6697e8558d71279e7adb Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 31 Dec 2017 00:50:31 +0300 Subject: eval,lua/converter: Fix problems spotted in review --- src/nvim/eval.c | 60 ++++++++++++++++++++++++++---------------------- src/nvim/lua/converter.c | 2 +- 2 files changed, 33 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 370d4f0c0b..d2432e864f 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1515,7 +1515,6 @@ ex_let_vars ( ) { char_u *arg = arg_start; - int i; typval_T ltv; if (*arg != '[') { @@ -1534,17 +1533,17 @@ ex_let_vars ( } list_T *const l = tv->vval.v_list; - i = tv_list_len(l); - if (semicolon == 0 && var_count < i) { + const int len = tv_list_len(l); + if (semicolon == 0 && var_count < len) { EMSG(_("E687: Less targets than List items")); return FAIL; } - if (var_count - semicolon > i) { + if (var_count - semicolon > len) { EMSG(_("E688: More targets than List items")); return FAIL; } - // lt may actually be NULL, but it should fail with E688 or even earlier if - // you try to do ":let [] = v:_null_list". + // List l may actually be NULL, but it should fail with E688 or even earlier + // if you try to do ":let [] = v:_null_list". assert(l != NULL); listitem_T *item = tv_list_first(l); @@ -12220,10 +12219,11 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, // matchlist(): return empty list when there are no matches. case kSomeMatchList: { tv_list_alloc_ret(rettv); - FALLTHROUGH; + break; } // matchstrpos(): return ["", -1, -1, -1] case kSomeMatchStrPos: { + tv_list_alloc_ret(rettv); tv_list_append_string(rettv->vval.v_list, "", 0); tv_list_append_number(rettv->vval.v_list, -1); tv_list_append_number(rettv->vval.v_list, -1); @@ -12385,7 +12385,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, if (l != NULL) { rettv->vval.v_number = idx; } else { - if (type == kSomeMatchEnd) { + if (type == kSomeMatch) { rettv->vval.v_number = (varnumber_T)(regmatch.startp[0] - str); } else { @@ -12394,6 +12394,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, } rettv->vval.v_number += (varnumber_T)(str - expr); } + break; } } } @@ -13020,7 +13021,6 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) long prevlen = 0; /* length of data in prev */ long prevsize = 0; /* size of prev buffer */ long maxline = MAXLNUM; - long cnt = 0; char_u *p; /* position in buf */ char_u *start; /* start of current line */ @@ -13034,6 +13034,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } tv_list_alloc_ret(rettv); + list_T *const l = rettv->vval.v_list; // Always open the file in binary mode, library functions have a mind of // their own about CR-LF conversion. @@ -13043,7 +13044,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - while (cnt < maxline || maxline < 0) { + while (maxline < 0 || tv_list_len(l) < maxline) { readlen = (int)fread(buf, 1, io_size, fd); /* This for loop processes what was read, but is also entered at end @@ -13081,22 +13082,32 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) prevlen = prevsize = 0; } - tv_list_append_owned_tv(rettv->vval.v_list, (typval_T) { + tv_list_append_owned_tv(l, (typval_T) { .v_type = VAR_STRING, .v_lock = VAR_UNLOCKED, .vval.v_string = s, }); - start = p + 1; /* step over newline */ - if ((++cnt >= maxline && maxline >= 0) || readlen <= 0) + start = p + 1; // Step over newline. + if (maxline < 0) { + if (tv_list_len(l) > -maxline) { + assert(tv_list_len(l) == 1 + (-maxline)); + tv_list_item_remove(l, tv_list_first(l)); + } + } else if (tv_list_len(l) >= maxline) { + assert(tv_list_len(l) == maxline); break; - } else if (*p == NUL) + } + if (readlen <= 0) { + break; + } + } else if (*p == NUL) { *p = '\n'; - /* Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this - * when finding the BF and check the previous two bytes. */ - else if (*p == 0xbf && enc_utf8 && !binary) { - /* Find the two bytes before the 0xbf. If p is at buf, or buf - * + 1, these may be in the "prev" string. */ + // Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this + // when finding the BF and check the previous two bytes. + } else if (*p == 0xbf && !binary) { + // Find the two bytes before the 0xbf. If p is at buf, or buf + 1, + // these may be in the "prev" string. char_u back1 = p >= buf + 1 ? p[-1] : prevlen >= 1 ? prev[prevlen - 1] : NUL; char_u back2 = p >= buf + 2 ? p[-2] @@ -13130,8 +13141,9 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } /* for */ - if ((cnt >= maxline && maxline >= 0) || readlen <= 0) + if ((maxline >= 0 && tv_list_len(l) >= maxline) || readlen <= 0) { break; + } if (start < p) { /* There's part of a line in buf, store it in "prev". */ if (p - start + prevlen >= prevsize) { @@ -13155,14 +13167,6 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } /* while */ - // For a negative line count use only the lines at the end of the file, - // free the rest. - if (maxline < -tv_list_len(rettv->vval.v_list)) { - listitem_T *const first_li = tv_list_find(rettv->vval.v_list, maxline); - listitem_T *const last_li = tv_list_last(rettv->vval.v_list); - tv_list_remove_items(rettv->vval.v_list, first_li, last_li); - } - xfree(prev); fclose(fd); } diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 89fedae73a..61cb428923 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -213,7 +213,6 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) if (cur.special) { list_T *const kv_pair = tv_list_alloc(); - tv_list_append_list(cur.tv->vval.v_list, kv_pair); typval_T s_tv = decode_string(s, len, kTrue, false, false); if (s_tv.v_type == VAR_UNKNOWN) { ret = false; @@ -227,6 +226,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) .v_type = VAR_UNKNOWN, }); kv_push(stack, cur); + tv_list_append_list(cur.tv->vval.v_list, kv_pair); cur = (TVPopStackItem) { .tv = TV_LIST_ITEM_TV(tv_list_last(kv_pair)), .container = false, -- cgit From 8ac7c23b7dd7c435fb80315921e3704c8e0a7448 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 31 Dec 2017 00:57:17 +0300 Subject: eval: Fix linter errors --- src/nvim/eval.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index d2432e864f..33f8ffb738 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -12358,7 +12358,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, } case kSomeMatchList: { // Return list with matched string and submatches. - for (int i = 0; i < NSUBEXP; ++i) { + for (int i = 0; i < NSUBEXP; i++) { if (regmatch.endp[i] == NULL) { tv_list_append_string(rettv->vval.v_list, NULL, 0); } else { @@ -13021,8 +13021,6 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) long prevlen = 0; /* length of data in prev */ long prevsize = 0; /* size of prev buffer */ long maxline = MAXLNUM; - char_u *p; /* position in buf */ - char_u *start; /* start of current line */ if (argvars[1].v_type != VAR_UNKNOWN) { if (strcmp(tv_get_string(&argvars[1]), "b") == 0) { @@ -13047,14 +13045,16 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) while (maxline < 0 || tv_list_len(l) < maxline) { readlen = (int)fread(buf, 1, io_size, fd); - /* This for loop processes what was read, but is also entered at end - * of file so that either: - * - an incomplete line gets written - * - a "binary" file gets an empty line at the end if it ends in a - * newline. */ + // This for loop processes what was read, but is also entered at end + // of file so that either: + // - an incomplete line gets written + // - a "binary" file gets an empty line at the end if it ends in a + // newline. + char_u *p; // Position in buf. + char_u *start; // Start of current line. for (p = buf, start = buf; p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary)); - ++p) { + p++) { if (*p == '\n' || readlen <= 0) { char_u *s = NULL; size_t len = p - start; @@ -15416,7 +15416,7 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) } } - /* Make an array with each entry pointing to an item in the List. */ + // Make an array with each entry pointing to an item in the List. ptrs = xmalloc((size_t)(len * sizeof(ListSortItem))); if (sort) { -- cgit From 6742fd8aead06e45f19b59222f96ccdcb1748e4c Mon Sep 17 00:00:00 2001 From: James McCoy Date: Sat, 30 Dec 2017 23:39:51 -0500 Subject: vim-patch:8.0.0634: cannot easily get to the last quickfix list Problem: Cannot easily get to the last quickfix list. Solution: Add "$" as a value for the "nr" argument of getqflist() and setqflist(). (Yegappan Lakshmanan) https://github.com/vim/vim/commit/875feea6ce223462d55543735143d747dcaf4287 --- src/nvim/quickfix.c | 47 ++++++++++++++++++++++++++++---------- src/nvim/testdir/test_quickfix.vim | 44 +++++++++++++++++++++++++++++++++-- 2 files changed, 77 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 120a449690..1d368bf87a 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4088,16 +4088,22 @@ enum { int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) { qf_info_T *qi = &ql_info; + dictitem_T *di; if (wp != NULL) { qi = GET_LOC_LIST(wp); if (qi == NULL) { + // If querying for the size of the location list, return 0 + if (((di = tv_dict_find(what, S_LEN("nr"))) != NULL) + && (di->di_tv.v_type == VAR_STRING) + && strequal((const char *)di->di_tv.vval.v_string, "$")) { + return tv_dict_add_nr(retdict, S_LEN("nr"), 0); + } return FAIL; } } int status = OK; - dictitem_T *di; int flags = QF_GETLIST_NONE; int qf_idx = qi->qf_curlist; // default is the current list @@ -4110,6 +4116,17 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { return FAIL; } + } else if (qi->qf_listcount == 0) { // stack is empty + return FAIL; + } + flags |= QF_GETLIST_NR; + } else if (di->di_tv.v_type == VAR_STRING + && strequal((const char *)di->di_tv.vval.v_string, "$")) { + // Get the last quickfix list number + if (qi->qf_listcount > 0) { + qf_idx = qi->qf_listcount - 1; + } else { + qf_idx = -1; // Quickfix stack is empty } flags |= QF_GETLIST_NR; } else { @@ -4117,20 +4134,22 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) } } - if (tv_dict_find(what, S_LEN("all")) != NULL) { - flags |= QF_GETLIST_ALL; - } + if (qf_idx != -1) { + if (tv_dict_find(what, S_LEN("all")) != NULL) { + flags |= QF_GETLIST_ALL; + } - if (tv_dict_find(what, S_LEN("title")) != NULL) { - flags |= QF_GETLIST_TITLE; - } + if (tv_dict_find(what, S_LEN("title")) != NULL) { + flags |= QF_GETLIST_TITLE; + } - if (tv_dict_find(what, S_LEN("winid")) != NULL) { - flags |= QF_GETLIST_WINID; - } + if (tv_dict_find(what, S_LEN("winid")) != NULL) { + flags |= QF_GETLIST_WINID; + } - if (tv_dict_find(what, S_LEN("context")) != NULL) { - flags |= QF_GETLIST_CONTEXT; + if (tv_dict_find(what, S_LEN("context")) != NULL) { + flags |= QF_GETLIST_CONTEXT; + } } if (flags & QF_GETLIST_TITLE) { @@ -4296,6 +4315,10 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action) if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { return FAIL; } + } else if (di->di_tv.v_type == VAR_STRING + && strequal((const char *)di->di_tv.vval.v_string, "$") + && qi->qf_listcount > 0) { + qf_idx = qi->qf_listcount - 1; } else { return FAIL; } diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 30023dddc9..828176e6be 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1632,12 +1632,12 @@ func XbottomTests(cchar) call assert_fails('lbottom', 'E776:') endif - call g:Xsetlist([{'filename': 'foo', 'lnum': 42}]) + call g:Xsetlist([{'filename': 'foo', 'lnum': 42}]) Xopen let wid = win_getid() call assert_equal(1, line('.')) wincmd w - call g:Xsetlist([{'filename': 'var', 'lnum': 24}], 'a') + call g:Xsetlist([{'filename': 'var', 'lnum': 24}], 'a') Xbottom call win_gotoid(wid) call assert_equal(2, line('.')) @@ -2102,3 +2102,43 @@ func Test_bufoverflow() set efm&vim endfunc +func Test_cclose_from_copen() + augroup QF_Test + au! + au FileType qf :cclose + augroup END + copen + augroup QF_Test + au! + augroup END + augroup! QF_Test +endfunc + +" Tests for getting the quickfix stack size +func XsizeTests(cchar) + call s:setup_commands(a:cchar) + + call g:Xsetlist([], 'f') + call assert_equal(0, g:Xgetlist({'nr':'$'}).nr) + call assert_equal(1, len(g:Xgetlist({'nr':'$', 'all':1}))) + call assert_equal(0, len(g:Xgetlist({'nr':0}))) + + Xexpr "File1:10:Line1" + Xexpr "File2:20:Line2" + Xexpr "File3:30:Line3" + Xolder | Xolder + call assert_equal(3, g:Xgetlist({'nr':'$'}).nr) + call g:Xsetlist([], 'f') + + Xexpr "File1:10:Line1" + Xexpr "File2:20:Line2" + Xexpr "File3:30:Line3" + Xolder | Xolder + call g:Xsetlist([], 'a', {'nr':'$', 'title':'Compiler'}) + call assert_equal('Compiler', g:Xgetlist({'nr':3, 'all':1}).title) +endfunc + +func Test_Qf_Size() + call XsizeTests('c') + call XsizeTests('l') +endfunc -- cgit From d0c4bd23f78ee00943725ea77a63f2a223dba66b Mon Sep 17 00:00:00 2001 From: James McCoy Date: Sun, 31 Dec 2017 00:36:35 -0500 Subject: vim-patch:8.0.0657: cannot get and set quickfix list items Problem: Cannot get and set quickfix list items. Solution: Add the "items" argument to getqflist() and setqflist(). (Yegappan Lakshmanan) https://github.com/vim/vim/commit/6a8958db259d4444da6e6956e54a6513c1af8860 --- src/nvim/quickfix.c | 81 +++++++++++++++++++++++++++++--------- src/nvim/testdir/test_quickfix.vim | 67 +++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 1d368bf87a..224e43008d 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -76,18 +76,25 @@ struct qfline_S { */ #define LISTCOUNT 10 +/// Quickfix/Location list definition +/// +/// Usually the list contains one or more entries. But an empty list can be +/// created using setqflist()/setloclist() with a title and/or user context +/// information and entries can be added later using setqflist()/setloclist(). typedef struct qf_list_S { - qfline_T *qf_start; // pointer to the first error - qfline_T *qf_last; // pointer to the last error - qfline_T *qf_ptr; // pointer to the current error - int qf_count; // number of errors (0 means no error list) - int qf_index; // current index in the error list - int qf_nonevalid; // TRUE if not a single valid entry found - char_u *qf_title; // title derived from the command that created - // the error list - typval_T *qf_ctx; // context set by setqflist/setloclist + qfline_T *qf_start; ///< pointer to the first error + qfline_T *qf_last; ///< pointer to the last error + qfline_T *qf_ptr; ///< pointer to the current error + int qf_count; ///< number of errors (0 means empty list) + int qf_index; ///< current index in the error list + int qf_nonevalid; ///< TRUE if not a single valid entry found + char_u *qf_title; ///< title derived from the command that created + ///< the error list or set by setqflist + typval_T *qf_ctx; ///< context set by setqflist/setloclist } qf_list_T; +/// Quickfix/Location list stack definition +/// Contains a list of quickfix/location lists (qf_list_T) struct qf_info_S { /* * Count of references to this list. Used only for location lists. @@ -1185,6 +1192,9 @@ qf_init_end: static void qf_store_title(qf_info_T *qi, int qf_idx, char_u *title) { + xfree(qi->qf_lists[qf_idx].qf_title); + qi->qf_lists[qf_idx].qf_title = NULL; + if (title != NULL) { size_t len = STRLEN(title) + 1; char_u *p = xmallocz(len); @@ -2396,8 +2406,9 @@ void qf_history(exarg_T *eap) } } -/// Free all the entries in the error list "idx". -static void qf_free(qf_info_T *qi, int idx) +/// Free all the entries in the error list "idx". Note that other information +/// associated with the list like context and title are not freed. +static void qf_free_items(qf_info_T *qi, int idx) { qfline_T *qfp; qfline_T *qfpnext; @@ -2421,12 +2432,9 @@ static void qf_free(qf_info_T *qi, int idx) qi->qf_lists[idx].qf_start = qfpnext; qi->qf_lists[idx].qf_count--; } - xfree(qi->qf_lists[idx].qf_title); + qi->qf_lists[idx].qf_start = NULL; qi->qf_lists[idx].qf_ptr = NULL; - qi->qf_lists[idx].qf_title = NULL; - tv_free(qi->qf_lists[idx].qf_ctx); - qi->qf_lists[idx].qf_ctx = NULL; qi->qf_lists[idx].qf_index = 0; qi->qf_lists[idx].qf_start = NULL; qi->qf_lists[idx].qf_last = NULL; @@ -2442,6 +2450,18 @@ static void qf_free(qf_info_T *qi, int idx) qi->qf_multiscan = false; } +/// Free error list "idx". Frees all the entries in the quickfix list, +/// associated context information and the title. +static void qf_free(qf_info_T *qi, int idx) +{ + qf_free_items(qi, idx); + + xfree(qi->qf_lists[idx].qf_title); + qi->qf_lists[idx].qf_title = NULL; + tv_free(qi->qf_lists[idx].qf_ctx); + qi->qf_lists[idx].qf_ctx = NULL; +} + /* * qf_mark_adjust: adjust marks */ @@ -4150,6 +4170,10 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) if (tv_dict_find(what, S_LEN("context")) != NULL) { flags |= QF_GETLIST_CONTEXT; } + + if (tv_dict_find(what, S_LEN("items")) != NULL) { + flags |= QF_GETLIST_ITEMS; + } } if (flags & QF_GETLIST_TITLE) { @@ -4168,6 +4192,11 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) status = tv_dict_add_nr(retdict, S_LEN("winid"), win->handle); } } + if ((status == OK) && (flags & QF_GETLIST_ITEMS)) { + list_T *l = tv_list_alloc(); + (void)get_errorlist(wp, qf_idx, l); + tv_dict_add_list(retdict, S_LEN("items"), l); + } if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) { if (qi->qf_lists[qf_idx].qf_ctx != NULL) { @@ -4204,7 +4233,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, // Adding to existing list, use last entry. old_last = qi->qf_lists[qf_idx].qf_last; } else if (action == 'r') { - qf_free(qi, qf_idx); + qf_free_items(qi, qf_idx); qf_store_title(qi, qf_idx, title); } @@ -4312,17 +4341,24 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action) if (di->di_tv.vval.v_number != 0) { qf_idx = (int)di->di_tv.vval.v_number - 1; } - if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { + + if ((action == ' ' || action == 'a') && qf_idx == qi->qf_listcount) { + // When creating a new list, accept qf_idx pointing to the next + // non-available list + newlist = true; + } else if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { return FAIL; + } else { + newlist = false; // use the specified list } } else if (di->di_tv.v_type == VAR_STRING && strequal((const char *)di->di_tv.vval.v_string, "$") && qi->qf_listcount > 0) { qf_idx = qi->qf_listcount - 1; + newlist = false; } else { return FAIL; } - newlist = false; // use the specified list } if (newlist) { @@ -4341,6 +4377,15 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action) retval = OK; } } + if ((di = tv_dict_find(what, S_LEN("items"))) != NULL) { + if (di->di_tv.v_type == VAR_LIST) { + char_u *title_save = vim_strsave(qi->qf_lists[qf_idx].qf_title); + + retval = qf_add_entries(qi, qf_idx, di->di_tv.vval.v_list, + title_save, action == ' ' ? 'a' : action); + xfree(title_save); + } + } if ((di = tv_dict_find(what, S_LEN("context"))) != NULL) { tv_free(qi->qf_lists[qf_idx].qf_ctx); diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 828176e6be..95a7d29089 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1817,6 +1817,73 @@ func Xproperty_tests(cchar) call test_garbagecollect_now() let m = g:Xgetlist({'context' : 1}) call assert_equal(["red", "blue", "green"], m.context) + + " Test for setting/getting items + Xexpr "" + let qfprev = g:Xgetlist({'nr':0}) + call g:Xsetlist([], ' ', {'title':'Green', + \ 'items' : [{'filename':'F1', 'lnum':10}]}) + let qfcur = g:Xgetlist({'nr':0}) + call assert_true(qfcur.nr == qfprev.nr + 1) + let l = g:Xgetlist({'items':1}) + call assert_equal('F1', bufname(l.items[0].bufnr)) + call assert_equal(10, l.items[0].lnum) + call g:Xsetlist([], 'a', {'items' : [{'filename':'F2', 'lnum':20}, + \ {'filename':'F2', 'lnum':30}]}) + let l = g:Xgetlist({'items':1}) + call assert_equal('F2', bufname(l.items[2].bufnr)) + call assert_equal(30, l.items[2].lnum) + call g:Xsetlist([], 'r', {'items' : [{'filename':'F3', 'lnum':40}]}) + let l = g:Xgetlist({'items':1}) + call assert_equal('F3', bufname(l.items[0].bufnr)) + call assert_equal(40, l.items[0].lnum) + call g:Xsetlist([], 'r', {'items' : []}) + let l = g:Xgetlist({'items':1}) + call assert_equal(0, len(l.items)) + + " Save and restore the quickfix stack + call g:Xsetlist([], 'f') + call assert_equal(0, g:Xgetlist({'nr':'$'}).nr) + Xexpr "File1:10:Line1" + Xexpr "File2:20:Line2" + Xexpr "File3:30:Line3" + let last_qf = g:Xgetlist({'nr':'$'}).nr + call assert_equal(3, last_qf) + let qstack = [] + for i in range(1, last_qf) + let qstack = add(qstack, g:Xgetlist({'nr':i, 'all':1})) + endfor + call g:Xsetlist([], 'f') + for i in range(len(qstack)) + call g:Xsetlist([], ' ', qstack[i]) + endfor + call assert_equal(3, g:Xgetlist({'nr':'$'}).nr) + call assert_equal(10, g:Xgetlist({'nr':1, 'items':1}).items[0].lnum) + call assert_equal(20, g:Xgetlist({'nr':2, 'items':1}).items[0].lnum) + call assert_equal(30, g:Xgetlist({'nr':3, 'items':1}).items[0].lnum) + call g:Xsetlist([], 'f') + + " Swap two quickfix lists + Xexpr "File1:10:Line10" + Xexpr "File2:20:Line20" + Xexpr "File3:30:Line30" + call g:Xsetlist([], 'r', {'nr':1,'title':'Colors','context':['Colors']}) + call g:Xsetlist([], 'r', {'nr':2,'title':'Fruits','context':['Fruits']}) + let l1=g:Xgetlist({'nr':1,'all':1}) + let l2=g:Xgetlist({'nr':2,'all':1}) + let l1.nr=2 + let l2.nr=1 + call g:Xsetlist([], 'r', l1) + call g:Xsetlist([], 'r', l2) + let newl1=g:Xgetlist({'nr':1,'all':1}) + let newl2=g:Xgetlist({'nr':2,'all':1}) + call assert_equal(':Fruits', newl1.title) + call assert_equal(['Fruits'], newl1.context) + call assert_equal('Line20', newl1.items[0].text) + call assert_equal(':Colors', newl2.title) + call assert_equal(['Colors'], newl2.context) + call assert_equal('Line10', newl2.items[0].text) + call g:Xsetlist([], 'f') endfunc func Test_qf_property() -- cgit From 1d8c612f788b1a5c8d74814db4654462e0224831 Mon Sep 17 00:00:00 2001 From: KunMing Xie Date: Mon, 1 Jan 2018 23:08:26 +0800 Subject: vim-patch:8.0.0339: illegal memory access with vi' (#7794) Problem: Illegal memory access with vi' Solution: For quoted text objects bail out if the Visual area spans more than one line. https://github.com/vim/vim/commit/46522af72424c7fadfa7a4cbba3dd21b82d19131 --- src/nvim/search.c | 5 +++++ src/nvim/testdir/test_visual.vim | 7 +++++++ 2 files changed, 12 insertions(+) (limited to 'src') diff --git a/src/nvim/search.c b/src/nvim/search.c index 387614fd09..1eb1a25a19 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -3669,6 +3669,11 @@ current_quote ( /* Correct cursor when 'selection' is exclusive */ if (VIsual_active) { + // this only works within one line + if (VIsual.lnum != curwin->w_cursor.lnum) { + return false; + } + vis_bef_curs = lt(VIsual, curwin->w_cursor); if (*p_sel == 'e' && vis_bef_curs) dec_cursor(); diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index 1694adbd32..69607e642c 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -43,3 +43,10 @@ func Test_dotregister_paste() call assert_equal('hello world world', getline(1)) q! endfunc + +func Test_Visual_inner_quote() + new + normal oxX + normal vki' + bwipe! +endfunc -- cgit From 321a46b724ccb2e574875cf18c8d280f8516527c Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 1 Jan 2018 16:21:55 +0100 Subject: vim-patch: b:changedtick-related patches vim-patch:8.0.0334 vim-patch:8.0.0335 vim-patch:8.0.0343 vim-patch:8.0.0345 Problem: Can't access b:changedtick from a dict reference. Solution: Make changedtick a member of the b: dict. (inspired by neovim vim/vim#6112) https://github.com/vim/vim/commit/79518e2ace5fce7b9c49060e462a6e935dba0a84 vim-patch:8.0.0343: b:changedtick can be unlocked Problem: b:changedtick can be unlocked, even though it has no effect. (Nikolai Pavlov) Solution: Add a check and error E940. (closes #1496) vim-patch:8.0.0345: islocked('d.changedtick') does not work Problem: islocked('d.changedtick') does not work. Solution: Make it work. vim-patch:8.0.0335: functions test fails Problem: Functions test fails. Solution: Use the right buffer number. https://github.com/vim/vim/commit/507647da3151f7ffccac1b217936240daa79849c --- src/nvim/testdir/Makefile | 1 + src/nvim/testdir/test_alot.vim | 3 +- src/nvim/testdir/test_changedtick.vim | 57 +++++++++++++++++++++++++++++++++++ src/nvim/testdir/test_functions.vim | 42 ++++++++++++++++++++++++++ 4 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 src/nvim/testdir/test_changedtick.vim (limited to 'src') diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 1f8cf8a0e7..5af8dd20cd 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -42,6 +42,7 @@ SCRIPTS ?= $(SCRIPTS_DEFAULT) NEW_TESTS ?= \ test_autocmd.res \ test_bufwintabinfo.res \ + test_changedtick.res \ test_charsearch.res \ test_cmdline.res \ test_command_count.res \ diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index c1f6405579..5ce6799b99 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -2,9 +2,10 @@ " This makes testing go faster, since Vim doesn't need to restart. source test_assign.vim +source test_changedtick.vim source test_cursor_func.vim -source test_execute_func.vim source test_ex_undo.vim +source test_execute_func.vim source test_expr.vim source test_expr_utf8.vim source test_feedkeys.vim diff --git a/src/nvim/testdir/test_changedtick.vim b/src/nvim/testdir/test_changedtick.vim new file mode 100644 index 0000000000..3a91bb54aa --- /dev/null +++ b/src/nvim/testdir/test_changedtick.vim @@ -0,0 +1,57 @@ +" Tests for b:changedtick + +func Test_changedtick_increments() + new + " New buffer has an empty line, tick starts at 2. + let expected = 2 + call assert_equal(expected, b:changedtick) + call assert_equal(expected, b:['changedtick']) + call setline(1, 'hello') + let expected += 1 + call assert_equal(expected, b:changedtick) + call assert_equal(expected, b:['changedtick']) + undo + " Somehow undo counts as two changes. + let expected += 2 + call assert_equal(expected, b:changedtick) + call assert_equal(expected, b:['changedtick']) + bwipe! +endfunc + +func Test_changedtick_dict_entry() + let d = b: + call assert_equal(b:changedtick, d['changedtick']) +endfunc + +func Test_changedtick_bdel() + new + let bnr = bufnr('%') + let v = b:changedtick + bdel + " Delete counts as a change too. + call assert_equal(v + 1, getbufvar(bnr, 'changedtick')) +endfunc + +func Test_changedtick_islocked() + call assert_equal(0, islocked('b:changedtick')) + let d = b: + call assert_equal(0, islocked('d.changedtick')) +endfunc + +func Test_changedtick_fixed() + call assert_fails('let b:changedtick = 4', 'E46:') + call assert_fails('let b:["changedtick"] = 4', 'E46:') + + call assert_fails('lockvar b:changedtick', 'E940:') + call assert_fails('lockvar b:["changedtick"]', 'E46:') + call assert_fails('unlockvar b:changedtick', 'E940:') + call assert_fails('unlockvar b:["changedtick"]', 'E46:') + call assert_fails('unlet b:changedtick', 'E795:') + call assert_fails('unlet b:["changedtick"]', 'E46:') + + let d = b: + call assert_fails('lockvar d["changedtick"]', 'E46:') + call assert_fails('unlockvar d["changedtick"]', 'E46:') + call assert_fails('unlet d["changedtick"]', 'E46:') + +endfunc diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 0ce034b63e..398e9ab331 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -311,3 +311,45 @@ func! Test_mode() bwipe! iunmap endfunc + +func Test_getbufvar() + let bnr = bufnr('%') + let b:var_num = '1234' + let def_num = '5678' + call assert_equal('1234', getbufvar(bnr, 'var_num')) + call assert_equal('1234', getbufvar(bnr, 'var_num', def_num)) + + let bd = getbufvar(bnr, '') + call assert_equal('1234', bd['var_num']) + call assert_true(exists("bd['changedtick']")) + call assert_equal(2, len(bd)) + + let bd2 = getbufvar(bnr, '', def_num) + call assert_equal(bd, bd2) + + unlet b:var_num + call assert_equal(def_num, getbufvar(bnr, 'var_num', def_num)) + call assert_equal('', getbufvar(bnr, 'var_num')) + + let bd = getbufvar(bnr, '') + call assert_equal(1, len(bd)) + let bd = getbufvar(bnr, '',def_num) + call assert_equal(1, len(bd)) + + call assert_equal('', getbufvar(9999, '')) + call assert_equal(def_num, getbufvar(9999, '', def_num)) + unlet def_num + + call assert_equal(0, getbufvar(bnr, '&autoindent')) + call assert_equal(0, getbufvar(bnr, '&autoindent', 1)) + + " Open new window with forced option values + set fileformats=unix,dos + new ++ff=dos ++bin ++enc=iso-8859-2 + call assert_equal('dos', getbufvar(bufnr('%'), '&fileformat')) + call assert_equal(1, getbufvar(bufnr('%'), '&bin')) + call assert_equal('iso-8859-2', getbufvar(bufnr('%'), '&fenc')) + close + + set fileformats& +endfunc -- cgit From c5f9762b7e40d23e6a0cfc8f808761a084223859 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 1 Jan 2018 16:46:03 +0100 Subject: vim-patch:8.0.0336: flags of :substitute not sufficiently tested Problem: Flags of :substitute not sufficiently tested. Solution: Test up to two letter flag combinations. (James McCoy, closes vim/vim#1479) https://github.com/vim/vim/commit/8c50d50b6e19b755d7bad7b2724d14ead29364a7 --- src/nvim/testdir/test_substitute.vim | 67 ++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index e2b6de03c3..a3bc04dcd0 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -39,3 +39,70 @@ function! Test_multiline_subst() call assert_equal('xxxxx', getline(13)) enew! endfunction + +function! Test_substitute_variants() + " Validate that all the 2-/3-letter variants which embed the flags into the + " command name actually work. + enew! + let ln = 'Testing string' + let variants = [ + \ { 'cmd': ':s/Test/test/c', 'exp': 'testing string', 'prompt': 'y' }, + \ { 'cmd': ':s/foo/bar/ce', 'exp': ln }, + \ { 'cmd': ':s/t/r/cg', 'exp': 'Tesring srring', 'prompt': 'a' }, + \ { 'cmd': ':s/t/r/ci', 'exp': 'resting string', 'prompt': 'y' }, + \ { 'cmd': ':s/t/r/cI', 'exp': 'Tesring string', 'prompt': 'y' }, + \ { 'cmd': ':s/t/r/cn', 'exp': ln }, + \ { 'cmd': ':s/t/r/cp', 'exp': 'Tesring string', 'prompt': 'y' }, + \ { 'cmd': ':s/t/r/cl', 'exp': 'Tesring string', 'prompt': 'y' }, + \ { 'cmd': ':s/t/r/gc', 'exp': 'Tesring srring', 'prompt': 'a' }, + \ { 'cmd': ':s/foo/bar/ge', 'exp': ln }, + \ { 'cmd': ':s/t/r/g', 'exp': 'Tesring srring' }, + \ { 'cmd': ':s/t/r/gi', 'exp': 'resring srring' }, + \ { 'cmd': ':s/t/r/gI', 'exp': 'Tesring srring' }, + \ { 'cmd': ':s/t/r/gn', 'exp': ln }, + \ { 'cmd': ':s/t/r/gp', 'exp': 'Tesring srring' }, + \ { 'cmd': ':s/t/r/gl', 'exp': 'Tesring srring' }, + \ { 'cmd': ':s//r/gr', 'exp': 'Testr strr' }, + \ { 'cmd': ':s/t/r/ic', 'exp': 'resting string', 'prompt': 'y' }, + \ { 'cmd': ':s/foo/bar/ie', 'exp': ln }, + \ { 'cmd': ':s/t/r/i', 'exp': 'resting string' }, + \ { 'cmd': ':s/t/r/iI', 'exp': 'Tesring string' }, + \ { 'cmd': ':s/t/r/in', 'exp': ln }, + \ { 'cmd': ':s/t/r/ip', 'exp': 'resting string' }, + \ { 'cmd': ':s//r/ir', 'exp': 'Testr string' }, + \ { 'cmd': ':s/t/r/Ic', 'exp': 'Tesring string', 'prompt': 'y' }, + \ { 'cmd': ':s/foo/bar/Ie', 'exp': ln }, + \ { 'cmd': ':s/t/r/Ig', 'exp': 'Tesring srring' }, + \ { 'cmd': ':s/t/r/Ii', 'exp': 'resting string' }, + \ { 'cmd': ':s/t/r/I', 'exp': 'Tesring string' }, + \ { 'cmd': ':s/t/r/Ip', 'exp': 'Tesring string' }, + \ { 'cmd': ':s/t/r/Il', 'exp': 'Tesring string' }, + \ { 'cmd': ':s//r/Ir', 'exp': 'Testr string' }, + \ { 'cmd': ':s//r/rc', 'exp': 'Testr string', 'prompt': 'y' }, + \ { 'cmd': ':s//r/rg', 'exp': 'Testr strr' }, + \ { 'cmd': ':s//r/ri', 'exp': 'Testr string' }, + \ { 'cmd': ':s//r/rI', 'exp': 'Testr string' }, + \ { 'cmd': ':s//r/rn', 'exp': 'Testing string' }, + \ { 'cmd': ':s//r/rp', 'exp': 'Testr string' }, + \ { 'cmd': ':s//r/rl', 'exp': 'Testr string' }, + \ { 'cmd': ':s//r/r', 'exp': 'Testr string' }, + \] + + for var in variants + for run in [1, 2] + let cmd = var.cmd + if run == 2 && cmd =~ "/.*/.*/." + " Change :s/from/to/{flags} to :s{flags} + let cmd = substitute(cmd, '/.*/', '', '') + endif + call setline(1, [ln]) + let msg = printf('using "%s"', cmd) + let @/='ing' + let v:errmsg = '' + call feedkeys(cmd . "\" . get(var, 'prompt', ''), 'ntx') + " No error should exist (matters for testing e flag) + call assert_equal('', v:errmsg, msg) + call assert_equal(var.exp, getline('.'), msg) + endfor + endfor +endfunction -- cgit From 60716371e97dae916c6525e9ba840aae562069bb Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 1 Jan 2018 19:38:18 +0100 Subject: tui: support TERM=konsole-256color TERM=konsole-256color is recognized by ncurses. TERM=konsole-xterm might be more clever, but should not be necessary (for Nvim at least), we already special-case Konsole in various places. We may need to clean up some areas that currently assume Konsole always "pretends xterm" (`TERM=xterm-256color`), though I didn't find any such cases. ref #6403 ref https://github.com/neovim/neovim/issues/6403#issuecomment-348713346 --- src/nvim/tui/terminfo.c | 4 ++-- src/nvim/tui/tui.c | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c index 492c1c5e9c..2f07e83158 100644 --- a/src/nvim/tui/terminfo.c +++ b/src/nvim/tui/terminfo.c @@ -90,8 +90,8 @@ bool terminfo_is_term_family(const char *term, const char *family) size_t tlen = strlen(term); size_t flen = strlen(family); return tlen >= flen - && 0 == memcmp(term, family, flen) \ - // Per the commentary in terminfo, minus sign is the suffix separator. + && 0 == memcmp(term, family, flen) + // Per commentary in terminfo, minus is the only valid suffix separator. && ('\0' == term[flen] || '-' == term[flen]); } diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index dbe1222dc0..df5b41a64b 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -222,7 +222,8 @@ static void terminfo_start(UI *ui) const char *vte_version_env = os_getenv("VTE_VERSION"); long vte_version = vte_version_env ? strtol(vte_version_env, NULL, 10) : 0; bool iterm_env = termprg && strstr(termprg, "iTerm.app"); - bool konsole = os_getenv("KONSOLE_PROFILE_NAME") + bool konsole = terminfo_is_term_family(term, "konsole") + || os_getenv("KONSOLE_PROFILE_NAME") || os_getenv("KONSOLE_DBUS_SESSION"); patch_terminfo_bugs(data, term, colorterm, vte_version, konsole, iterm_env); -- cgit From b86e44aa358c2164bebddbb4ed9640b000323c6b Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 1 Jan 2018 23:11:31 -0500 Subject: vim-patch:8.0.0160: EMSG() is sometimes used where it should be IEMSG() Problem: EMSG() is sometimes used for internal errors. Solution: Change them to IEMSG(). (Dominique Pelle) And a few more. https://github.com/vim/vim/commit/de33011ec623fd562419dede6bf465b5b9881a20 --- src/nvim/if_cscope.c | 2 +- src/nvim/regexp_nfa.c | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index 654b4630c5..303a9bd92b 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -1370,7 +1370,7 @@ static char *cs_manage_matches(char **matches, char **contexts, cs_print_tags_priv(mp, cp, cnt); break; default: /* should not reach here */ - (void)EMSG(_("E570: fatal error in cs_manage_matches")); + IEMSG(_("E570: fatal error in cs_manage_matches")); return NULL; } diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 93ba9ce097..bc3ad57dde 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -1263,7 +1263,7 @@ static int nfa_regatom(void) rc_did_emsg = TRUE; return FAIL; } - EMSGN("INTERNAL: Unknown character class char: %" PRId64, c); + IEMSGN("INTERNAL: Unknown character class char: %" PRId64, c); return FAIL; } /* When '.' is followed by a composing char ignore the dot, so that @@ -4414,7 +4414,7 @@ static int check_char_class(int class, int c) default: /* should not be here :P */ - EMSGN(_(e_ill_char_class), class); + IEMSGN(_(e_ill_char_class), class); return FAIL; } return FAIL; @@ -5993,7 +5993,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, #ifdef REGEXP_DEBUG if (c < 0) - EMSGN("INTERNAL: Negative state char: %" PRId64, c); + IEMSGN("INTERNAL: Negative state char: %" PRId64, c); #endif result = (c == curc); @@ -6464,9 +6464,9 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags) if (postfix == NULL) { /* TODO: only give this error for debugging? */ if (post_ptr >= post_end) - EMSGN("Internal error: estimated max number " - "of states insufficient: %" PRId64, - post_end - post_start); + IEMSGN("Internal error: estimated max number " + "of states insufficient: %" PRId64, + post_end - post_start); goto fail; /* Cascaded (syntax?) error */ } -- cgit From 5f5011e8f67e6c26ec0c04d2f390bca392dba648 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 1 Jan 2018 23:17:31 -0500 Subject: lint --- src/nvim/if_cscope.c | 2 +- src/nvim/regexp_nfa.c | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index 303a9bd92b..773e29693c 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -1369,7 +1369,7 @@ static char *cs_manage_matches(char **matches, char **contexts, case Print: cs_print_tags_priv(mp, cp, cnt); break; - default: /* should not reach here */ + default: // should not reach here IEMSG(_("E570: fatal error in cs_manage_matches")); return NULL; } diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index bc3ad57dde..c520ef5fb9 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -4413,7 +4413,7 @@ static int check_char_class(int class, int c) break; default: - /* should not be here :P */ + // should not be here :P IEMSGN(_(e_ill_char_class), class); return FAIL; } @@ -5992,8 +5992,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, int c = t->state->c; #ifdef REGEXP_DEBUG - if (c < 0) + if (c < 0) { IEMSGN("INTERNAL: Negative state char: %" PRId64, c); + } #endif result = (c == curc); @@ -6462,12 +6463,13 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags) * (and count its size). */ postfix = re2post(); if (postfix == NULL) { - /* TODO: only give this error for debugging? */ - if (post_ptr >= post_end) + // TODO(vim): only give this error for debugging? + if (post_ptr >= post_end) { IEMSGN("Internal error: estimated max number " "of states insufficient: %" PRId64, post_end - post_start); - goto fail; /* Cascaded (syntax?) error */ + } + goto fail; // Cascaded (syntax?) error } /* -- cgit From d63c3d9d105b7d81ad397e784b33d3dec7073338 Mon Sep 17 00:00:00 2001 From: Paul Rigge Date: Sat, 30 Dec 2017 20:53:01 -0800 Subject: Add assertion in set_var_lval for null pointer. If the lval is a index into a list, li should not be null. --- src/nvim/eval.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 33f8ffb738..f30840041b 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2417,6 +2417,7 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == lp->ll_n1)) { break; } + assert(lp->ll_li != NULL); if (TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li) == NULL) { // Need to add an empty item. tv_list_append_number(lp->ll_list, 0); -- cgit From dea7a41138674b45e8cfd5c1d713d4048987c830 Mon Sep 17 00:00:00 2001 From: Paul Rigge Date: Sat, 30 Dec 2017 22:17:31 -0800 Subject: Add another const to tv_copy Clang static analyzer had trouble with filter_map in eval.c because tv_copy could, in principle, change the v_type of argvars[0]. It saw a potential null pointer going somewhere it shouldn't as a result. The from argument in tv_copy should be const, which also cleans up the static analyzer's complaint. --- src/nvim/eval/typval.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 21bb84a945..ac6c8c8aa6 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -2153,7 +2153,7 @@ void tv_free(typval_T *tv) /// /// @param[in] from Location to copy from. /// @param[out] to Location to copy to. -void tv_copy(typval_T *const from, typval_T *const to) +void tv_copy(const typval_T *const from, typval_T *const to) { to->v_type = from->v_type; to->v_lock = VAR_UNLOCKED; -- cgit From 65ec4ea62972e021065d5a5be83b04bb8da2561a Mon Sep 17 00:00:00 2001 From: Paul Rigge Date: Mon, 1 Jan 2018 20:20:57 -0800 Subject: Add assertions for watchers The clang static analyzer convinced itself lp->ll_newkey could be NULL. This adds an assertion that checks this doesn't actually happen, as well as a parallel assertion for di->di_key. --- src/nvim/eval.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index f30840041b..555a0506d9 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2480,9 +2480,11 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, notify: if (watched) { if (oldtv.v_type == VAR_UNKNOWN) { + assert(lp->ll_newkey != NULL); tv_dict_watcher_notify(dict, (char *)lp->ll_newkey, lp->ll_tv, NULL); } else { dictitem_T *di = lp->ll_di; + assert(di->di_key != NULL); tv_dict_watcher_notify(dict, (char *)di->di_key, lp->ll_tv, &oldtv); tv_clear(&oldtv); } -- cgit From 2e630d261157dbb902768ba8ef8346ee1eb41eb7 Mon Sep 17 00:00:00 2001 From: Paul Rigge Date: Mon, 1 Jan 2018 21:15:22 -0800 Subject: Refactor profiling check in call_user_func. do_profiling is a global variable, and as such the clang static analyzer has trouble making arguments about it. This commit does one comparison against do_profiling and puts the result in a local variable. This prevents errors from the value of do_profiling changing between comparisons. --- src/nvim/eval.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 555a0506d9..1c4dda0716 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -21254,15 +21254,17 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, } } + const bool do_profiling_yes = do_profiling == PROF_YES; + bool func_not_yet_profiling_but_should = - do_profiling == PROF_YES + do_profiling_yes && !fp->uf_profiling && has_profiling(FALSE, fp->uf_name, NULL); if (func_not_yet_profiling_but_should) func_do_profile(fp); bool func_or_func_caller_profiling = - do_profiling == PROF_YES + do_profiling_yes && (fp->uf_profiling || (fc->caller != NULL && fc->caller->func->uf_profiling)); @@ -21272,7 +21274,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, fp->uf_tm_children = profile_zero(); } - if (do_profiling == PROF_YES) { + if (do_profiling_yes) { script_prof_save(&wait_start); } @@ -21348,7 +21350,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, sourcing_name = save_sourcing_name; sourcing_lnum = save_sourcing_lnum; current_SID = save_current_SID; - if (do_profiling == PROF_YES) + if (do_profiling_yes) script_prof_restore(&wait_start); if (p_verbose >= 12 && sourcing_name != NULL) { -- cgit From 1bbe6d0a3015007bf67fc81caab86859a0e2d2bd Mon Sep 17 00:00:00 2001 From: Paul Rigge Date: Mon, 1 Jan 2018 22:45:35 -0800 Subject: Add null pointer assertions for do_unlet_var. --- src/nvim/eval.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 1c4dda0716..186fa0da76 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2900,6 +2900,7 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit) lp->ll_name_len))) { return FAIL; } else if (lp->ll_range) { + assert(lp->ll_list != NULL); // Delete a range of List items. listitem_T *const first_li = lp->ll_li; listitem_T *last_li = first_li; @@ -2926,6 +2927,7 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit) } else { // unlet a Dictionary item. dict_T *d = lp->ll_dict; + assert(d != NULL); dictitem_T *di = lp->ll_di; bool watched = tv_dict_is_watched(d); char *key = NULL; -- cgit From 88d643eb36693bcbbac24ec3d6f63a316a452402 Mon Sep 17 00:00:00 2001 From: Paul Rigge Date: Mon, 1 Jan 2018 23:22:13 -0800 Subject: Add null check when adding variable to dict. --- src/nvim/eval.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 186fa0da76..155b816b33 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -19016,6 +19016,9 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv, return; } + // Make sure dict is valid + assert(dict != NULL); + v = xmalloc(sizeof(dictitem_T) + strlen(varname)); STRCPY(v->di_key, varname); if (tv_dict_add(dict, v) == FAIL) { -- cgit From 0a881575dab1af7bf70b3c85385f71997efe8258 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 3 Jan 2018 19:15:40 +0100 Subject: vim-patch:8.0.0955: Test_existent_file() fails on some file systems Problem: Test_existent_file() fails on some file systems. Solution: Run the test again with a sleep when the test fails without a sleep. (James McCoy, closes vim/vim#1984) https://github.com/vim/vim/commit/82de3c2c036bc89c2d9bdea236e0a7f1208a5571 --- src/nvim/testdir/test_stat.vim | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_stat.vim b/src/nvim/testdir/test_stat.vim index dee0d13e84..1239fe9427 100644 --- a/src/nvim/testdir/test_stat.vim +++ b/src/nvim/testdir/test_stat.vim @@ -1,20 +1,41 @@ " Tests for stat functions and checktime -func Test_existent_file() +func CheckFileTime(doSleep) let fname = 'Xtest.tmp' + let result = 0 let ts = localtime() + if a:doSleep + sleep 1 + endif let fl = ['Hello World!'] call writefile(fl, fname) let tf = getftime(fname) + if a:doSleep + sleep 1 + endif let te = localtime() - call assert_true(ts <= tf && tf <= te) - call assert_equal(strlen(fl[0] . "\n"), getfsize(fname)) - call assert_equal('file', getftype(fname)) - call assert_equal('rw-', getfperm(fname)[0:2]) + let time_correct = (ts <= tf && tf <= te) + if a:doSleep || time_correct + call assert_true(time_correct) + call assert_equal(strlen(fl[0] . "\n"), getfsize(fname)) + call assert_equal('file', getftype(fname)) + call assert_equal('rw-', getfperm(fname)[0:2]) + let result = 1 + endif call delete(fname) + return result +endfunc + +func Test_existent_file() + " On some systems the file timestamp is rounded to a multiple of 2 seconds. + " We need to sleep to handle that, but that makes the test slow. First try + " without the sleep, and if it fails try again with the sleep. + if CheckFileTime(0) == 0 + call CheckFileTime(1) + endif endfunc func Test_existent_directory() -- cgit From 7c4bb23ff38a459911a742657572bc273e528ddf Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 3 Jan 2018 19:57:22 +0100 Subject: defaults: do :filetype stuff unless explicitly "off" Until now, the default `:filetype ...` setup was skipped if the user config touched `:filetype` in any way (including implicitly via `:syntax on`). No one needs that, and it's very confusing. Instead, proceed with `:filetype ... on` unless the user explicitly called `:filetype ... off`. closes #7765 --- src/nvim/ex_docmd.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index a0ede4f3c5..2fa8db6b82 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -9718,17 +9718,18 @@ static void ex_filetype(exarg_T *eap) EMSG2(_(e_invarg2), arg); } -/// Do ":filetype plugin indent on" if user did not already do some -/// permutation thereof. +/// Set all :filetype options ON if user did not explicitly set any to OFF. void filetype_maybe_enable(void) { - if (filetype_detect == kNone - && filetype_plugin == kNone - && filetype_indent == kNone) { + if (filetype_detect == kNone) { source_runtime((char_u *)FILETYPE_FILE, true); filetype_detect = kTrue; + } + if (filetype_plugin == kNone) { source_runtime((char_u *)FTPLUGIN_FILE, true); filetype_plugin = kTrue; + } + if (filetype_indent == kNone) { source_runtime((char_u *)INDENT_FILE, true); filetype_indent = kTrue; } -- cgit From c82e7c75fe4c127a2886e22891794a9f12bff530 Mon Sep 17 00:00:00 2001 From: Marvim the Paranoid Android Date: Sat, 6 Jan 2018 16:35:19 +0100 Subject: version.c: update [ci skip] (#7780) --- src/nvim/version.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/nvim/version.c b/src/nvim/version.c index e35b803b4e..996c06c902 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -480,7 +480,7 @@ static const int included_patches[] = { // 958, // 957, // 956, - // 955, + 955, // 954, // 953, // 952, @@ -778,7 +778,7 @@ static const int included_patches[] = { // 660, // 659, // 658, - // 657, + 657, // 656, // 655, // 654, @@ -794,14 +794,14 @@ static const int included_patches[] = { // 644, // 643, // 642, - // 641, + 641, // 640, // 639, // 638, // 637, // 636, // 635, - // 634, + 634, // 633, // 632, // 631, @@ -827,7 +827,7 @@ static const int included_patches[] = { // 611, // 610, // 609, - // 608, + 608, 607, 606, 605, @@ -844,7 +844,7 @@ static const int included_patches[] = { // 594, // 593, // 592, - // 591, + 591, 590, // 589, // 588, @@ -1096,12 +1096,12 @@ static const int included_patches[] = { // 342, 341, // 340, - // 339, + 339, // 338, // 337, - // 336, - // 335, - // 334, + 336, + 335, + 334, 333, // 332, 331, @@ -1120,8 +1120,8 @@ static const int included_patches[] = { // 318, // 317, // 316, - // 315, - // 314, + 315, + 314, // 313, // 312, 311, @@ -1275,7 +1275,7 @@ static const int included_patches[] = { 163, 162, 161, - // 160, + 160, 159, 158, 157, -- cgit From b61a305039a58ac0c2ff095b22e4eb6d7ccfc89e Mon Sep 17 00:00:00 2001 From: ckelsel Date: Mon, 8 Jan 2018 12:23:33 +0800 Subject: vim-patch:8.0.0351: no test for concatenating an empty string Problem: No test for concatenating an empty string that results from out of bounds indexing. Solution: Add a simple test. https://github.com/vim/vim/commit/218426896cbb2129aa4e85803ea97c5b57df1eaa --- src/nvim/testdir/test_expr.vim | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim index d32facaa98..ad967c528c 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -439,3 +439,8 @@ func Test_funcref() call assert_equal(2, OneByRef()) call assert_fails('echo funcref("{")', 'E475:') endfunc + +func Test_empty_concatenate() + call assert_equal('b', 'a'[4:0] . 'b') + call assert_equal('b', 'b' . 'a'[4:0]) +endfunc -- cgit From e182a8c8363a72fe3204a24f5f82cd129c8ad9ef Mon Sep 17 00:00:00 2001 From: ckelsel Date: Mon, 8 Jan 2018 12:27:09 +0800 Subject: vim-patch:8.0.0352: not easy to see when a typval needs to be cleared Problem: The condition for when a typval needs to be cleared is too complicated. Solution: Init the type to VAR_UNKNOWN and clear it always. https://github.com/vim/vim/commit/f06e5a549f42396be3478ccc1b5f03be64e1173e --- src/nvim/eval.c | 53 ++++++++++++++++++----------------------------------- 1 file changed, 18 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 73bfcd4291..f34b6db8d8 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2084,6 +2084,8 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, * Loop until no more [idx] or .key is following. */ lp->ll_tv = &v->di_tv; + var1.v_type = VAR_UNKNOWN; + var2.v_type = VAR_UNKNOWN; while (*p == '[' || (*p == '.' && lp->ll_tv->v_type == VAR_DICT)) { if (!(lp->ll_tv->v_type == VAR_LIST && lp->ll_tv->vval.v_list != NULL) && !(lp->ll_tv->v_type == VAR_DICT @@ -2134,9 +2136,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, if (!quiet) { EMSG(_(e_dictrange)); } - if (!empty1) { - tv_clear(&var1); - } + tv_clear(&var1); return NULL; } if (rettv != NULL && (rettv->v_type != VAR_LIST @@ -2144,9 +2144,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, if (!quiet) { emsgf(_("E709: [:] requires a List value")); } - if (!empty1) { - tv_clear(&var1); - } + tv_clear(&var1); return NULL; } p = skipwhite(p + 1); @@ -2155,16 +2153,12 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, } else { lp->ll_empty2 = false; if (eval1(&p, &var2, true) == FAIL) { // Recursive! - if (!empty1) { - tv_clear(&var1); - } + tv_clear(&var1); return NULL; } if (!tv_check_str(&var2)) { // Not a number or string. - if (!empty1) { - tv_clear(&var1); - } + tv_clear(&var1); tv_clear(&var2); return NULL; } @@ -2177,12 +2171,8 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, if (!quiet) { emsgf(_(e_missbrac)); } - if (!empty1) { - tv_clear(&var1); - } - if (lp->ll_range && !lp->ll_empty2) { - tv_clear(&var2); - } + tv_clear(&var1); + tv_clear(&var2); return NULL; } @@ -2236,9 +2226,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, if (!quiet) { emsgf(_(e_dictkey), key); } - if (len == -1) { - tv_clear(&var1); - } + tv_clear(&var1); return NULL; } if (len == -1) { @@ -2246,32 +2234,28 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, } else { lp->ll_newkey = vim_strnsave(key, len); } - if (len == -1) { - tv_clear(&var1); - } + tv_clear(&var1); break; // existing variable, need to check if it can be changed } else if (!(flags & GLV_READ_ONLY) && var_check_ro(lp->ll_di->di_flags, (const char *)name, (size_t)(p - name))) { - if (len == -1) { - tv_clear(&var1); - } + tv_clear(&var1); return NULL; } - if (len == -1) { - tv_clear(&var1); - } + tv_clear(&var1); lp->ll_tv = &lp->ll_di->di_tv; } else { // Get the number and item for the only or first index of the List. if (empty1) { lp->ll_n1 = 0; } else { - lp->ll_n1 = (long)tv_get_number(&var1); // Is number or string. - tv_clear(&var1); + // Is number or string. + lp->ll_n1 = (long)tv_get_number(&var1); } + tv_clear(&var1); + lp->ll_dict = NULL; lp->ll_list = lp->ll_tv->vval.v_list; lp->ll_li = tv_list_find(lp->ll_list, lp->ll_n1); @@ -2282,9 +2266,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, } } if (lp->ll_li == NULL) { - if (lp->ll_range && !lp->ll_empty2) { - tv_clear(&var2); - } + tv_clear(&var2); if (!quiet) { EMSGN(_(e_listidx), lp->ll_n1); } @@ -2326,6 +2308,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, } } + tv_clear(&var1); return p; } -- cgit From b58c17f8df95d7626aec57de36781daa277314ee Mon Sep 17 00:00:00 2001 From: dvejmz Date: Sat, 21 Oct 2017 16:48:47 +0100 Subject: vim-patch:8.0.0198 Problem: Some syntax arguments take effect even after "if 0". (Taylor Venable) Solution: Properly skip the syntax statements. Make "syn case" and "syn conceal" report the current state. Fix that "syn clear" didn't reset the conceal flag. Add tests for :syntax skipping properly. https://github.com/vim/vim/commit/de318c5c35ed0d65fd2a07196cb8acd5ee6d9bf8 --- src/nvim/syntax.c | 75 +++++++++++++++------- src/nvim/testdir/test_syntax.vim | 132 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 185 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index d1a5f0bd1c..2222f41c78 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -3019,12 +3019,19 @@ static void syn_cmd_conceal(exarg_T *eap, int syncing) return; next = skiptowhite(arg); - if (STRNICMP(arg, "on", 2) == 0 && next - arg == 2) + if (*arg == NUL) { + if (curwin->w_s->b_syn_conceal) { + MSG(_("syn conceal on")); + } else { + MSG(_("syn conceal off")); + } + } else if (STRNICMP(arg, "on", 2) == 0 && next - arg == 2) { curwin->w_s->b_syn_conceal = TRUE; - else if (STRNICMP(arg, "off", 3) == 0 && next - arg == 3) + } else if (STRNICMP(arg, "off", 3) == 0 && next - arg == 3) { curwin->w_s->b_syn_conceal = FALSE; - else + } else { EMSG2(_("E390: Illegal argument: %s"), arg); + } } /* @@ -3040,12 +3047,19 @@ static void syn_cmd_case(exarg_T *eap, int syncing) return; next = skiptowhite(arg); - if (STRNICMP(arg, "match", 5) == 0 && next - arg == 5) + if (*arg == NUL) { + if (curwin->w_s->b_syn_ic) { + MSG(_("syntax case ignore")); + } else { + MSG(_("syntax case match")); + } + } else if (STRNICMP(arg, "match", 5) == 0 && next - arg == 5) { curwin->w_s->b_syn_ic = FALSE; - else if (STRNICMP(arg, "ignore", 6) == 0 && next - arg == 6) + } else if (STRNICMP(arg, "ignore", 6) == 0 && next - arg == 6) { curwin->w_s->b_syn_ic = TRUE; - else + } else { EMSG2(_("E390: Illegal argument: %s"), arg); + } } /* @@ -3061,7 +3075,15 @@ static void syn_cmd_spell(exarg_T *eap, int syncing) return; next = skiptowhite(arg); - if (STRNICMP(arg, "toplevel", 8) == 0 && next - arg == 8) { + if (*arg == NUL) { + if (curwin->w_s->b_syn_spell == SYNSPL_TOP) { + MSG(_("syntax spell toplevel")); + } else if (curwin->w_s->b_syn_spell == SYNSPL_NOTOP) { + MSG(_("syntax spell notoplevel")); + } else { + MSG(_("syntax spell default")); + } + } else if (STRNICMP(arg, "toplevel", 8) == 0 && next - arg == 8) { curwin->w_s->b_syn_spell = SYNSPL_TOP; } else if (STRNICMP(arg, "notoplevel", 10) == 0 && next - arg == 10) { curwin->w_s->b_syn_spell = SYNSPL_NOTOP; @@ -3125,6 +3147,7 @@ void syntax_clear(synblock_T *block) block->b_syn_ic = FALSE; /* Use case, by default */ block->b_syn_spell = SYNSPL_DEFAULT; /* default spell checking */ block->b_syn_containedin = FALSE; + block->b_syn_conceal = FALSE; /* free the keywords */ clear_keywtab(&block->b_keywtab); @@ -4004,7 +4027,8 @@ static char_u * get_syn_options ( char_u *arg, /* next argument to be checked */ syn_opt_arg_T *opt, /* various things */ - int *conceal_char + int *conceal_char, + int skip /* TRUE if skipping over command */ ) { char_u *gname_start, *gname; @@ -4080,13 +4104,13 @@ get_syn_options ( EMSG(_("E395: contains argument not accepted here")); return NULL; } - if (get_id_list(&arg, 8, &opt->cont_list) == FAIL) + if (get_id_list(&arg, 8, &opt->cont_list, skip) == FAIL) return NULL; } else if (flagtab[fidx].argtype == 2) { - if (get_id_list(&arg, 11, &opt->cont_in_list) == FAIL) + if (get_id_list(&arg, 11, &opt->cont_in_list, skip) == FAIL) return NULL; } else if (flagtab[fidx].argtype == 3) { - if (get_id_list(&arg, 9, &opt->next_list) == FAIL) + if (get_id_list(&arg, 9, &opt->next_list, skip) == FAIL) return NULL; } else if (flagtab[fidx].argtype == 11 && arg[5] == '=') { /* cchar=? */ @@ -4257,7 +4281,11 @@ static void syn_cmd_keyword(exarg_T *eap, int syncing) rest = get_group_name(arg, &group_name_end); if (rest != NULL) { - syn_id = syn_check_group(arg, (int)(group_name_end - arg)); + if (eap->skip) { + syn_id = -1; + } else { + syn_id = syn_check_group(arg, (int)(group_name_end - arg)); + } if (syn_id != 0) { // Allocate a buffer, for removing backslashes in the keyword. keyword_copy = xmalloc(STRLEN(rest) + 1); @@ -4276,7 +4304,7 @@ static void syn_cmd_keyword(exarg_T *eap, int syncing) cnt = 0; p = keyword_copy; for (; rest != NULL && !ends_excmd(*rest); rest = skipwhite(rest)) { - rest = get_syn_options(rest, &syn_opt_arg, &conceal_char); + rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip); if (rest == NULL || ends_excmd(*rest)) { break; } @@ -4375,7 +4403,7 @@ syn_cmd_match ( syn_opt_arg.cont_list = NULL; syn_opt_arg.cont_in_list = NULL; syn_opt_arg.next_list = NULL; - rest = get_syn_options(rest, &syn_opt_arg, &conceal_char); + rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip); /* get the pattern. */ init_syn_patterns(); @@ -4385,7 +4413,7 @@ syn_cmd_match ( syn_opt_arg.flags |= HL_HAS_EOL; /* Get options after the pattern */ - rest = get_syn_options(rest, &syn_opt_arg, &conceal_char); + rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip); if (rest != NULL) { /* all arguments are valid */ /* @@ -4502,7 +4530,7 @@ syn_cmd_region ( */ while (rest != NULL && !ends_excmd(*rest)) { /* Check for option arguments */ - rest = get_syn_options(rest, &syn_opt_arg, &conceal_char); + rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip); if (rest == NULL || ends_excmd(*rest)) break; @@ -4926,12 +4954,14 @@ static void syn_cmd_cluster(exarg_T *eap, int syncing) break; clstr_list = NULL; - if (get_id_list(&rest, opt_len, &clstr_list) == FAIL) { + if (get_id_list(&rest, opt_len, &clstr_list, eap->skip) == FAIL) { EMSG2(_(e_invarg2), rest); break; } - syn_combine_list(&SYN_CLSTR(curwin->w_s)[scl_id].scl_list, - &clstr_list, list_op); + if (scl_id >= 0) { + syn_combine_list(&SYN_CLSTR(curwin->w_s)[scl_id].scl_list, + &clstr_list, list_op); + } got_clstr = TRUE; } @@ -5181,8 +5211,9 @@ static int get_id_list ( char_u **arg, int keylen, /* length of keyword */ - short **list /* where to store the resulting list, if not + short **list, /* where to store the resulting list, if not NULL, the list is silently skipped! */ + int skip ) { char_u *p = NULL; @@ -5251,7 +5282,9 @@ get_id_list ( id = SYNID_CONTAINED; id += current_syn_inc_tag; } else if (name[1] == '@') { - id = syn_check_cluster(name + 2, (int)(end - p - 1)); + if (!skip) { + id = syn_check_cluster(name + 2, (int)(end - p - 1)); + } } else { /* * Handle full group name. diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 6c084dd2a7..64b3c14d73 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -157,4 +157,134 @@ func Test_syntax_completion() call feedkeys(":syn match \\\"\", 'tx') call assert_match('^"syn match Boolean Character ', @:) -endfunc \ No newline at end of file +endfunc + +func Test_syntax_arg_skipped() + syn clear + syntax case ignore + if 0 + syntax case match + endif + call assert_match('case ignore', execute('syntax case')) + + syn keyword Foo foo + call assert_match('Foo', execute('syntax')) + syn clear + call assert_match('case match', execute('syntax case')) + call assert_notmatch('Foo', execute('syntax')) + + if has('conceal') + syn clear + syntax conceal on + if 0 + syntax conceal off + endif + call assert_match('conceal on', execute('syntax conceal')) + syn clear + call assert_match('conceal off', execute('syntax conceal')) + endif + + syntax region Tar start=// + if 0 + syntax region NotTest start=// contains=@Spell + endif + call assert_match('Tar', execute('syntax')) + call assert_notmatch('NotTest', execute('syntax')) + call assert_notmatch('Spell', execute('syntax')) + + hi Foo ctermfg=blue + let a = execute('hi Foo') + if 0 + syntax rest + endif + call assert_equal(a, execute('hi Foo')) + + set ft=tags + syn off + if 0 + syntax enable + endif + call assert_match('No Syntax items defined', execute('syntax')) + syntax enable + call assert_match('tagComment', execute('syntax')) + set ft= + + syn clear + if 0 + syntax include @Spell nothing + endif + call assert_notmatch('Spell', execute('syntax')) + + syn clear + syn iskeyword 48-57,$,_ + call assert_match('48-57,$,_', execute('syntax iskeyword')) + if 0 + syn clear + syn iskeyword clear + endif + call assert_match('48-57,$,_', execute('syntax iskeyword')) + syn iskeyword clear + call assert_match('not set', execute('syntax iskeyword')) + syn iskeyword 48-57,$,_ + syn clear + call assert_match('not set', execute('syntax iskeyword')) + + syn clear + syn keyword Foo foo + if 0 + syn keyword NotAdded bar + endif + call assert_match('Foo', execute('syntax')) + call assert_notmatch('NotAdded', execute('highlight')) + + syn clear + syn keyword Foo foo + call assert_match('Foo', execute('syntax')) + call assert_match('Foo', execute('syntax list')) + call assert_notmatch('Foo', execute('if 0 | syntax | endif')) + call assert_notmatch('Foo', execute('if 0 | syntax list | endif')) + + syn clear + syn match Fopi /asdf/ + if 0 + syn match Fopx /asdf/ + endif + call assert_match('Fopi', execute('syntax')) + call assert_notmatch('Fopx', execute('syntax')) + + syn clear + syn spell toplevel + call assert_match('spell toplevel', execute('syntax spell')) + if 0 + syn spell notoplevel + endif + call assert_match('spell toplevel', execute('syntax spell')) + syn spell notoplevel + call assert_match('spell notoplevel', execute('syntax spell')) + syn spell default + call assert_match('spell default', execute('syntax spell')) + + syn clear + if 0 + syntax cluster Spell + endif + call assert_notmatch('Spell', execute('syntax')) + + syn clear + syn keyword Foo foo + syn sync ccomment + syn sync maxlines=5 + if 0 + syn sync maxlines=11 + endif + call assert_match('on C-style comments', execute('syntax sync')) + call assert_match('maximal 5 lines', execute('syntax sync')) + syn clear + syn keyword Foo foo + if 0 + syn sync ccomment + endif + call assert_notmatch('on C-style comments', execute('syntax sync')) + + syn clear +endfunc -- cgit From 889bc3c20cf308b2bda43358639b739fe987e1ec Mon Sep 17 00:00:00 2001 From: dvejmz Date: Mon, 8 Jan 2018 19:37:24 +0000 Subject: vim-patch:8.0.0200: some syntax arguments are not tested Problem: Some syntax arguments are not tested. Solution: Add more syntax command tests. https://github.com/vim/vim/commit/58f60ca2fcd2858faac84e386b3ccf5ced75084d --- src/nvim/testdir/test_syntax.vim | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 64b3c14d73..636bdb8a4d 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -184,6 +184,10 @@ func Test_syntax_arg_skipped() call assert_match('conceal off', execute('syntax conceal')) endif + syntax conceal on + syntax conceal off + call assert_match('conceal off', execute('syntax conceal')) + syntax region Tar start=// if 0 syntax region NotTest start=// contains=@Spell -- cgit From 7f70c5f7b7fd0c0241613f1cc189b5e26a7642dc Mon Sep 17 00:00:00 2001 From: dvejmz Date: Mon, 8 Jan 2018 21:37:12 +0000 Subject: vim-patch:8.0.0201: completion of highlight groups includes cleared names Problem: When completing a group name for a highlight or syntax command cleared groups are included. Solution: Skip groups that have been cleared. https://github.com/vim/vim/commit/d61e8aaae57bd66279def479462bf11c22ec2f1c --- src/nvim/syntax.c | 19 +++++++++++++++-- src/nvim/testdir/test_syntax.vim | 45 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 58 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 2222f41c78..b274e22caa 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -52,6 +52,7 @@ static bool did_syntax_onoff = false; struct hl_group { char_u *sg_name; ///< highlight group name char_u *sg_name_u; ///< uppercase of sg_name + int sg_cleared; ///< "hi clear" was used int sg_attr; ///< Screen attr @see ATTR_ENTRY int sg_link; ///< link to this highlight group ID int sg_set; ///< combination of flags in \ref SG_SET @@ -6490,6 +6491,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) HL_TABLE()[from_id - 1].sg_set |= SG_LINK; HL_TABLE()[from_id - 1].sg_link = to_id; HL_TABLE()[from_id - 1].sg_scriptID = current_SID; + HL_TABLE()[from_id - 1].sg_cleared = false; redraw_all_later(SOME_VALID); } } @@ -6872,6 +6874,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) error = true; break; } + HL_TABLE()[idx].sg_cleared = false; // When highlighting has been given for a group, don't link it. if (!init || !(HL_TABLE()[idx].sg_set & SG_LINK)) { @@ -6953,6 +6956,8 @@ static int hl_has_settings(int idx, int check_link) */ static void highlight_clear(int idx) { + HL_TABLE()[idx].sg_cleared = true; + HL_TABLE()[idx].sg_attr = 0; HL_TABLE()[idx].sg_cterm = 0; HL_TABLE()[idx].sg_cterm_bold = FALSE; @@ -7763,10 +7768,20 @@ const char *get_highlight_name(expand_T *const xp, const int idx) } else if (idx == highlight_ga.ga_len + include_none + include_default + 1 && include_link != 0) { return "clear"; - } else if (idx < 0 || idx >= highlight_ga.ga_len) { + } else if (idx < 0) { return NULL; } - return (const char *)HL_TABLE()[idx].sg_name; + + /* Items are never removed from the table, skip the ones that were cleared. + */ + int current_idx = idx; + while (current_idx < highlight_ga.ga_len && HL_TABLE()[current_idx].sg_cleared) { + ++current_idx; + } + if (current_idx >= highlight_ga.ga_len) { + return NULL; + } + return (const char *)HL_TABLE()[current_idx].sg_name; } color_name_table_T color_name_table[] = { diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 636bdb8a4d..39259fe680 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -152,6 +152,12 @@ func Test_syntax_completion() call feedkeys(":syn sync \\\"\", 'tx') call assert_equal('"syn sync ccomment clear fromstart linebreaks= linecont lines= match maxlines= minlines= region', @:) + " Check that clearing "Aap" avoids it showing up before Boolean. + hi Aap ctermfg=blue + call feedkeys(":syn list \\\"\", 'tx') + call assert_match('^"syn list Aap Boolean Character ', @:) + hi clear Aap + call feedkeys(":syn list \\\"\", 'tx') call assert_match('^"syn list Boolean Character ', @:) @@ -188,11 +194,11 @@ func Test_syntax_arg_skipped() syntax conceal off call assert_match('conceal off', execute('syntax conceal')) - syntax region Tar start=// + syntax region Bar start=// if 0 syntax region NotTest start=// contains=@Spell endif - call assert_match('Tar', execute('syntax')) + call assert_match('Bar', execute('syntax')) call assert_notmatch('NotTest', execute('syntax')) call assert_notmatch('Spell', execute('syntax')) @@ -202,6 +208,8 @@ func Test_syntax_arg_skipped() syntax rest endif call assert_equal(a, execute('hi Foo')) + hi clear Bar + hi clear Foo set ft=tags syn off @@ -283,8 +291,7 @@ func Test_syntax_arg_skipped() endif call assert_match('on C-style comments', execute('syntax sync')) call assert_match('maximal 5 lines', execute('syntax sync')) - syn clear - syn keyword Foo foo + syn sync clear if 0 syn sync ccomment endif @@ -292,3 +299,33 @@ func Test_syntax_arg_skipped() syn clear endfunc + +func Test_invalid_arg() + call assert_fails('syntax case asdf', 'E390:') + call assert_fails('syntax conceal asdf', 'E390:') + call assert_fails('syntax spell asdf', 'E390:') +endfunc + +func Test_syn_sync() + syntax region HereGroup start=/this/ end=/that/ + syntax sync match SyncHere grouphere HereGroup "pattern" + call assert_match('SyncHere', execute('syntax sync')) + syn sync clear + call assert_notmatch('SyncHere', execute('syntax sync')) + syn clear +endfunc + +func Test_syn_clear() + syntax keyword Foo foo + syntax keyword Bar tar + call assert_match('Foo', execute('syntax')) + call assert_match('Bar', execute('syntax')) + syn clear Foo + call assert_notmatch('Foo', execute('syntax')) + call assert_match('Bar', execute('syntax')) + syn clear Foo Bar + call assert_notmatch('Foo', execute('syntax')) + call assert_notmatch('Bar', execute('syntax')) + hi clear Foo + hi clear Bar +endfunc -- cgit From e888439f5589d00f2ac545e38a29924f91dad38e Mon Sep 17 00:00:00 2001 From: dvejmz Date: Mon, 8 Jan 2018 22:36:05 +0000 Subject: vim-patch:8.0.0202: no test for invalid syntax group name Problem: No test for invalid syntax group name. Solution: Add a test for group name error and warning. https://github.com/vim/vim/commit/4007ed4a5e8c34197078e9d5718bd1d4a429dd23 --- src/nvim/testdir/test_syntax.vim | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 39259fe680..6456ca2b23 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -329,3 +329,14 @@ func Test_syn_clear() hi clear Foo hi clear Bar endfunc + +func Test_invalid_name() + syn clear + syn keyword Nop yes + call assert_fails("syntax keyword Wr\x17ong bar", 'E669:') + syntax keyword @Wrong bar + call assert_match('W18:', execute('1messages')) + syn clear + hi clear Nop + hi clear @Wrong +endfunc -- cgit From b23fc444b52a7ea10ffce4eca01d9429859147c9 Mon Sep 17 00:00:00 2001 From: dvejmz Date: Mon, 8 Jan 2018 22:40:21 +0000 Subject: vim-patch:8.0.0204: compiler warns for uninitialized variable Problem: Compiler warns for uninitialized variable. (Tony Mechelynck) Solution: When skipping set "id" to -1. https://github.com/vim/vim/commit/eb46f8fa14a586779f55b1c7f1648f559618322e --- src/nvim/syntax.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index b274e22caa..cf68971f76 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -5283,7 +5283,9 @@ get_id_list ( id = SYNID_CONTAINED; id += current_syn_inc_tag; } else if (name[1] == '@') { - if (!skip) { + if (skip) { + id = -1; + } else { id = syn_check_cluster(name + 2, (int)(end - p - 1)); } } else { -- cgit From 34de6d33c9e4ea7b2c67319eafe8345db7fe0151 Mon Sep 17 00:00:00 2001 From: dvejmz Date: Tue, 9 Jan 2018 22:58:24 +0000 Subject: lint --- src/nvim/syntax.c | 73 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 38 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index cf68971f76..01d2728f3d 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -52,7 +52,7 @@ static bool did_syntax_onoff = false; struct hl_group { char_u *sg_name; ///< highlight group name char_u *sg_name_u; ///< uppercase of sg_name - int sg_cleared; ///< "hi clear" was used + int sg_cleared; ///< "hi clear" was used int sg_attr; ///< Screen attr @see ATTR_ENTRY int sg_link; ///< link to this highlight group ID int sg_set; ///< combination of flags in \ref SG_SET @@ -3027,9 +3027,9 @@ static void syn_cmd_conceal(exarg_T *eap, int syncing) MSG(_("syn conceal off")); } } else if (STRNICMP(arg, "on", 2) == 0 && next - arg == 2) { - curwin->w_s->b_syn_conceal = TRUE; + curwin->w_s->b_syn_conceal = true; } else if (STRNICMP(arg, "off", 3) == 0 && next - arg == 3) { - curwin->w_s->b_syn_conceal = FALSE; + curwin->w_s->b_syn_conceal = false; } else { EMSG2(_("E390: Illegal argument: %s"), arg); } @@ -3055,9 +3055,9 @@ static void syn_cmd_case(exarg_T *eap, int syncing) MSG(_("syntax case match")); } } else if (STRNICMP(arg, "match", 5) == 0 && next - arg == 5) { - curwin->w_s->b_syn_ic = FALSE; + curwin->w_s->b_syn_ic = false; } else if (STRNICMP(arg, "ignore", 6) == 0 && next - arg == 6) { - curwin->w_s->b_syn_ic = TRUE; + curwin->w_s->b_syn_ic = true; } else { EMSG2(_("E390: Illegal argument: %s"), arg); } @@ -3144,11 +3144,11 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing) */ void syntax_clear(synblock_T *block) { - block->b_syn_error = FALSE; /* clear previous error */ - block->b_syn_ic = FALSE; /* Use case, by default */ - block->b_syn_spell = SYNSPL_DEFAULT; /* default spell checking */ - block->b_syn_containedin = FALSE; - block->b_syn_conceal = FALSE; + block->b_syn_error = false; // clear previous error + block->b_syn_ic = false; // Use case, by default + block->b_syn_spell = SYNSPL_DEFAULT; // default spell checking + block->b_syn_containedin = false; + block->b_syn_conceal = false; /* free the keywords */ clear_keywtab(&block->b_keywtab); @@ -4025,11 +4025,11 @@ get_group_name ( * Return NULL for any error; */ static char_u * -get_syn_options ( - char_u *arg, /* next argument to be checked */ - syn_opt_arg_T *opt, /* various things */ +get_syn_options( + char_u *arg, // next argument to be checked + syn_opt_arg_T *opt, // various things int *conceal_char, - int skip /* TRUE if skipping over command */ + int skip // TRUE if skipping over command ) { char_u *gname_start, *gname; @@ -4105,14 +4105,17 @@ get_syn_options ( EMSG(_("E395: contains argument not accepted here")); return NULL; } - if (get_id_list(&arg, 8, &opt->cont_list, skip) == FAIL) + if (get_id_list(&arg, 8, &opt->cont_list, skip) == FAIL) { return NULL; + } } else if (flagtab[fidx].argtype == 2) { - if (get_id_list(&arg, 11, &opt->cont_in_list, skip) == FAIL) + if (get_id_list(&arg, 11, &opt->cont_in_list, skip) == FAIL) { return NULL; + } } else if (flagtab[fidx].argtype == 3) { - if (get_id_list(&arg, 9, &opt->next_list, skip) == FAIL) + if (get_id_list(&arg, 9, &opt->next_list, skip) == FAIL) { return NULL; + } } else if (flagtab[fidx].argtype == 11 && arg[5] == '=') { /* cchar=? */ if (has_mbyte) { @@ -4410,10 +4413,11 @@ syn_cmd_match ( init_syn_patterns(); memset(&item, 0, sizeof(item)); rest = get_syn_pattern(rest, &item); - if (vim_regcomp_had_eol() && !(syn_opt_arg.flags & HL_EXCLUDENL)) + if (vim_regcomp_had_eol() && !(syn_opt_arg.flags & HL_EXCLUDENL)) { syn_opt_arg.flags |= HL_HAS_EOL; + } - /* Get options after the pattern */ + // Get options after the pattern rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip); if (rest != NULL) { /* all arguments are valid */ @@ -4526,14 +4530,13 @@ syn_cmd_region ( syn_opt_arg.cont_in_list = NULL; syn_opt_arg.next_list = NULL; - /* - * get the options, patterns and matchgroup. - */ + // get the options, patterns and matchgroup. while (rest != NULL && !ends_excmd(*rest)) { - /* Check for option arguments */ + // Check for option arguments rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip); - if (rest == NULL || ends_excmd(*rest)) + if (rest == NULL || ends_excmd(*rest)) { break; + } /* must be a pattern or matchgroup then */ key_end = rest; @@ -4963,7 +4966,7 @@ static void syn_cmd_cluster(exarg_T *eap, int syncing) syn_combine_list(&SYN_CLSTR(curwin->w_s)[scl_id].scl_list, &clstr_list, list_op); } - got_clstr = TRUE; + got_clstr = true; } if (got_clstr) { @@ -5211,9 +5214,9 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) static int get_id_list ( char_u **arg, - int keylen, /* length of keyword */ - short **list, /* where to store the resulting list, if not - NULL, the list is silently skipped! */ + int keylen, // length of keyword + int16_t **list, // where to store the resulting list, if not + // NULL, the list is silently skipped! int skip ) { @@ -6493,7 +6496,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) HL_TABLE()[from_id - 1].sg_set |= SG_LINK; HL_TABLE()[from_id - 1].sg_link = to_id; HL_TABLE()[from_id - 1].sg_scriptID = current_SID; - HL_TABLE()[from_id - 1].sg_cleared = false; + HL_TABLE()[from_id - 1].sg_cleared = false; redraw_all_later(SOME_VALID); } } @@ -7774,14 +7777,14 @@ const char *get_highlight_name(expand_T *const xp, const int idx) return NULL; } - /* Items are never removed from the table, skip the ones that were cleared. - */ - int current_idx = idx; - while (current_idx < highlight_ga.ga_len && HL_TABLE()[current_idx].sg_cleared) { - ++current_idx; + // Items are never removed from the table, skip the ones that were cleared. + int current_idx = idx; + while (current_idx < highlight_ga.ga_len + && HL_TABLE()[current_idx].sg_cleared) { + current_idx++; } if (current_idx >= highlight_ga.ga_len) { - return NULL; + return NULL; } return (const char *)HL_TABLE()[current_idx].sg_name; } -- cgit From c095f83116eb8ef87983ca5fea61053755fbc4e5 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 5 Jan 2018 11:17:21 +0100 Subject: api: change nvim_command_output behavior Implement nvim_command_output with `execute({cmd},"silent")`. Behavior changes: - does not provoke any hit-enter prompt - no longer prepends a newline char - does not capture some noise (like the "[New File]" message, see the change to tabnewentered_spec.lua) Technically ("bug-for-bug") this a breaking change. But the previous behavior of nvim_command_output meant that it probably wasn't used for anything outside of tests. Also remove the undocumented `v:command_output` variable which was a hack introduced only for the purposes of nvim_command_output. closes #7726 --- src/nvim/api/vim.c | 40 +++++++++++++++++++++++++++++++--------- src/nvim/eval.c | 1 - src/nvim/eval.h | 1 - 3 files changed, 31 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 172f2ce18e..8929bc5b9b 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -43,14 +43,15 @@ #endif /// Executes an ex-command. -/// On VimL error: Returns the VimL error; v:errmsg is not updated. +/// +/// On parse error: forwards the Vim error; does not update v:errmsg. +/// On runtime error: forwards the Vim error; does not update v:errmsg. /// /// @param command Ex-command string -/// @param[out] err Error details (including actual VimL error), if any +/// @param[out] err Error details (Vim error), if any void nvim_command(String command, Error *err) FUNC_API_SINCE(1) { - // Run the command try_start(); do_cmdline_cmd(command.data); update_screen(VALID); @@ -207,18 +208,39 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, return cstr_as_string(ptr); } -String nvim_command_output(String str, Error *err) +/// Executes an ex-command and returns its (non-error) output. +/// Shell |:!| output is not captured. +/// +/// On parse error: forwards the Vim error; does not update v:errmsg. +/// On runtime error: forwards the Vim error; does not update v:errmsg. +/// +/// @param command Ex-command string +/// @param[out] err Error details (Vim error), if any +String nvim_command_output(String command, Error *err) FUNC_API_SINCE(1) { - do_cmdline_cmd("redir => v:command_output"); - nvim_command(str, err); - do_cmdline_cmd("redir END"); - + Array args = ARRAY_DICT_INIT; + ADD(args, STRING_OBJ(copy_string(command))); + ADD(args, STRING_OBJ(cstr_to_string("silent"))); + String fn = cstr_to_string("execute"); + Object rv = nvim_call_function(fn, args, err); + api_free_string(fn); + api_free_array(args); if (ERROR_SET(err)) { + assert(rv.type == kObjectTypeNil); return (String)STRING_INIT; } - return cstr_to_string((char *)get_vim_var_str(VV_COMMAND_OUTPUT)); + assert(rv.type == kObjectTypeString); + // execute() always(?) prepends a newline; remove it. + if (rv.data.string.size > 1) { + assert(rv.data.string.data[0] == '\n'); + String *s = &rv.data.string; + s->size--; + memmove(s->data, s->data + 1, s->size); + s->data[s->size] = '\0'; + } + return rv.data.string; } /// Evaluates a VimL expression (:help expression). diff --git a/src/nvim/eval.c b/src/nvim/eval.c index f34b6db8d8..875f323a28 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -402,7 +402,6 @@ static struct vimvar { VV(VV_OLDFILES, "oldfiles", VAR_LIST, 0), VV(VV_WINDOWID, "windowid", VAR_NUMBER, VV_RO_SBX), VV(VV_PROGPATH, "progpath", VAR_STRING, VV_RO), - VV(VV_COMMAND_OUTPUT, "command_output", VAR_STRING, 0), VV(VV_COMPLETED_ITEM, "completed_item", VAR_DICT, VV_RO), VV(VV_OPTION_NEW, "option_new", VAR_STRING, VV_RO), VV(VV_OPTION_OLD, "option_old", VAR_STRING, VV_RO), diff --git a/src/nvim/eval.h b/src/nvim/eval.h index 0c0a6881f6..b798eae187 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -86,7 +86,6 @@ typedef enum { VV_OLDFILES, VV_WINDOWID, VV_PROGPATH, - VV_COMMAND_OUTPUT, VV_COMPLETED_ITEM, VV_OPTION_NEW, VV_OPTION_OLD, -- cgit From 5055d4a755d3c100cddf51bded77abca04beb971 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 9 Jan 2018 10:36:25 +0100 Subject: api: nvim_command_output: direct impl --- src/nvim/README.md | 57 +++++++++++++++++++++++++++++++++++++++++------------- src/nvim/api/vim.c | 46 ++++++++++++++++++++++++++----------------- src/nvim/eval.c | 3 +-- 3 files changed, 73 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/src/nvim/README.md b/src/nvim/README.md index da87a0208e..1ece92e44d 100644 --- a/src/nvim/README.md +++ b/src/nvim/README.md @@ -1,10 +1,22 @@ -Nvim core source -================ +Nvim core +========= Module-specific details are documented at the top of each module (`terminal.c`, -`screen.c`, ...). +`screen.c`, …). -See `:help development` for more guidelines. +See `:help dev` for guidelines. + +Filename conventions +-------------------- + +The source files use extensions to hint about their purpose. + +- `*.c`, `*.generated.c` - full C files, with all includes, etc. +- `*.c.h` - parametrized C files, contain all necessary includes, but require + defining macros before actually using. Example: `typval_encode.c.h` +- `*.h` - full headers, with all includes. Does *not* apply to `*.generated.h`. +- `*.h.generated.h` - exported functions’ declarations. +- `*.c.generated.h` - static functions’ declarations. Logs ---- @@ -20,17 +32,36 @@ UI events are logged at level 0 (`DEBUG_LOG_LEVEL`). rm -rf build/ make CMAKE_EXTRA_FLAGS="-DMIN_LOG_LEVEL=0" -Filename conventions --------------------- +Build with ASAN +--------------- -The source files use extensions to hint about their purpose. +Building Nvim with Clang sanitizers (Address Sanitizer: ASan, Undefined +Behavior Sanitizer: UBSan, Memory Sanitizer: MSan, Thread Sanitizer: TSan) is +a good way to catch undefined behavior, leaks and other errors as soon as they +happen. It's significantly faster than Valgrind. -- `*.c`, `*.generated.c` - full C files, with all includes, etc. -- `*.c.h` - parametrized C files, contain all necessary includes, but require - defining macros before actually using. Example: `typval_encode.c.h` -- `*.h` - full headers, with all includes. Does *not* apply to `*.generated.h`. -- `*.h.generated.h` - exported functions’ declarations. -- `*.c.generated.h` - static functions’ declarations. +Requires clang 3.4 or later: + + clang --version + +Build Nvim with sanitizer instrumentation: + + CC=clang make CMAKE_EXTRA_FLAGS="-DCLANG_ASAN_UBSAN=ON" + +Create a directory to store logs: + + mkdir -p "$HOME/logs" + +Enable the sanitizer(s) via these environment variables: + + # Change to detect_leaks=1 to detect memory leaks (slower). + export ASAN_OPTIONS="detect_leaks=0:log_path=$HOME/logs/asan" + export ASAN_SYMBOLIZER_PATH=/usr/lib/llvm-5.0/bin/llvm-symbolizer + + export MSAN_SYMBOLIZER_PATH=/usr/lib/llvm-5.0/bin/llvm-symbolizer + export TSAN_OPTIONS="external_symbolizer_path=/usr/lib/llvm-5.0/bin/llvm-symbolizer log_path=${HOME}/logs/tsan" + +Logs will be written to `${HOME}/logs/*san.PID`. TUI debugging ------------- diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 8929bc5b9b..0e7cc428d4 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -219,28 +219,38 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, String nvim_command_output(String command, Error *err) FUNC_API_SINCE(1) { - Array args = ARRAY_DICT_INIT; - ADD(args, STRING_OBJ(copy_string(command))); - ADD(args, STRING_OBJ(cstr_to_string("silent"))); - String fn = cstr_to_string("execute"); - Object rv = nvim_call_function(fn, args, err); - api_free_string(fn); - api_free_array(args); + const int save_msg_silent = msg_silent; + garray_T *const save_capture_ga = capture_ga; + garray_T capture_local; + ga_init(&capture_local, 1, 80); + + try_start(); + msg_silent++; + capture_ga = &capture_local; + do_cmdline_cmd(command.data); + capture_ga = save_capture_ga; + msg_silent = save_msg_silent; + try_end(err); + if (ERROR_SET(err)) { - assert(rv.type == kObjectTypeNil); - return (String)STRING_INIT; + goto theend; } - assert(rv.type == kObjectTypeString); - // execute() always(?) prepends a newline; remove it. - if (rv.data.string.size > 1) { - assert(rv.data.string.data[0] == '\n'); - String *s = &rv.data.string; - s->size--; - memmove(s->data, s->data + 1, s->size); - s->data[s->size] = '\0'; + if (capture_local.ga_len > 1) { + // redir always(?) prepends a newline; remove it. + char *s = capture_local.ga_data; + assert(s[0] == '\n'); + memmove(s, s + 1, (size_t)capture_local.ga_len); + s[capture_local.ga_len - 1] = '\0'; + return (String) { // Caller will free the memory. + .data = s, + .size = (size_t)(capture_local.ga_len - 1), + }; } - return rv.data.string; + +theend: + ga_clear(&capture_local); + return (String)STRING_INIT; } /// Evaluates a VimL expression (:help expression). diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 875f323a28..2e8bf18f2d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -8116,8 +8116,7 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) ga_append(capture_ga, NUL); rettv->v_type = VAR_STRING; - rettv->vval.v_string = vim_strsave(capture_ga->ga_data); - ga_clear(capture_ga); + rettv->vval.v_string = capture_ga->ga_data; capture_ga = save_capture_ga; } -- cgit From 18d244eded434e6bc47b351ef00088378883bf1d Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 11 Jan 2018 01:32:41 +0100 Subject: coverity/169163: decode_string: Null pointer deref *** CID 169163: Null pointer dereferences (FORWARD_NULL) /src/nvim/eval/decode.c: 290 in decode_string() 284 if (elw_ret == -1) { 285 tv_clear(&tv); 286 return (typval_T) { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED }; 287 } 288 return tv; 289 } else { >>> CID 169163: Null pointer dereferences (FORWARD_NULL) >>> Passing null pointer "s" to "xmemdupz", which dereferences it. (The dereference is assumed on the basis of the 'nonnull' parameter attribute.) 290 return (typval_T) { 291 .v_type = VAR_STRING, 292 .v_lock = VAR_UNLOCKED, 293 .vval = { .v_string = (char_u *)( 294 s_allocated ? (char *)s : xmemdupz(s, len)) }, 295 }; --- src/nvim/eval/decode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index af4e055d23..cd967ed5c5 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -285,7 +285,7 @@ typval_T decode_string(const char *const s, const size_t len, .v_type = VAR_STRING, .v_lock = VAR_UNLOCKED, .vval = { .v_string = (char_u *)( - s_allocated ? (char *)s : xmemdupz(s, len)) }, + (s == NULL || s_allocated) ? (char *)s : xmemdupz(s, len)) }, }; } } -- cgit From 624ac8aede93cc521d7ea27ae406ad4780642fcb Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 11 Jan 2018 01:36:37 +0100 Subject: coverity/161216: get_user_input: RETURN_LOCAL *** CID 161216: Memory - illegal accesses (RETURN_LOCAL) /src/nvim/eval.c: 11143 in get_user_input() 11137 rettv->vval.v_string = 11138 (char_u *)getcmdline_prompt(inputsecret_flag ? NUL : '@', p, echo_attr, 11139 xp_type, xp_arg, input_callback); 11140 ex_normal_busy = save_ex_normal_busy; 11141 callback_free(&input_callback); 11142 >>> CID 161216: Memory - illegal accesses (RETURN_LOCAL) >>> Using "cancelreturn", which points to an out-of-scope variable "def". 11143 if (rettv->vval.v_string == NULL && cancelreturn != NULL) { 11144 rettv->vval.v_string = (char_u *)xstrdup(cancelreturn); 11145 } 11146 11147 xfree(xp_arg); 11148 --- src/nvim/eval.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 2e8bf18f2d..a642a3c0dd 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -11110,6 +11110,7 @@ void get_user_input(const typval_T *const argvars, char defstr_buf[NUMBUFLEN]; char cancelreturn_buf[NUMBUFLEN]; char xp_name_buf[NUMBUFLEN]; + char def[1] = { 0 }; if (argvars[0].v_type == VAR_DICT) { if (argvars[1].v_type != VAR_UNKNOWN) { emsgf(_("E5050: {opts} must be the only argument")); @@ -11124,7 +11125,6 @@ void get_user_input(const typval_T *const argvars, if (defstr == NULL) { return; } - char def[1] = { 0 }; cancelreturn = tv_dict_get_string_buf_chk(dict, S_LEN("cancelreturn"), cancelreturn_buf, def); if (cancelreturn == NULL) { // error -- cgit From 9ddeb6e187e6ef6045bf037e4225dc46c8efb693 Mon Sep 17 00:00:00 2001 From: KunMing Xie Date: Sun, 14 Jan 2018 02:26:21 +0800 Subject: vim-patch:8.0.0364 (#7837) vim-patch:8.0.0364: ]s does not move cursor with two spell errors in one line Problem: ]s does not move cursor with two spell errors in one line. (Manuel Ortega) Solution: Don't stop search immediately when wrapped, search the line first. (Ken Takata) Add a test. https://github.com/vim/vim/commit/d3f78dc9ebd729475a7f24a50a91112e300d5ac9 * disable spell test for now --- src/nvim/spell.c | 25 ++++++++++++++----------- src/nvim/testdir/Makefile | 1 + src/nvim/testdir/test_spell.vim | 20 ++++++++++++++++++++ 3 files changed, 35 insertions(+), 11 deletions(-) create mode 100644 src/nvim/testdir/test_spell.vim (limited to 'src') diff --git a/src/nvim/spell.c b/src/nvim/spell.c index d2b2575f6a..b3e80bd768 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -1484,21 +1484,23 @@ spell_move_to ( return found_len; } - if (curline) + if (curline) { break; // only check cursor line + } + + // If we are back at the starting line and searched it again there + // is no match, give up. + if (lnum == wp->w_cursor.lnum && wrapped) { + break; + } // Advance to next line. if (dir == BACKWARD) { - // If we are back at the starting line and searched it again there - // is no match, give up. - if (lnum == wp->w_cursor.lnum && wrapped) - break; - - if (lnum > 1) - --lnum; - else if (!p_ws) + if (lnum > 1) { + lnum--; + } else if (!p_ws) { break; // at first line and 'nowrapscan' - else { + } else { // Wrap around to the end of the buffer. May search the // starting line again and accept the last match. lnum = wp->w_buffer->b_ml.ml_line_count; @@ -1523,8 +1525,9 @@ spell_move_to ( // If we are back at the starting line and there is no match then // give up. - if (lnum == wp->w_cursor.lnum && (!found_one || wrapped)) + if (lnum == wp->w_cursor.lnum && !found_one) { break; + } // Skip the characters at the start of the next line that were // included in a match crossing line boundaries. diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 5af8dd20cd..a23dacba15 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -81,6 +81,7 @@ NEW_TESTS ?= \ test_search.res \ test_signs.res \ test_smartindent.res \ + test_spell.res \ test_stat.res \ test_startup.res \ test_startup_utf8.res \ diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim new file mode 100644 index 0000000000..334568aadb --- /dev/null +++ b/src/nvim/testdir/test_spell.vim @@ -0,0 +1,20 @@ +" Test spell checking +" TODO: move test58 tests here + +if v:true + finish +endif + +func Test_wrap_search() + new + call setline(1, ['The', '', 'A plong line with two zpelling mistakes', '', 'End']) + set spell wrapscan + normal ]s + call assert_equal('plong', expand('')) + normal ]s + call assert_equal('zpelling', expand('')) + normal ]s + call assert_equal('plong', expand('')) + bwipe! + set nospell +endfunc -- cgit From 8eb0888a5de2481e15fcae63fa477f0ac634c9f2 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 13 Jan 2018 19:26:30 +0100 Subject: vim-patch:8.0.0582: illegal memory access with z= command Problem: Illegal memory access with z= command. (Dominique Pelle) Solution: Avoid case folded text to be longer than the original text. Use MB_PTR2LEN() instead of MB_BYTE2LEN(). https://github.com/vim/vim/commit/5b276aa80e112ae1993bd43e28f599f257827c54 --- src/nvim/spell.c | 40 +++++++++++++++++++++------------------- src/nvim/testdir/test_spell.vim | 9 +++++++++ 2 files changed, 30 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/nvim/spell.c b/src/nvim/spell.c index b3e80bd768..d74360bcc0 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -2568,7 +2568,7 @@ static bool spell_iswordp(char_u *p, win_T *wp) int c; if (has_mbyte) { - l = MB_BYTE2LEN(*p); + l = MB_PTR2LEN(p); s = p; if (l == 1) { // be quick for ASCII @@ -3141,6 +3141,12 @@ spell_find_suggest ( STRLCPY(su->su_badword, su->su_badptr, su->su_badlen + 1); (void)spell_casefold(su->su_badptr, su->su_badlen, su->su_fbadword, MAXWLEN); + + // TODO(vim): make this work if the case-folded text is longer than the + // original text. Currently an illegal byte causes wrong pointer + // computations. + su->su_fbadword[su->su_badlen] = NUL; + // get caps flags for bad word su->su_badflags = badword_captype(su->su_badptr, su->su_badptr + su->su_badlen); @@ -4110,10 +4116,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so && goodword_ends) { int l; - if (has_mbyte) - l = MB_BYTE2LEN(fword[sp->ts_fidx]); - else - l = 1; + l = MB_PTR2LEN(fword + sp->ts_fidx); if (fword_ends) { // Copy the skipped character to preword. memmove(preword + sp->ts_prewordlen, @@ -4259,8 +4262,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Correct ts_fidx for the byte length of the // character (we didn't check that before). sp->ts_fidx = sp->ts_fcharstart - + MB_BYTE2LEN( - fword[sp->ts_fcharstart]); + + MB_PTR2LEN(fword + sp->ts_fcharstart); // For changing a composing character adjust // the score from SCORE_SUBST to @@ -4366,7 +4368,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // results. if (has_mbyte) { c = mb_ptr2char(fword + sp->ts_fidx); - stack[depth].ts_fidx += MB_BYTE2LEN(fword[sp->ts_fidx]); + stack[depth].ts_fidx += MB_PTR2LEN(fword + sp->ts_fidx); if (enc_utf8 && utf_iscomposing(c)) stack[depth].ts_score -= SCORE_DEL - SCORE_DELCOMP; else if (c == mb_ptr2char(fword + stack[depth].ts_fidx)) @@ -4552,9 +4554,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Undo the STATE_SWAP swap: "21" -> "12". p = fword + sp->ts_fidx; if (has_mbyte) { - n = MB_BYTE2LEN(*p); + n = MB_PTR2LEN(p); c = mb_ptr2char(p + n); - memmove(p + MB_BYTE2LEN(p[n]), p, n); + memmove(p + MB_PTR2LEN(p + n), p, n); mb_char2bytes(c, p); } else { c = *p; @@ -4627,11 +4629,11 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Undo STATE_SWAP3: "321" -> "123" p = fword + sp->ts_fidx; if (has_mbyte) { - n = MB_BYTE2LEN(*p); + n = MB_PTR2LEN(p); c2 = mb_ptr2char(p + n); - fl = MB_BYTE2LEN(p[n]); + fl = MB_PTR2LEN(p + n); c = mb_ptr2char(p + n + fl); - tl = MB_BYTE2LEN(p[n + fl]); + tl = MB_PTR2LEN(p + n + fl); memmove(p + fl + tl, p, n); mb_char2bytes(c, p); mb_char2bytes(c2, p + tl); @@ -4690,10 +4692,10 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Undo ROT3L: "231" -> "123" p = fword + sp->ts_fidx; if (has_mbyte) { - n = MB_BYTE2LEN(*p); - n += MB_BYTE2LEN(p[n]); + n = MB_PTR2LEN(p); + n += MB_PTR2LEN(p + n); c = mb_ptr2char(p + n); - tl = MB_BYTE2LEN(p[n]); + tl = MB_PTR2LEN(p + n); memmove(p + tl, p, n); mb_char2bytes(c, p); } else { @@ -4743,9 +4745,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so p = fword + sp->ts_fidx; if (has_mbyte) { c = mb_ptr2char(p); - tl = MB_BYTE2LEN(*p); - n = MB_BYTE2LEN(p[tl]); - n += MB_BYTE2LEN(p[tl + n]); + tl = MB_PTR2LEN(p); + n = MB_PTR2LEN(p + tl); + n += MB_PTR2LEN(p + tl + n); memmove(p, p + tl, n); mb_char2bytes(c, p + n); } else { diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index 334568aadb..66be5c2441 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -18,3 +18,12 @@ func Test_wrap_search() bwipe! set nospell endfunc + +func Test_z_equal_on_invalid_utf8_word() + split + set spell + call setline(1, "\xff") + norm z= + set nospell + bwipe! +endfunc -- cgit From 9ea1752d60589e8fc5e7184144bc6d1c1b9f16a3 Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 3 Jan 2018 00:00:16 +0300 Subject: *: Provide list length when allocating lists --- src/nvim/api/private/helpers.c | 2 +- src/nvim/channel.c | 37 +++---- src/nvim/eval.c | 225 ++++++++++++++++++++--------------------- src/nvim/eval/decode.c | 32 +++--- src/nvim/eval/typval.c | 18 +++- src/nvim/eval/typval.h | 19 ++++ src/nvim/ex_cmds2.c | 6 +- src/nvim/lua/converter.c | 7 +- src/nvim/main.c | 2 +- src/nvim/menu.c | 2 +- src/nvim/ops.c | 21 ++-- src/nvim/quickfix.c | 2 +- src/nvim/regexp.c | 4 +- src/nvim/shada.c | 2 +- src/nvim/tag.c | 2 +- src/nvim/undo.c | 29 +++--- src/nvim/window.c | 4 +- 17 files changed, 217 insertions(+), 197 deletions(-) (limited to 'src') diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 26ad7ac1a6..9baa996560 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -783,7 +783,7 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) break; case kObjectTypeArray: { - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc((ptrdiff_t)obj.data.array.size); for (uint32_t i = 0; i < obj.data.array.size; i++) { Object item = obj.data.array.items[i]; diff --git a/src/nvim/channel.c b/src/nvim/channel.c index efef95de01..9d0a7d3c21 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -4,6 +4,7 @@ #include "nvim/api/ui.h" #include "nvim/channel.h" #include "nvim/eval.h" +#include "nvim/eval/encode.h" #include "nvim/event/socket.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/server.h" @@ -522,32 +523,18 @@ err: return 0; } -/// NB: mutates buf in place! -static list_T *buffer_to_tv_list(char *buf, size_t count) +/// Convert binary byte array to a readfile()-style list +/// +/// @param[in] buf Array to convert. +/// @param[in] len Array length. +/// +/// @return [allocated] Converted list. +static inline list_T *buffer_to_tv_list(const char *const buf, const size_t len) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE { - list_T *ret = tv_list_alloc(); - char *ptr = buf; - size_t remaining = count; - size_t off = 0; - - while (off < remaining) { - // append the line - if (ptr[off] == NL) { - tv_list_append_string(ret, ptr, (ssize_t)off); - size_t skip = off + 1; - ptr += skip; - remaining -= skip; - off = 0; - continue; - } - if (ptr[off] == NUL) { - // Translate NUL to NL - ptr[off] = NL; - } - off++; - } - tv_list_append_string(ret, ptr, (ssize_t)off); - return ret; + list_T *const l = tv_list_alloc(kListLenMayKnow); + encode_list_write(l, buf, len); + return l; } // vimscript job callbacks must be executed on Nvim main loop diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 2e8bf18f2d..ec32f1821d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -568,7 +568,7 @@ void eval_init(void) dict_T *const msgpack_types_dict = tv_dict_alloc(); for (size_t i = 0; i < ARRAY_SIZE(msgpack_type_names); i++) { - list_T *const type_list = tv_list_alloc(); + list_T *const type_list = tv_list_alloc(0); tv_list_set_lock(type_list, VAR_FIXED); tv_list_ref(type_list); dictitem_T *const di = tv_dict_item_alloc(msgpack_type_names[i]); @@ -591,7 +591,7 @@ void eval_init(void) dict_T *v_event = tv_dict_alloc(); v_event->dv_lock = VAR_FIXED; set_vim_var_dict(VV_EVENT, v_event); - set_vim_var_list(VV_ERRORS, tv_list_alloc()); + set_vim_var_list(VV_ERRORS, tv_list_alloc(kListLenUnknown)); set_vim_var_nr(VV_STDERR, CHAN_STDERR); set_vim_var_nr(VV_SEARCHFORWARD, 1L); set_vim_var_nr(VV_HLSEARCH, 1L); @@ -1546,6 +1546,7 @@ ex_let_vars ( assert(l != NULL); listitem_T *item = tv_list_first(l); + size_t rest_len = tv_list_len(l); while (*arg != ']') { arg = skipwhite(arg + 1); arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, (const char_u *)",;]", @@ -1553,13 +1554,14 @@ ex_let_vars ( if (arg == NULL) { return FAIL; } + rest_len--; item = TV_LIST_ITEM_NEXT(l, item); arg = skipwhite(arg); if (*arg == ';') { /* Put the rest of the list (may be empty) in the var after ';'. * Create a new list for this. */ - list_T *const rest_list = tv_list_alloc(); + list_T *const rest_list = tv_list_alloc(rest_len); while (item != NULL) { tv_list_append_tv(rest_list, TV_LIST_ITEM_TV(item)); item = TV_LIST_ITEM_NEXT(l, item); @@ -4512,7 +4514,7 @@ eval_index ( if (!empty2 && (n2 < 0 || n2 + 1 < n1)) { n2 = -1; } - l = tv_list_alloc(); + l = tv_list_alloc(n2 - n1 + 1); item = tv_list_find(rettv->vval.v_list, n1); while (n1++ <= n2) { tv_list_append_tv(l, TV_LIST_ITEM_TV(item)); @@ -4870,7 +4872,7 @@ static int get_list_tv(char_u **arg, typval_T *rettv, int evaluate) list_T *l = NULL; if (evaluate) { - l = tv_list_alloc(); + l = tv_list_alloc(kListLenShouldKnow); } *arg = skipwhite(*arg + 1); @@ -6666,7 +6668,7 @@ static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr) } rettv->v_type = VAR_STRING; } else { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, ARGCOUNT); for (idx = 0; idx < ARGCOUNT; idx++) { tv_list_append_string(rettv->vval.v_list, (const char *)alist_name(&ARGLIST[idx]), -1); @@ -6776,7 +6778,7 @@ static void assert_error(garray_T *gap) if (vp->vv_type != VAR_LIST || vimvars[VV_ERRORS].vv_list == NULL) { // Make sure v:errors is a list. - set_vim_var_list(VV_ERRORS, tv_list_alloc()); + set_vim_var_list(VV_ERRORS, tv_list_alloc(1)); } tv_list_append_string(vimvars[VV_ERRORS].vv_list, (const char *)gap->ga_data, (ptrdiff_t)gap->ga_len); @@ -8225,7 +8227,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) result = eval_vars((char_u *)s, (char_u *)s, &len, NULL, &errormsg, NULL); emsg_off--; if (rettv->v_type == VAR_LIST) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, (result != NULL)); if (result != NULL) { tv_list_append_string(rettv->vval.v_list, (const char *)result, -1); } @@ -8248,8 +8250,8 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = ExpandOne(&xpc, (char_u *)s, NULL, options, WILD_ALL); } else { - tv_list_alloc_ret(rettv); ExpandOne(&xpc, (char_u *)s, NULL, options, WILD_ALL_KEEP); + tv_list_alloc_ret(rettv, xpc.xp_numfiles); for (int i = 0; i < xpc.xp_numfiles; i++) { tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], -1); @@ -8266,7 +8268,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "menu_get(path [, modes])" function static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); int modes = MENU_ALL_MODES; if (argvars[1].v_type == VAR_STRING) { const char_u *const strmodes = (char_u *)tv_get_string(&argvars[1]); @@ -8427,7 +8429,7 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) } if (count < 0) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenUnknown); } if (*fname != NUL && !error) { @@ -9108,7 +9110,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } else if (strcmp(what, "args") == 0) { rettv->v_type = VAR_LIST; - if (tv_list_alloc_ret(rettv) != NULL) { + if (tv_list_alloc_ret(rettv, pt->pt_argc) != NULL) { for (int i = 0; i < pt->pt_argc; i++) { tv_list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]); } @@ -9132,8 +9134,10 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// Returns information about signs placed in a buffer as list of dicts. -static void get_buffer_signs(buf_T *buf, list_T *l) +static list_T *get_buffer_signs(buf_T *buf) + FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { + list_T *const l = tv_list_alloc(kListLenMayKnow); for (signlist_T *sign = buf->b_signlist; sign; sign = sign->next) { dict_T *const d = tv_dict_alloc(); @@ -9144,6 +9148,7 @@ static void get_buffer_signs(buf_T *buf, list_T *l) tv_list_append_dict(l, d); } + return l; } /// Returns buffer options, variables and other attributes in a dictionary. @@ -9167,7 +9172,7 @@ static dict_T *get_buffer_info(buf_T *buf) tv_dict_add_dict(dict, S_LEN("variables"), buf->b_vars); // List of windows displaying this buffer - list_T *const windows = tv_list_alloc(); + list_T *const windows = tv_list_alloc(kListLenMayKnow); FOR_ALL_TAB_WINDOWS(tp, wp) { if (wp->w_buffer == buf) { tv_list_append_number(windows, (varnumber_T)wp->handle); @@ -9177,9 +9182,7 @@ static dict_T *get_buffer_info(buf_T *buf) if (buf->b_signlist != NULL) { // List of signs placed in this buffer - list_T *const signs = tv_list_alloc(); - get_buffer_signs(buf, signs); - tv_dict_add_list(dict, S_LEN("signs"), signs); + tv_dict_add_list(dict, S_LEN("signs"), get_buffer_signs(buf)); } return dict; @@ -9193,7 +9196,7 @@ static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool sel_buflisted = false; bool sel_bufloaded = false; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); // List of all the buffers or selected buffers if (argvars[0].v_type == VAR_DICT) { @@ -9252,35 +9255,31 @@ static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retlist, typval_T *rettv) { - char_u *p; - - rettv->v_type = VAR_STRING; + rettv->v_type = (retlist ? VAR_LIST : VAR_STRING); rettv->vval.v_string = NULL; - if (retlist) { - tv_list_alloc_ret(rettv); - } - if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0) + if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0 || end < start) { + tv_list_alloc_ret(rettv, 0); return; + } - if (!retlist) { - if (start >= 1 && start <= buf->b_ml.ml_line_count) - p = ml_get_buf(buf, start, FALSE); - else - p = (char_u *)""; - rettv->vval.v_string = vim_strsave(p); - } else { - if (end < start) - return; - - if (start < 1) + if (retlist) { + if (start < 1) { start = 1; - if (end > buf->b_ml.ml_line_count) + } + if (end > buf->b_ml.ml_line_count) { end = buf->b_ml.ml_line_count; + } + tv_list_alloc_ret(rettv, end - start + 1); while (start <= end) { tv_list_append_string(rettv->vval.v_list, (const char *)ml_get_buf(buf, start++, false), -1); } + } else { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = ((start >= 1 && start <= buf->b_ml.ml_line_count) + ? vim_strsave(ml_get_buf(buf, start, false)) + : NULL); } } @@ -9605,8 +9604,8 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) theend: pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context); - tv_list_alloc_ret(rettv); ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP); + tv_list_alloc_ret(rettv, xpc.xp_numfiles); for (int i = 0; i < xpc.xp_numfiles; i++) { tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], @@ -9900,7 +9899,7 @@ static void f_getline(typval_T *argvars, typval_T *rettv, FunPtr fptr) const linenr_T lnum = tv_get_lnum(argvars); if (argvars[1].v_type == VAR_UNKNOWN) { - end = 0; + end = lnum; retlist = false; } else { end = tv_get_lnum(&argvars[1]); @@ -9914,7 +9913,7 @@ static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, typval_T *rettv) { if (what_arg->v_type == VAR_UNKNOWN) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); if (is_qf || wp != NULL) { (void)get_errorlist(wp, -1, rettv->vval.v_list); } @@ -9949,7 +9948,7 @@ static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) matchitem_T *cur = curwin->w_match_head; int i; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); while (cur != NULL) { dict_T *dict = tv_dict_alloc(); if (cur->match.regprog == NULL) { @@ -9962,7 +9961,7 @@ static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (llpos->lnum == 0) { break; } - list_T *l = tv_list_alloc(); + list_T *const l = tv_list_alloc(1 + (llpos->col > 0 ? 2 : 0)); tv_list_append_number(l, (varnumber_T)llpos->lnum); if (llpos->col > 0) { tv_list_append_number(l, (varnumber_T)llpos->col); @@ -10011,7 +10010,7 @@ static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos) fp = var2fpos(&argvars[0], true, &fnum); } - list_T *l = tv_list_alloc_ret(rettv); + list_T *const l = tv_list_alloc_ret(rettv, 4 + (!!getcurpos)); tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0); tv_list_append_number(l, ((fp != NULL) ? (varnumber_T)fp->lnum @@ -10084,10 +10083,10 @@ static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (return_list) { rettv->v_type = VAR_LIST; - rettv->vval.v_list = + rettv->vval.v_list = get_reg_contents(regname, (arg2 ? kGRegExprSrc : 0) | kGRegList); if (rettv->vval.v_list == NULL) { - rettv->vval.v_list = tv_list_alloc(); + rettv->vval.v_list = tv_list_alloc(0); } tv_list_ref(rettv->vval.v_list); } else { @@ -10137,7 +10136,7 @@ static dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx) tv_dict_add_nr(dict, S_LEN("tabnr"), tp_idx); - list_T *const l = tv_list_alloc(); + list_T *const l = tv_list_alloc(kListLenMayKnow); FOR_ALL_WINDOWS_IN_TAB(wp, tp) { tv_list_append_number(l, (varnumber_T)wp->handle); } @@ -10154,7 +10153,9 @@ static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tabpage_T *tparg = NULL; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, (argvars[0].v_type == VAR_UNKNOWN + ? 1 + : kListLenMayKnow)); if (argvars[0].v_type != VAR_UNKNOWN) { // Information about one tab page @@ -10253,7 +10254,7 @@ static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) { win_T *wparg = NULL; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); if (argvars[0].v_type != VAR_UNKNOWN) { wparg = win_id2wp(argvars); @@ -10478,9 +10479,9 @@ static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = ExpandOne( &xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options, WILD_ALL); } else { - tv_list_alloc_ret(rettv); ExpandOne(&xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options, WILD_ALL_KEEP); + tv_list_alloc_ret(rettv, xpc.xp_numfiles); for (int i = 0; i < xpc.xp_numfiles; i++) { tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], -1); @@ -10529,7 +10530,7 @@ static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (rettv->v_type == VAR_STRING) { rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n"); } else { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, ga.ga_len); for (int i = 0; i < ga.ga_len; i++) { tv_list_append_string(rettv->vval.v_list, ((const char **)(ga.ga_data))[i], -1); @@ -11429,7 +11430,7 @@ static void dict_list(typval_T *const tv, typval_T *const rettv, return; } - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, tv_dict_len(tv->vval.v_dict)); TV_DICT_ITER(tv->vval.v_dict, di, { typval_T tv = { .v_lock = VAR_UNLOCKED }; @@ -11446,7 +11447,7 @@ static void dict_list(typval_T *const tv, typval_T *const rettv, } case kDictListItems: { // items() - list_T *const sub_l = tv_list_alloc(); + list_T *const sub_l = tv_list_alloc(2); tv.v_type = VAR_LIST; tv.vval.v_list = sub_l; tv_list_ref(sub_l); @@ -11776,7 +11777,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - list_T *rv = tv_list_alloc(); + list_T *const rv = tv_list_alloc(tv_list_len(args)); // restore the parent queue for any jobs still alive for (i = 0; i < tv_list_len(args); i++) { @@ -12204,12 +12205,12 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, switch (type) { // matchlist(): return empty list when there are no matches. case kSomeMatchList: { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); break; } // matchstrpos(): return ["", -1, -1, -1] case kSomeMatchStrPos: { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, 4); tv_list_append_string(rettv->vval.v_list, "", 0); tv_list_append_number(rettv->vval.v_list, -1); tv_list_append_number(rettv->vval.v_list, -1); @@ -12516,10 +12517,12 @@ static void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tv_list_alloc_ret(rettv); - const int id = tv_get_number(&argvars[0]); + tv_list_alloc_ret(rettv, (id >= 1 && id <= 3 + ? 2 + : 0)); + if (id >= 1 && id <= 3) { matchitem_T *const m = (matchitem_T *)get_match(curwin, id); @@ -12705,8 +12708,8 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG2(_(e_listarg), "msgpackdump()"); return; } - list_T *ret_list = tv_list_alloc_ret(rettv); - list_T *list = argvars[0].vval.v_list; + list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow); + list_T *const list = argvars[0].vval.v_list; msgpack_packer *lpacker = msgpack_packer_new(ret_list, &encode_list_write); const char *const msg = _("msgpackdump() argument, index %i"); // Assume that translation will not take more then 4 times more space @@ -12730,8 +12733,8 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG2(_(e_listarg), "msgpackparse()"); return; } - list_T *ret_list = tv_list_alloc_ret(rettv); - const list_T *list = argvars[0].vval.v_list; + list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow); + const list_T *const list = argvars[0].vval.v_list; if (tv_list_len(list) == 0) { return; } @@ -12986,7 +12989,7 @@ static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else if (stride > 0 ? end + 1 < start : end - 1 > start) { emsgf(_("E727: Start past end")); } else { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, (end - start) / stride); for (i = start; stride > 0 ? i <= end : i >= end; i += stride) { tv_list_append_number(rettv->vval.v_list, (varnumber_T)i); } @@ -13017,8 +13020,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - tv_list_alloc_ret(rettv); - list_T *const l = rettv->vval.v_list; + list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown); // Always open the file in binary mode, library functions have a mind of // their own about CR-LF conversion. @@ -13231,7 +13233,7 @@ static void f_reltime(typval_T *argvars, typval_T *rettv, FunPtr fptr) STATIC_ASSERT(sizeof(u.prof) == sizeof(u) && sizeof(u.split) == sizeof(u), "type punning will produce incorrect results on this platform"); - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, 2); tv_list_append_number(rettv->vval.v_list, u.split.high); tv_list_append_number(rettv->vval.v_list, u.split.low); } @@ -13324,7 +13326,8 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (li == NULL) { // Didn't find "item2" after "item". emsgf(_(e_invrange)); } else { - tv_list_move_items(l, item, item2, tv_list_alloc_ret(rettv), cnt); + tv_list_move_items(l, item, item2, tv_list_alloc_ret(rettv, cnt), + cnt); } } } @@ -13354,7 +13357,7 @@ static void f_repeat(typval_T *argvars, typval_T *rettv, FunPtr fptr) { varnumber_T n = tv_get_number(&argvars[1]); if (argvars[0].v_type == VAR_LIST) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, (n > 0) * n * tv_list_len(argvars[0].vval.v_list)); while (n-- > 0) { tv_list_extend(rettv->vval.v_list, argvars[0].vval.v_list, NULL); } @@ -14124,7 +14127,7 @@ static void f_searchpairpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) int lnum = 0; int col = 0; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, 2); if (searchpair_cmn(argvars, &match_pos) > 0) { lnum = match_pos.lnum; @@ -14292,18 +14295,14 @@ do_searchpair ( static void f_searchpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { pos_T match_pos; - int lnum = 0; - int col = 0; - int n; int flags = 0; - tv_list_alloc_ret(rettv); + const int n = search_cmn(argvars, &match_pos, &flags); - n = search_cmn(argvars, &match_pos, &flags); - if (n > 0) { - lnum = match_pos.lnum; - col = match_pos.col; - } + tv_list_alloc_ret(rettv, 2 + (!!(flags & SP_SUBPAT))); + + const int lnum = (n > 0 ? match_pos.lnum : 0); + const int col = (n > 0 ? match_pos.col : 0); tv_list_append_number(rettv->vval.v_list, (varnumber_T)lnum); tv_list_append_number(rettv->vval.v_list, (varnumber_T)col); @@ -14319,7 +14318,7 @@ static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) char **addrs = server_address_list(&n); // Copy addrs into a linked list. - list_T *l = tv_list_alloc_ret(rettv); + list_T *const l = tv_list_alloc_ret(rettv, n); for (size_t i = 0; i < n; i++) { tv_list_append_allocated_string(l, addrs[i]); } @@ -14715,7 +14714,7 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) dictitem_T *const di = tv_dict_find(d, S_LEN("pattern")); if (di == NULL) { if (s == NULL) { - s = tv_list_alloc(); + s = tv_list_alloc(9); } // match from matchaddpos() @@ -15531,8 +15530,6 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) hlf_T attr = HLF_COUNT; size_t len = 0; - tv_list_alloc_ret(rettv); - if (argvars[0].v_type == VAR_UNKNOWN) { // Find the start and length of the badly spelled word. len = spell_move_to(curwin, FORWARD, true, true, &attr); @@ -15557,6 +15554,7 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) } assert(len <= INT_MAX); + tv_list_alloc_ret(rettv, 2); tv_list_append_string(rettv->vval.v_list, word, len); tv_list_append_string(rettv->vval.v_list, (attr == HLF_SPB ? "bad" @@ -15573,35 +15571,36 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) { bool typeerr = false; int maxcount; - garray_T ga; + garray_T ga = GA_EMPTY_INIT_VALUE; bool need_capital = false; - tv_list_alloc_ret(rettv); - if (curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) { const char *const str = tv_get_string(&argvars[0]); if (argvars[1].v_type != VAR_UNKNOWN) { maxcount = tv_get_number_chk(&argvars[1], &typeerr); if (maxcount <= 0) { - return; + goto f_spellsuggest_return; } if (argvars[2].v_type != VAR_UNKNOWN) { need_capital = tv_get_number_chk(&argvars[2], &typeerr); if (typeerr) { - return; + goto f_spellsuggest_return; } } - } else + } else { maxcount = 25; + } spell_suggest_list(&ga, (char_u *)str, maxcount, need_capital, false); + } - for (int i = 0; i < ga.ga_len; i++) { - char *p = ((char **)ga.ga_data)[i]; - tv_list_append_allocated_string(rettv->vval.v_list, p); - } - ga_clear(&ga); +f_spellsuggest_return: + tv_list_alloc_ret(rettv, (ptrdiff_t)ga.ga_len); + for (int i = 0; i < ga.ga_len; i++) { + char *const p = ((char **)ga.ga_data)[i]; + tv_list_append_allocated_string(rettv->vval.v_list, p); } + ga_clear(&ga); } static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) @@ -15633,10 +15632,11 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) pat = "[\\x01- ]\\+"; } - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); - if (typeerr) + if (typeerr) { return; + } regmatch.regprog = vim_regcomp((char_u *)pat, RE_MAGIC + RE_STRING); if (regmatch.regprog != NULL) { @@ -16263,7 +16263,6 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) memset(str, NUL, sizeof(str)); - tv_list_alloc_ret(rettv); if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count && col >= 0 && (size_t)col <= STRLEN(ml_get(lnum)) && curwin->w_p_cole > 0) { (void)syn_get_id(curwin, lnum, col, false, NULL, false); @@ -16276,14 +16275,12 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) cchar = lcs_conceal; } if (cchar != NUL) { - if (has_mbyte) - (*mb_char2bytes)(cchar, str); - else - str[0] = cchar; + utf_char2bytes(cchar, str); } } } + tv_list_alloc_ret(rettv, 3); tv_list_append_number(rettv->vval.v_list, (syntax_flags & HL_CONCEAL) != 0); // -1 to auto-determine strlen tv_list_append_string(rettv->vval.v_list, (const char *)str, -1); @@ -16306,7 +16303,7 @@ static void f_synstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) && lnum <= curbuf->b_ml.ml_line_count && col >= 0 && (size_t)col <= STRLEN(ml_get(lnum))) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); (void)syn_get_id(curwin, lnum, col, false, NULL, true); int id; @@ -16322,7 +16319,7 @@ static list_T *string_to_list(const char *str, size_t len, const bool keepempty) if (!keepempty && str[len - 1] == NL) { len--; } - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc(kListLenMayKnow); encode_list_write(list, str, len); return list; } @@ -16368,7 +16365,7 @@ static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, if (res == NULL) { if (retlist) { // return an empty list when there's no output - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, 0); } else { rettv->vval.v_string = (char_u *) xstrdup(""); } @@ -16434,7 +16431,7 @@ static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } if (wp != NULL) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); while (wp != NULL) { tv_list_append_number(rettv->vval.v_list, wp->w_buffer->b_fnum); wp = wp->w_next; @@ -16532,7 +16529,7 @@ static void f_tagfiles(typval_T *argvars, typval_T *rettv, FunPtr fptr) char *fname; tagname_T tn; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenUnknown); fname = xmalloc(MAXPATHL); bool first = true; @@ -16561,8 +16558,8 @@ static void f_taglist(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[1].v_type != VAR_UNKNOWN) { fname = tv_get_string(&argvars[1]); } - (void)get_tags(tv_list_alloc_ret(rettv), (char_u *)tag_pattern, - (char_u *)fname); + (void)get_tags(tv_list_alloc_ret(rettv, kListLenUnknown), + (char_u *)tag_pattern, (char_u *)fname); } /* @@ -16803,7 +16800,9 @@ static void add_timer_info_all(typval_T *rettv) /// "timer_info([timer])" function static void f_timer_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, (argvars[0].v_type != VAR_UNKNOWN + ? 1 + : timers->table->n_occupied)); if (argvars[0].v_type != VAR_UNKNOWN) { if (argvars[0].v_type != VAR_NUMBER) { EMSG(_(e_number_exp)); @@ -17163,7 +17162,6 @@ static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_dict_alloc_ret(rettv); dict_T *dict = rettv->vval.v_dict; - list_T *list; tv_dict_add_nr(dict, S_LEN("synced"), (varnumber_T)curbuf->b_u_synced); tv_dict_add_nr(dict, S_LEN("seq_last"), (varnumber_T)curbuf->b_u_seq_last); @@ -17173,9 +17171,7 @@ static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_dict_add_nr(dict, S_LEN("time_cur"), (varnumber_T)curbuf->b_u_time_cur); tv_dict_add_nr(dict, S_LEN("save_cur"), (varnumber_T)curbuf->b_u_save_nr_cur); - list = tv_list_alloc(); - u_eval_tree(curbuf->b_u_oldhead, list); - tv_dict_add_list(dict, S_LEN("entries"), list); + tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(curbuf->b_u_oldhead)); } /* @@ -17234,7 +17230,7 @@ static void f_wildmenumode(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "win_findbuf()" function static void f_win_findbuf(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); win_findbuf(argvars, rettv->vval.v_list); } @@ -17253,8 +17249,7 @@ static void f_win_gotoid(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "win_id2tabwin()" function static void f_win_id2tabwin(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tv_list_alloc_ret(rettv); - win_id2tabwin(argvars, rettv->vval.v_list); + win_id2tabwin(argvars, rettv); } /// "win_id2win()" function @@ -22367,7 +22362,7 @@ static void script_host_eval(char *name, typval_T *argvars, typval_T *rettv) return; } - list_T *args = tv_list_alloc(); + list_T *args = tv_list_alloc(1); tv_list_append_string(args, (const char *)argvars[0].vval.v_string, -1); *rettv = eval_call_provider(name, "eval", args); } diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index af4e055d23..c729d00ae7 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -150,7 +150,7 @@ static inline int json_decoder_pop(ValuesStackItem obj, } obj_di->di_tv = obj.val; } else { - list_T *const kv_pair = tv_list_alloc(); + list_T *const kv_pair = tv_list_alloc(2); tv_list_append_list(last_container.special_val, kv_pair); tv_list_append_owned_tv(kv_pair, key.val); tv_list_append_owned_tv(kv_pair, obj.val); @@ -221,13 +221,18 @@ static inline int json_decoder_pop(ValuesStackItem obj, /// Create a new special dictionary that ought to represent a MAP /// /// @param[out] ret_tv Address where new special dictionary is saved. +/// @param[in] len Expected number of items to be populated before list +/// becomes accessible from VimL. It is still valid to +/// underpopulate a list, value only controls how many elements +/// will be allocated in advance. @see ListLenSpecials. /// /// @return [allocated] list which should contain key-value pairs. Return value /// may be safely ignored. -list_T *decode_create_map_special_dict(typval_T *const ret_tv) +list_T *decode_create_map_special_dict(typval_T *const ret_tv, + const ptrdiff_t len) FUNC_ATTR_NONNULL_ALL { - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc(len); tv_list_ref(list); create_special_dict(ret_tv, kMPMap, ((typval_T) { .v_type = VAR_LIST, @@ -263,7 +268,7 @@ typval_T decode_string(const char *const s, const size_t len, ? ((s != NULL) && (memchr(s, NUL, len) != NULL)) : (bool)hasnul); if (really_hasnul) { - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc(kListLenMayKnow); tv_list_ref(list); typval_T tv; create_special_dict(&tv, binary ? kMPBinary : kMPString, ((typval_T) { @@ -843,7 +848,7 @@ json_decode_string_cycle_start: break; } case '[': { - list_T *list = tv_list_alloc(); + list_T *list = tv_list_alloc(kListLenMayKnow); tv_list_ref(list); typval_T tv = { .v_type = VAR_LIST, @@ -864,7 +869,7 @@ json_decode_string_cycle_start: list_T *val_list = NULL; if (next_map_special) { next_map_special = false; - val_list = decode_create_map_special_dict(&tv); + val_list = decode_create_map_special_dict(&tv, kListLenMayKnow); } else { dict_T *dict = tv_dict_alloc(); dict->dv_refcount++; @@ -964,7 +969,7 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) .vval = { .v_number = (varnumber_T) mobj.via.u64 }, }; } else { - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc(4); tv_list_ref(list); create_special_dict(rettv, kMPInteger, ((typval_T) { .v_type = VAR_LIST, @@ -987,7 +992,7 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) .vval = { .v_number = (varnumber_T) mobj.via.i64 }, }; } else { - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc(4); tv_list_ref(list); create_special_dict(rettv, kMPInteger, ((typval_T) { .v_type = VAR_LIST, @@ -1033,7 +1038,7 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) break; } case MSGPACK_OBJECT_ARRAY: { - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc((ptrdiff_t)mobj.via.array.size); tv_list_ref(list); *rettv = (typval_T) { .v_type = VAR_LIST, @@ -1085,9 +1090,10 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) } break; msgpack_to_vim_generic_map: {} - list_T *const list = decode_create_map_special_dict(rettv); + list_T *const list = decode_create_map_special_dict( + rettv, (ptrdiff_t)mobj.via.map.size); for (size_t i = 0; i < mobj.via.map.size; i++) { - list_T *const kv_pair = tv_list_alloc(); + list_T *const kv_pair = tv_list_alloc(2); tv_list_append_list(list, kv_pair); typval_T key_tv = { .v_type = VAR_UNKNOWN }; @@ -1107,10 +1113,10 @@ msgpack_to_vim_generic_map: {} break; } case MSGPACK_OBJECT_EXT: { - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc(2); tv_list_ref(list); tv_list_append_number(list, mobj.via.ext.type); - list_T *const ext_val_list = tv_list_alloc(); + list_T *const ext_val_list = tv_list_alloc(kListLenMayKnow); tv_list_append_list(list, ext_val_list); create_special_dict(rettv, kMPExt, ((typval_T) { .v_type = VAR_LIST, diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index ac6c8c8aa6..6f829be0b4 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -132,8 +132,14 @@ void tv_list_watch_fix(list_T *const l, const listitem_T *const item) /// /// Caller should take care of the reference count. /// +/// @param[in] len Expected number of items to be populated before list +/// becomes accessible from VimL. It is still valid to +/// underpopulate a list, value only controls how many elements +/// will be allocated in advance. Currently does nothing. +/// @see ListLenSpecials. +/// /// @return [allocated] new list. -list_T *tv_list_alloc(void) +list_T *tv_list_alloc(const ptrdiff_t len) FUNC_ATTR_NONNULL_RET { list_T *const list = xcalloc(1, sizeof(list_T)); @@ -521,7 +527,7 @@ list_T *tv_list_copy(const vimconv_T *const conv, list_T *const orig, return NULL; } - list_T *copy = tv_list_alloc(); + list_T *copy = tv_list_alloc(tv_list_len(orig)); tv_list_ref(copy); if (copyID != 0) { // Do this before adding the items, because one of the items may @@ -1817,12 +1823,16 @@ void tv_dict_set_keys_readonly(dict_T *const dict) /// Also sets reference count. /// /// @param[out] ret_tv Structure where list is saved. +/// @param[in] len Expected number of items to be populated before list +/// becomes accessible from VimL. It is still valid to +/// underpopulate a list, value only controls how many elements +/// will be allocated in advance. @see ListLenSpecials. /// /// @return [allocated] pointer to the created list. -list_T *tv_list_alloc_ret(typval_T *const ret_tv) +list_T *tv_list_alloc_ret(typval_T *const ret_tv, const ptrdiff_t len) FUNC_ATTR_NONNULL_ALL { - list_T *const l = tv_list_alloc(); + list_T *const l = tv_list_alloc(len); ret_tv->vval.v_list = l; ret_tv->v_type = VAR_LIST; ret_tv->v_lock = VAR_UNLOCKED; diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index c9a9a3e7e8..3993a384d2 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -31,6 +31,25 @@ typedef double float_T; /// Refcount for dict or list that should not be freed enum { DO_NOT_FREE_CNT = (INT_MAX / 2) }; +/// Additional values for tv_list_alloc() len argument +enum { + /// List length is not known in advance + /// + /// To be used when there is neither a way to know how many elements will be + /// needed nor are any educated guesses. + kListLenUnknown = -1, + /// List length *should* be known, but is actually not + /// + /// All occurrences of this value should be eventually removed. This is for + /// the case when the only reason why list length is not known is that it + /// would be hard to code without refactoring, but refactoring is needed. + kListLenShouldKnow = -2, + /// List length may be known in advance, but it requires too much effort + /// + /// To be used when it looks impractical to determine list length. + kListLenMayKnow = -3, +} ListLenSpecials; + /// Maximal possible value of varnumber_T variable #define VARNUMBER_MAX INT64_MAX #define UVARNUMBER_MAX UINT64_MAX diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index ec4ce63e17..28b021d4e4 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -3758,7 +3758,7 @@ static void script_host_execute(char *name, exarg_T *eap) char *const script = script_get(eap, &len); if (script != NULL) { - list_T *const args = tv_list_alloc(); + list_T *const args = tv_list_alloc(3); // script tv_list_append_allocated_string(args, script); // current range @@ -3773,7 +3773,7 @@ static void script_host_execute_file(char *name, exarg_T *eap) uint8_t buffer[MAXPATHL]; vim_FullName((char *)eap->arg, (char *)buffer, sizeof(buffer), false); - list_T *args = tv_list_alloc(); + list_T *args = tv_list_alloc(3); // filename tv_list_append_string(args, (const char *)buffer, -1); // current range @@ -3784,7 +3784,7 @@ static void script_host_execute_file(char *name, exarg_T *eap) static void script_host_do_range(char *name, exarg_T *eap) { - list_T *args = tv_list_alloc(); + list_T *args = tv_list_alloc(3); tv_list_append_number(args, (int)eap->line1); tv_list_append_number(args, (int)eap->line2); tv_list_append_string(args, (const char *)eap->arg, -1); diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 61cb428923..9e3063b164 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -211,7 +211,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) size_t len; const char *s = lua_tolstring(lstate, -2, &len); if (cur.special) { - list_T *const kv_pair = tv_list_alloc(); + list_T *const kv_pair = tv_list_alloc(2); typval_T s_tv = decode_string(s, len, kTrue, false, false); if (s_tv.v_type == VAR_UNKNOWN) { @@ -321,7 +321,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) switch (table_props.type) { case kObjectTypeArray: { cur.tv->v_type = VAR_LIST; - cur.tv->vval.v_list = tv_list_alloc(); + cur.tv->vval.v_list = tv_list_alloc((ptrdiff_t)table_props.maxidx); tv_list_ref(cur.tv->vval.v_list); if (table_props.maxidx != 0) { cur.container = true; @@ -338,7 +338,8 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) } else { cur.special = table_props.has_string_with_nul; if (table_props.has_string_with_nul) { - decode_create_map_special_dict(cur.tv); + decode_create_map_special_dict( + cur.tv, (ptrdiff_t)table_props.string_keys_num); assert(cur.tv->v_type == VAR_DICT); dictitem_T *const val_di = tv_dict_find(cur.tv->vval.v_dict, S_LEN("_VAL")); diff --git a/src/nvim/main.c b/src/nvim/main.c index 0b24023ad0..015df5d070 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -412,7 +412,7 @@ int main(int argc, char **argv) } // It's better to make v:oldfiles an empty list than NULL. if (get_vim_var_list(VV_OLDFILES) == NULL) { - set_vim_var_list(VV_OLDFILES, tv_list_alloc()); + set_vim_var_list(VV_OLDFILES, tv_list_alloc(0)); } /* diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 01c8e94bac..42417f75d5 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -714,7 +714,7 @@ static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes) } } else { // visit recursively all children - list_T *children_list = tv_list_alloc(); + list_T *const children_list = tv_list_alloc(kListLenMayKnow); for (menu = menu->children; menu != NULL; menu = menu->next) { dict_T *dic = menu_get_recursive(menu, modes); if (tv_dict_len(dict) > 0) { diff --git a/src/nvim/ops.c b/src/nvim/ops.c index a5e131190d..b606f10b88 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -2564,7 +2564,7 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) dict_T *dict = get_vim_var_dict(VV_EVENT); // the yanked text - list_T *list = tv_list_alloc(); + list_T *const list = tv_list_alloc((ptrdiff_t)reg->y_size); for (size_t i = 0; i < reg->y_size; i++) { tv_list_append_string(list, (const char *)reg->y_array[i], -1); } @@ -4854,7 +4854,7 @@ static void *get_reg_wrap_one_line(char_u *s, int flags) if (!(flags & kGRegList)) { return s; } - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc(1); tv_list_append_allocated_string(list, (char *)s); return list; } @@ -4904,7 +4904,7 @@ void *get_reg_contents(int regname, int flags) return NULL; if (flags & kGRegList) { - list_T *list = tv_list_alloc(); + list_T *const list = tv_list_alloc((ptrdiff_t)reg->y_size); for (size_t i = 0; i < reg->y_size; i++) { tv_list_append_string(list, (const char *)reg->y_array[i], -1); } @@ -5593,7 +5593,7 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet) } free_register(reg); - list_T *const args = tv_list_alloc(); + list_T *const args = tv_list_alloc(1); const char regname = (char)name; tv_list_append_string(args, ®name, 1); @@ -5712,15 +5712,13 @@ static void set_clipboard(int name, yankreg_T *reg) return; } - list_T *lines = tv_list_alloc(); + list_T *const lines = tv_list_alloc( + (ptrdiff_t)reg->y_size + (reg->y_type != kMTCharWise)); for (size_t i = 0; i < reg->y_size; i++) { tv_list_append_string(lines, (const char *)reg->y_array[i], -1); } - list_T *args = tv_list_alloc(); - tv_list_append_list(args, lines); - char regtype; switch (reg->y_type) { case kMTLineWise: { @@ -5741,10 +5739,11 @@ static void set_clipboard(int name, yankreg_T *reg) assert(false); } } - tv_list_append_string(args, ®type, 1); - const char regname = (char)name; - tv_list_append_string(args, ®name, 1); + list_T *args = tv_list_alloc(3); + tv_list_append_list(args, lines); + tv_list_append_string(args, ®type, 1); + tv_list_append_string(args, ((char[]) { (char)name }), 1); (void)eval_call_provider("clipboard", "set", args); } diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 224e43008d..63252df3dc 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4193,7 +4193,7 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) } } if ((status == OK) && (flags & QF_GETLIST_ITEMS)) { - list_T *l = tv_list_alloc(); + list_T *l = tv_list_alloc(kListLenMayKnow); (void)get_errorlist(wp, qf_idx, l); tv_dict_add_list(retdict, S_LEN("items"), l); } diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index ddc3681867..e4de43b49e 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -7044,7 +7044,7 @@ list_T *reg_submatch_list(int no) colnr_T scol = rsm.sm_mmatch->startpos[no].col; colnr_T ecol = rsm.sm_mmatch->endpos[no].col; - list = tv_list_alloc(); + list = tv_list_alloc(elnum - slnum + 1); s = (const char *)reg_getline_submatch(slnum) + scol; if (slnum == elnum) { @@ -7063,7 +7063,7 @@ list_T *reg_submatch_list(int no) if (s == NULL || rsm.sm_match->endp[no] == NULL) { return NULL; } - list = tv_list_alloc(); + list = tv_list_alloc(1); tv_list_append_string(list, s, (const char *)rsm.sm_match->endp[no] - s); } diff --git a/src/nvim/shada.c b/src/nvim/shada.c index ce9303f14d..c00fd912ec 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1217,7 +1217,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) khash_t(fnamebufs) fname_bufs = KHASH_EMPTY_TABLE(fnamebufs); khash_t(strset) oldfiles_set = KHASH_EMPTY_TABLE(strset); if (get_old_files && (oldfiles_list == NULL || force)) { - oldfiles_list = tv_list_alloc(); + oldfiles_list = tv_list_alloc(kListLenUnknown); set_vim_var_list(VV_OLDFILES, oldfiles_list); } ShaDaReadResult srni_ret; diff --git a/src/nvim/tag.c b/src/nvim/tag.c index be9d621c7d..f23465e501 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -667,7 +667,7 @@ do_tag ( fname = xmalloc(MAXPATHL + 1); cmd = xmalloc(CMDBUFFSIZE + 1); - list = tv_list_alloc(); + list = tv_list_alloc(num_matches); for (i = 0; i < num_matches; ++i) { int len, cmd_len; diff --git a/src/nvim/undo.c b/src/nvim/undo.c index f611a3bb29..b902f82f31 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -2941,17 +2941,20 @@ bool curbufIsChanged(void) && (curbuf->b_changed || file_ff_differs(curbuf, true))); } -/* - * For undotree(): Append the list of undo blocks at "first_uhp" to "list". - * Recursive. - */ -void u_eval_tree(u_header_T *first_uhp, list_T *list) +/// Append the list of undo blocks to a newly allocated list +/// +/// For use in undotree(). Recursive. +/// +/// @param[in] first_uhp Undo blocks list to start with. +/// +/// @return [allocated] List with a representation of undo blocks. +list_T *u_eval_tree(const u_header_T *const first_uhp) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET { - u_header_T *uhp = first_uhp; - dict_T *dict; + list_T *const list = tv_list_alloc(kListLenMayKnow); - while (uhp != NULL) { - dict = tv_dict_alloc(); + for (const u_header_T *uhp = first_uhp; uhp != NULL; uhp = uhp->uh_prev.ptr) { + dict_T *const dict = tv_dict_alloc(); tv_dict_add_nr(dict, S_LEN("seq"), (varnumber_T)uhp->uh_seq); tv_dict_add_nr(dict, S_LEN("time"), (varnumber_T)uhp->uh_time); if (uhp == curbuf->b_u_newhead) { @@ -2965,14 +2968,12 @@ void u_eval_tree(u_header_T *first_uhp, list_T *list) } if (uhp->uh_alt_next.ptr != NULL) { - list_T *alt_list = tv_list_alloc(); - // Recursive call to add alternate undo tree. - u_eval_tree(uhp->uh_alt_next.ptr, alt_list); - tv_dict_add_list(dict, S_LEN("alt"), alt_list); + tv_dict_add_list(dict, S_LEN("alt"), u_eval_tree(uhp->uh_alt_next.ptr)); } tv_list_append_dict(list, dict); - uhp = uhp->uh_prev.ptr; } + + return list; } diff --git a/src/nvim/window.c b/src/nvim/window.c index b687781dfb..4dfc72f212 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -5921,13 +5921,15 @@ void win_get_tabwin(handle_T id, int *tabnr, int *winnr) } } -void win_id2tabwin(typval_T *argvars, list_T *list) +void win_id2tabwin(typval_T *const argvars, typval_T *const rettv) { int winnr = 1; int tabnr = 1; handle_T id = (handle_T)tv_get_number(&argvars[0]); win_get_tabwin(id, &tabnr, &winnr); + + list_T *const list = tv_list_alloc_ret(rettv, 2); tv_list_append_number(list, tabnr); tv_list_append_number(list, winnr); } -- cgit From c10ae4bc8548ca170876e00489fb5d907801e553 Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 3 Jan 2018 03:31:10 +0300 Subject: os/fileio: Fix some flag names in file_* functions documentation --- src/nvim/os/fileio.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c index 5d68473982..d294f9139b 100644 --- a/src/nvim/os/fileio.c +++ b/src/nvim/os/fileio.c @@ -39,9 +39,9 @@ /// @param[in] fname File name to open. /// @param[in] flags Flags, @see FileOpenFlags. Currently reading from and /// writing to the file at once is not supported, so either -/// FILE_WRITE_ONLY or FILE_READ_ONLY is required. +/// kFileWriteOnly or kFileReadOnly is required. /// @param[in] mode Permissions for the newly created file (ignored if flags -/// does not have FILE_CREATE\*). +/// does not have kFileCreate\*). /// /// @return Error code (@see os_strerror()) or 0. int file_open(FileDescriptor *const ret_fp, const char *const fname, @@ -120,7 +120,7 @@ int file_open_fd(FileDescriptor *const ret_fp, const int fd, const bool wr) /// @param[in] fname File name to open. /// @param[in] flags Flags, @see FileOpenFlags. /// @param[in] mode Permissions for the newly created file (ignored if flags -/// does not have FILE_CREATE\*). +/// does not have kFileCreate\*). /// /// @return [allocated] Opened file or NULL in case of error. FileDescriptor *file_open_new(int *const error, const char *const fname, -- cgit From 6a1557f2f496bf8e8f4dad7ed0d423051a7b65e2 Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 3 Jan 2018 04:25:42 +0300 Subject: eval/typval: Log list actions New logging is guarded by cmake LOG_LIST_ACTIONS define. To make it more efficient it is allocated as a linked list with chunks of length 2^(7+chunk_num); that uses basically the same idea as behind increasing kvec length (make appending O(1) (amortized)), but reduces constant by not bothering to move memory around what realloc() would surely do: it is not like we need random access to log entries here to justify usage of a single continuous memory block. --- src/nvim/eval.c | 12 ++++++ src/nvim/eval.lua | 1 + src/nvim/eval/typval.c | 81 +++++++++++++++++++++++++++++++++++++++++ src/nvim/eval/typval.h | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/nvim/memory.c | 2 + 5 files changed, 195 insertions(+) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index ec32f1821d..55396b070d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -16665,6 +16665,18 @@ static void f_test_garbagecollect_now(typval_T *argvars, garbage_collect(true); } +// "test_write_list_log()" function +static void f_test_write_list_log(typval_T *const argvars, + typval_T *const rettv, + FunPtr fptr) +{ + const char *const fname = tv_get_string_chk(&argvars[0]); + if (fname == NULL) { + return; + } + list_write_log(fname); +} + bool callback_from_typval(Callback *const callback, typval_T *const arg) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 54cbc54d78..daa3b637a3 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -313,6 +313,7 @@ return { tempname={}, termopen={args={1, 2}}, test_garbagecollect_now={}, + test_write_list_log={args=1}, timer_info={args={0,1}}, timer_pause={args=2}, timer_start={args={2,3}}, diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 6f829be0b4..c8b550f902 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -31,6 +31,7 @@ #include "nvim/message.h" // TODO(ZyX-I): Move line_breakcheck out of misc1 #include "nvim/misc1.h" // For line_breakcheck +#include "nvim/os/fileio.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/typval.c.generated.h" @@ -45,6 +46,70 @@ bool tv_in_free_unref_items = false; const char *const tv_empty_string = ""; //{{{1 Lists +//{{{2 List log +#ifdef LOG_LIST_ACTIONS +ListLog *list_log_first = NULL; +ListLog *list_log_last = NULL; + +/// Write list log to the given file +/// +/// @param[in] fname File to write log to. Will be appended to if already +/// present. +void list_write_log(const char *const fname) + FUNC_ATTR_NONNULL_ALL +{ + FileDescriptor fp; + const int fo_ret = file_open(&fp, fname, kFileCreate|kFileAppend, 0600); + if (fo_ret != 0) { + emsgf(_("E5142: Failed to open file %s: %s"), fname, os_strerror(fo_ret)); + return; + } + for (ListLog *chunk = list_log_first; chunk != NULL;) { + for (size_t i = 0; i < chunk->size; i++) { + char buf[10 + 1 + ((16 + 3) * 3) + (8 + 2) + 2]; + // act : hex " c:" len "[]" "\n\0" + const ListLogEntry entry = chunk->entries[i]; + const size_t snp_len = (size_t)snprintf( + buf, sizeof(buf), + "%-10.10s: l:%016" PRIxPTR "[%08d] 1:%016" PRIxPTR " 2:%016" PRIxPTR + "\n", + entry.action, entry.l, entry.len, entry.li1, entry.li2); + assert(snp_len + 1 == sizeof(buf)); + const ptrdiff_t fw_ret = file_write(&fp, buf, snp_len); + if (fw_ret != (ptrdiff_t)snp_len) { + assert(fw_ret < 0); + if (i) { + memmove(chunk->entries, chunk->entries + i, + sizeof(chunk->entries[0]) * (chunk->size - i)); + chunk->size -= i; + } + emsgf(_("E5143: Failed to write to file %s: %s"), + fname, os_strerror((int)fw_ret)); + return; + } + } + list_log_first = chunk->next; + xfree(chunk); + chunk = list_log_first; + } + const int fc_ret = file_close(&fp, true); + if (fc_ret != 0) { + emsgf(_("E5144: Failed to close file %s: %s"), fname, os_strerror(fc_ret)); + } +} + +#ifdef EXITFREE +/// Free list log +void list_free_log(void) +{ + for (ListLog *chunk = list_log_first; chunk != NULL;) { + list_log_first = chunk->next; + xfree(chunk); + chunk = list_log_first; + } +} +#endif +#endif //{{{2 List item /// Allocate a list item @@ -151,6 +216,7 @@ list_T *tv_list_alloc(const ptrdiff_t len) list->lv_used_prev = NULL; list->lv_used_next = gc_first_list; gc_first_list = list; + list_log(list, NULL, (void *)(uintptr_t)len, "alloc"); return list; } @@ -180,6 +246,8 @@ void tv_list_init_static10(staticList10_T *const sl) li->li_prev = li - 1; li->li_next = li + 1; } + list_log((const list_T *)sl, &sl->sl_items[0], &sl->sl_items[SL_SIZE - 1], + "s10init"); #undef SL_SIZE } @@ -191,6 +259,7 @@ void tv_list_init_static(list_T *const l) { memset(l, 0, sizeof(*l)); l->lv_refcount = DO_NOT_FREE_CNT; + list_log(l, NULL, NULL, "sinit"); } /// Free items contained in a list @@ -199,6 +268,7 @@ void tv_list_init_static(list_T *const l) void tv_list_free_contents(list_T *const l) FUNC_ATTR_NONNULL_ALL { + list_log(l, NULL, NULL, "freecont"); for (listitem_T *item = l->lv_first; item != NULL; item = l->lv_first) { // Remove the item before deleting it. l->lv_first = item->li_next; @@ -228,6 +298,7 @@ void tv_list_free_list(list_T *const l) if (l->lv_used_next != NULL) { l->lv_used_next->lv_used_prev = l->lv_used_prev; } + list_log(l, NULL, NULL, "freelist"); xfree(l); } @@ -272,6 +343,7 @@ void tv_list_drop_items(list_T *const l, listitem_T *const item, listitem_T *const item2) FUNC_ATTR_NONNULL_ALL { + list_log(l, item, item2, "drop"); // Notify watchers. for (listitem_T *ip = item; ip != item2->li_next; ip = ip->li_next) { l->lv_len--; @@ -289,6 +361,7 @@ void tv_list_drop_items(list_T *const l, listitem_T *const item, item->li_prev->li_next = item2->li_next; } l->lv_idx_item = NULL; + list_log(l, l->lv_first, l->lv_last, "afterdrop"); } /// Like tv_list_drop_items, but also frees all removed items @@ -296,6 +369,7 @@ void tv_list_remove_items(list_T *const l, listitem_T *const item, listitem_T *const item2) FUNC_ATTR_NONNULL_ALL { + list_log(l, item, item2, "remove"); tv_list_drop_items(l, item, item2); for (listitem_T *li = item;;) { tv_clear(TV_LIST_ITEM_TV(li)); @@ -320,6 +394,7 @@ void tv_list_move_items(list_T *const l, listitem_T *const item, const int cnt) FUNC_ATTR_NONNULL_ALL { + list_log(l, item, item2, "move"); tv_list_drop_items(l, item, item2); item->li_prev = tgt_l->lv_last; item2->li_next = NULL; @@ -330,6 +405,7 @@ void tv_list_move_items(list_T *const l, listitem_T *const item, } tgt_l->lv_last = item2; tgt_l->lv_len += cnt; + list_log(tgt_l, tgt_l->lv_first, tgt_l->lv_last, "movetgt"); } /// Insert list item @@ -358,6 +434,7 @@ void tv_list_insert(list_T *const l, listitem_T *const ni, } item->li_prev = ni; l->lv_len++; + list_log(l, ni, item, "insert"); } } @@ -384,6 +461,7 @@ void tv_list_insert_tv(list_T *const l, typval_T *const tv, void tv_list_append(list_T *const l, listitem_T *const item) FUNC_ATTR_NONNULL_ALL { + list_log(l, item, NULL, "append"); if (l->lv_last == NULL) { // empty list l->lv_first = item; @@ -747,6 +825,7 @@ void tv_list_reverse(list_T *const l) if (tv_list_len(l) <= 1) { return; } + list_log(l, NULL, NULL, "reverse"); #define SWAP(a, b) \ do { \ tmp = a; \ @@ -785,6 +864,7 @@ void tv_list_item_sort(list_T *const l, ListSortItem *const ptrs, if (len <= 1) { return; } + list_log(l, NULL, NULL, "sort"); int i = 0; TV_LIST_ITER(l, li, { ptrs[i].item = li; @@ -873,6 +953,7 @@ listitem_T *tv_list_find(list_T *const l, int n) // Cache the used index. l->lv_idx = idx; l->lv_idx_item = item; + list_log(l, l->lv_idx_item, (void *)(uintptr_t)l->lv_idx, "find"); return item; } diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 3993a384d2..40a1738d9e 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -20,6 +20,9 @@ #include "nvim/gettext.h" #include "nvim/message.h" #include "nvim/macros.h" +#ifdef LOG_LIST_ACTIONS +# include "nvim/memory.h" +#endif /// Type used for VimL VAR_NUMBER values typedef int64_t varnumber_T; @@ -323,6 +326,96 @@ typedef struct { typedef int (*ListSorter)(const void *, const void *); +#ifdef LOG_LIST_ACTIONS + +/// List actions log entry +typedef struct { + uintptr_t l; ///< List log entry belongs to. + uintptr_t li1; ///< First list item log entry belongs to, if applicable. + uintptr_t li2; ///< Second list item log entry belongs to, if applicable. + int len; ///< List length when log entry was created. + const char *action; ///< Logged action. +} ListLogEntry; + +typedef struct list_log ListLog; + +/// List actions log +struct list_log { + ListLog *next; ///< Next chunk or NULL. + size_t capacity; ///< Number of entries in current chunk. + size_t size; ///< Current chunk size. + ListLogEntry entries[]; ///< Actual log entries. +}; + +extern ListLog *list_log_first; ///< First list log chunk, NULL if missing +extern ListLog *list_log_last; ///< Last list log chunk + +static inline ListLog *list_log_alloc(const size_t size) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Allocate a new log chunk and update globals +/// +/// @param[in] size Number of entries in a new chunk. +/// +/// @return [allocated] Newly allocated chunk. +static inline ListLog *list_log_new(const size_t size) +{ + ListLog *ret = xmalloc(offsetof(ListLog, entries) + + size * sizeof(ret->entries[0])); + ret->size = 0; + ret->capacity = size; + ret->next = NULL; + if (list_log_first == NULL) { + list_log_first = ret; + } else { + list_log_last->next = ret; + } + list_log_last = ret; + return ret; +} + +static inline void list_log(const list_T *const l, + const listitem_T *const li1, + const listitem_T *const li2, + const char *const action) + REAL_FATTR_ALWAYS_INLINE; + +/// Add new entry to log +/// +/// If last chunk was filled it uses twice as much memory to allocate the next +/// chunk. +/// +/// @param[in] l List to which entry belongs. +/// @param[in] li1 List item 1. +/// @param[in] li2 List item 2, often used for integers and not list items. +/// @param[in] action Logged action. +static inline void list_log(const list_T *const l, + const listitem_T *const li1, + const listitem_T *const li2, + const char *const action) +{ + ListLog *tgt; + if (list_log_first == NULL) { + tgt = list_log_new(128); + } else if (list_log_last->size == list_log_last->capacity) { + tgt = list_log_new(list_log_last->capacity * 2); + } else { + tgt = list_log_last; + } + tgt->entries[tgt->size++] = (ListLogEntry) { + .l = (uintptr_t)l, + .li1 = (uintptr_t)li1, + .li2 = (uintptr_t)li2, + .len = (l == NULL ? 0 : l->lv_len), + .action = action, + }; +} +#else +# define list_log(...) +# define list_write_log(...) +# define list_free_log() +#endif + // In a hashtab item "hi_key" points to "di_key" in a dictitem. // This avoids adding a pointer to the hashtab item. @@ -396,6 +489,7 @@ static inline int tv_list_len(const list_T *const l) /// @param[in] l List to check. static inline int tv_list_len(const list_T *const l) { + list_log(l, NULL, NULL, "len"); if (l == NULL) { return 0; } @@ -479,8 +573,10 @@ static inline listitem_T *tv_list_first(const list_T *const l) static inline listitem_T *tv_list_first(const list_T *const l) { if (l == NULL) { + list_log(l, NULL, NULL, "first"); return NULL; } + list_log(l, l->lv_first, NULL, "first"); return l->lv_first; } @@ -495,8 +591,10 @@ static inline listitem_T *tv_list_last(const list_T *const l) static inline listitem_T *tv_list_last(const list_T *const l) { if (l == NULL) { + list_log(l, NULL, NULL, "last"); return NULL; } + list_log(l, l->lv_last, NULL, "last"); return l->lv_last; } @@ -564,6 +662,7 @@ extern bool tv_in_free_unref_items; #define _TV_LIST_ITER_MOD(modifier, l, li, code) \ do { \ modifier list_T *const l_ = (l); \ + list_log(l_, NULL, NULL, "iter" #modifier); \ if (l_ != NULL) { \ for (modifier listitem_T *li = l_->lv_first; \ li != NULL; li = li->li_next) { \ diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 328b96fd5c..a66ab6a3cc 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -559,6 +559,7 @@ void time_to_bytes(time_t time_, uint8_t buf[8]) #include "nvim/tag.h" #include "nvim/window.h" #include "nvim/os/os.h" +#include "nvim/eval/typval.h" /* * Free everything that we allocated. @@ -692,6 +693,7 @@ void free_all_mem(void) free_screenlines(); clear_hl_tables(); + list_free_log(); } #endif -- cgit From a8cb510a2ed2f53f60ba4b2e722f4bc64954c606 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 9 Jan 2018 12:39:15 +0300 Subject: channel: Make empty output be represented by `['']` again --- src/nvim/channel.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 9d0a7d3c21..265d4d8b89 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -533,6 +533,9 @@ static inline list_T *buffer_to_tv_list(const char *const buf, const size_t len) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE { list_T *const l = tv_list_alloc(kListLenMayKnow); + // Empty buffer should be represented by [''], encode_list_write() thinks + // empty list is fine for the case. + tv_list_append_string(l, "", 0); encode_list_write(l, buf, len); return l; } -- cgit From 4b8d6caf48e28730767ae87a985a345ed6f0d2b3 Mon Sep 17 00:00:00 2001 From: ckelsel Date: Sun, 14 Jan 2018 20:50:05 +0800 Subject: vim-patch:8.0.0380: with 'linebreak' double wide char wraps badly Problem: With 'linebreak' set and 'breakat' includes ">" a double-wide character results in "<<" displayed. Solution: Check for the character not to be replaced. (Ozaki Kiichi, closes vim/vim#1456) https://github.com/vim/vim/commit/38632faf635f6434441827e136bceb5a930c59ad --- src/nvim/screen.c | 26 +++++++++++++------------- src/nvim/testdir/test_listlbr_utf8.vim | 27 +++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/nvim/screen.c b/src/nvim/screen.c index ed96e98d32..8a29734025 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -3143,14 +3143,15 @@ win_line ( } --n_extra; } else { + int c0; + if (p_extra_free != NULL) { xfree(p_extra_free); p_extra_free = NULL; } - /* - * Get a character from the line itself. - */ - c = *ptr; + + // Get a character from the line itself. + c0 = c = *ptr; if (has_mbyte) { mb_c = c; if (enc_utf8) { @@ -3160,11 +3161,12 @@ win_line ( mb_utf8 = FALSE; if (mb_l > 1) { mb_c = utfc_ptr2char(ptr, u8cc); - /* Overlong encoded ASCII or ASCII with composing char - * is displayed normally, except a NUL. */ - if (mb_c < 0x80) - c = mb_c; - mb_utf8 = TRUE; + // Overlong encoded ASCII or ASCII with composing char + // is displayed normally, except a NUL. + if (mb_c < 0x80) { + c0 = c = mb_c; + } + mb_utf8 = true; /* At start of the line we can have a composing char. * Draw it as a space with a composing char. */ @@ -3428,10 +3430,8 @@ win_line ( char_attr = hl_combine_attr(char_attr, term_attrs[vcol]); } - /* - * Found last space before word: check for line break. - */ - if (wp->w_p_lbr && vim_isbreak(c) && !vim_isbreak(*ptr)) { + // Found last space before word: check for line break. + if (wp->w_p_lbr && c0 == c && vim_isbreak(c) && !vim_isbreak(*ptr)) { int mb_off = has_mbyte ? (*mb_head_off)(line, ptr - 1) : 0; char_u *p = ptr - (mb_off + 1); // TODO: is passing p for start of the line OK? diff --git a/src/nvim/testdir/test_listlbr_utf8.vim b/src/nvim/testdir/test_listlbr_utf8.vim index 980d67d49d..56a4cc9b31 100644 --- a/src/nvim/testdir/test_listlbr_utf8.vim +++ b/src/nvim/testdir/test_listlbr_utf8.vim @@ -194,6 +194,33 @@ func Test_multibyte_sign_and_colorcolumn() call s:close_windows() endfunc +func Test_illegal_byte_and_breakat() + call s:test_windows("setl sbr= brk+=<") + vert resize 18 + call setline(1, repeat("\x80", 6)) + redraw! + let lines = s:screen_lines([1, 2], winwidth(0)) + let expect = [ +\ "<80><80><80><80><8", +\ "0><80> ", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows('setl brk&vim') +endfunc + +func Test_multibyte_wrap_and_breakat() + call s:test_windows("setl sbr= brk+=>") + call setline(1, repeat('a', 17) . repeat('あ', 2)) + redraw! + let lines = s:screen_lines([1, 2], winwidth(0)) + let expect = [ +\ "aaaaaaaaaaaaaaaaaあ>", +\ "あ ", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows('setl brk&vim') +endfunc + func Test_chinese_char_on_wrap_column() call s:test_windows("setl nolbr wrap sbr=") syntax off -- cgit From 7faeaf9f243a8b955b73960d3bba9a34fce3132d Mon Sep 17 00:00:00 2001 From: ckelsel Date: Sun, 14 Jan 2018 20:50:49 +0800 Subject: vim-patch:8.0.0381: diff mode is not sufficiently tested Problem: Diff mode is not sufficiently tested. Solution: Add more diff mode tests. (Dominique Pelle, closes vim/vim#1515) https://github.com/vim/vim/commit/aeb661e1f4a491286ef7af8c3105aff1f3b16f1c --- src/nvim/testdir/test_diffmode.vim | 75 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 8ee82bd538..c3d4be9cf9 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -272,3 +272,78 @@ func Test_setting_cursor() call delete('Xtest1') call delete('Xtest2') endfunc + +func Test_diff_move_to() + new + call setline(1, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + diffthis + vnew + call setline(1, [1, '2x', 3, 4, 4, 5, '6x', 7, '8x', 9, '10x']) + diffthis + norm ]c + call assert_equal(2, line('.')) + norm 3]c + call assert_equal(9, line('.')) + norm 10]c + call assert_equal(11, line('.')) + norm [c + call assert_equal(9, line('.')) + norm 2[c + call assert_equal(5, line('.')) + norm 10[c + call assert_equal(2, line('.')) + %bwipe! +endfunc + +func Test_diffpatch() + " The patch program on MS-Windows may fail or hang. + if !executable('patch') || !has('unix') + return + endif + new + insert +*************** +*** 1,3 **** + 1 +! 2 + 3 +--- 1,4 ---- + 1 +! 2x + 3 ++ 4 +. + saveas Xpatch + bwipe! + new + call assert_fails('diffpatch Xpatch', 'E816:') + call setline(1, ['1', '2', '3']) + diffpatch Xpatch + call assert_equal(['1', '2x', '3', '4'], getline(1, '$')) + call delete('Xpatch') + bwipe! +endfunc + +func Test_diff_too_many_buffers() + for i in range(1, 8) + exe "new Xtest" . i + diffthis + endfor + new Xtest9 + call assert_fails('diffthis', 'E96:') + %bwipe! +endfunc + +func Test_diff_nomodifiable() + new + call setline(1, [1, 2, 3, 4]) + setl nomodifiable + diffthis + vnew + call setline(1, ['1x', 2, 3, 3, 4]) + diffthis + call assert_fails('norm dp', 'E793:') + setl nomodifiable + call assert_fails('norm do', 'E21:') + %bwipe! +endfunc -- cgit From fe4ba6995895f95f461d40eda356cc7e1dbb1abf Mon Sep 17 00:00:00 2001 From: ckelsel Date: Sun, 14 Jan 2018 20:52:51 +0800 Subject: vim-patch:8.0.0385: no tests for arabic Problem: No tests for arabic. Solution: Add a first test for arabic. (Dominique Pelle, closes vim/vim#1518) https://github.com/vim/vim/commit/b5e8377364110ee70090274da15d202778e96a64 --- src/nvim/testdir/Makefile | 1 + src/nvim/testdir/test_arabic.vim | 92 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 src/nvim/testdir/test_arabic.vim (limited to 'src') diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index a23dacba15..0c25945274 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -40,6 +40,7 @@ SCRIPTS ?= $(SCRIPTS_DEFAULT) # Tests using runtest.vim. # Keep test_alot*.res as the last one, sort the others. NEW_TESTS ?= \ + test_arabic.vim \ test_autocmd.res \ test_bufwintabinfo.res \ test_changedtick.res \ diff --git a/src/nvim/testdir/test_arabic.vim b/src/nvim/testdir/test_arabic.vim new file mode 100644 index 0000000000..b9e033462e --- /dev/null +++ b/src/nvim/testdir/test_arabic.vim @@ -0,0 +1,92 @@ +" Simplistic testing of Arabic mode. + +if !has('arabic') + finish +endif + +set encoding=utf-8 +scriptencoding utf-8 + +" Return list of utf8 sequences of each character at line lnum. +" Combining characters are treated as a single item. +func GetCharsUtf8(lnum) + call cursor(a:lnum, 1) + let chars = [] + let numchars = strchars(getline('.'), 1) + for i in range(1, numchars) + exe 'norm ' i . '|' + call add(chars, execute('norm g8')) + endfor + return chars +endfunc + +func Test_arabic_toggle() + set arabic + call assert_equal(1, &rightleft) + call assert_equal(1, &arabicshape) + call assert_equal('arabic', &keymap) + call assert_equal(1, &delcombine) + + set iminsert=1 imsearch=1 + set arabic& + call assert_equal(0, &rightleft) + call assert_equal(1, &arabicshape) + call assert_equal('arabic', &keymap) + call assert_equal(1, &delcombine) + call assert_equal(0, &iminsert) + call assert_equal(-1, &imsearch) + + set arabicshape& keymap= delcombine& +endfunc + +func Test_arabic_input() + new + set arabic + " Typing sghl in Arabic insert mode should show the + " Arabic word 'Salaam' i.e. 'peace'. + call feedkeys('isghl', 'tx') + redraw + call assert_equal([ + \ "\nd8 b3 ", + \ "\nd9 84 + d8 a7 ", + \ "\nd9 85 "], GetCharsUtf8(1)) + + " Without shaping, it should give individual Arabic letters. + set noarabicshape + redraw + call assert_equal([ + \ "\nd8 b3 ", + \ "\nd9 84 ", + \ "\nd8 a7 ", + \ "\nd9 85 "], GetCharsUtf8(1)) + + set arabicshape& + set arabic& + bwipe! +endfunc + +func Test_arabic_toggle_keymap() + new + set arabic + call feedkeys("i12\12\12", 'tx') + redraw + call assert_equal('١٢12١٢', getline('.')) + set arabic& + bwipe! +endfunc + +func Test_delcombine() + new + set arabic + call feedkeys("isghl\\", 'tx') + redraw + call assert_equal(["\nd8 b3 ", "\nd9 84 "], GetCharsUtf8(1)) + + " Now the same with nodelcombine + set nodelcombine + %d + call feedkeys("isghl\\", 'tx') + call assert_equal(["\nd8 b3 "], GetCharsUtf8(1)) + set arabic& + bwipe! +endfunc -- cgit From fc97e9fbdf372e52370ca8640422327d8a0c8cb5 Mon Sep 17 00:00:00 2001 From: ckelsel Date: Mon, 15 Jan 2018 17:32:54 +0800 Subject: vim-patch:8.0.0389: test for arabic does not check what is displayed Problem: Test for arabic does not check what is displayed. Solution: Improve what is asserted. (Dominique Pelle, closes vim/vim#1523) Add a first shaping test. https://github.com/vim/vim/commit/5342f00ff95ed0256b8183063a83d72112f1243c --- src/nvim/testdir/test_arabic.vim | 89 +++++++++++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_arabic.vim b/src/nvim/testdir/test_arabic.vim index b9e033462e..9e648a9617 100644 --- a/src/nvim/testdir/test_arabic.vim +++ b/src/nvim/testdir/test_arabic.vim @@ -1,21 +1,23 @@ " Simplistic testing of Arabic mode. -if !has('arabic') +if !has('arabic') || !has('multi_byte') finish endif -set encoding=utf-8 -scriptencoding utf-8 +source view_util.vim -" Return list of utf8 sequences of each character at line lnum. +" Return list of Unicode characters at line lnum. " Combining characters are treated as a single item. -func GetCharsUtf8(lnum) +func s:get_chars(lnum) call cursor(a:lnum, 1) let chars = [] let numchars = strchars(getline('.'), 1) for i in range(1, numchars) exe 'norm ' i . '|' - call add(chars, execute('norm g8')) + let c=execute('ascii') + let c=substitute(c, '\n\?<.\{-}Hex\s*', 'U+', 'g') + let c=substitute(c, ',\s*Octal\s*\d*', '', 'g') + call add(chars, c) endfor return chars endfunc @@ -43,25 +45,28 @@ func Test_arabic_input() new set arabic " Typing sghl in Arabic insert mode should show the - " Arabic word 'Salaam' i.e. 'peace'. - call feedkeys('isghl', 'tx') - redraw + " Arabic word 'Salaam' i.e. 'peace', spelled: + " SEEN, LAM, ALEF, MEEM. + " See: https://www.mediawiki.org/wiki/VisualEditor/Typing/Right-to-left + call feedkeys('isghl!', 'tx') + call assert_match("^ *!\uFEE1\uFEFC\uFEB3$", ScreenLines(1, &columns)[0]) call assert_equal([ - \ "\nd8 b3 ", - \ "\nd9 84 + d8 a7 ", - \ "\nd9 85 "], GetCharsUtf8(1)) + \ 'U+0633', + \ 'U+0644 U+0627', + \ 'U+0645', + \ 'U+21'], s:get_chars(1)) " Without shaping, it should give individual Arabic letters. set noarabicshape - redraw + call assert_match("^ *!\u0645\u0627\u0644\u0633$", ScreenLines(1, &columns)[0]) call assert_equal([ - \ "\nd8 b3 ", - \ "\nd9 84 ", - \ "\nd8 a7 ", - \ "\nd9 85 "], GetCharsUtf8(1)) + \ 'U+0633', + \ 'U+0644', + \ 'U+0627', + \ 'U+0645', + \ 'U+21'], s:get_chars(1)) - set arabicshape& - set arabic& + set arabic& arabicshape& bwipe! endfunc @@ -69,7 +74,7 @@ func Test_arabic_toggle_keymap() new set arabic call feedkeys("i12\12\12", 'tx') - redraw + call assert_match("^ *٢١21٢١$", ScreenLines(1, &columns)[0]) call assert_equal('١٢12١٢', getline('.')) set arabic& bwipe! @@ -79,14 +84,50 @@ func Test_delcombine() new set arabic call feedkeys("isghl\\", 'tx') - redraw - call assert_equal(["\nd8 b3 ", "\nd9 84 "], GetCharsUtf8(1)) + call assert_match("^ *\uFEDE\uFEB3$", ScreenLines(1, &columns)[0]) + call assert_equal(['U+0633', 'U+0644'], s:get_chars(1)) - " Now the same with nodelcombine + " Now the same with 'nodelcombine' set nodelcombine %d call feedkeys("isghl\\", 'tx') - call assert_equal(["\nd8 b3 "], GetCharsUtf8(1)) + call assert_match("^ *\uFEB1$", ScreenLines(1, &columns)[0]) + call assert_equal(['U+0633'], s:get_chars(1)) set arabic& bwipe! endfunc + +let s:a_YEH_HAMZA = "\u0626" +let s:a_i_YEH_HAMZA = "\ufe8b" + +let s:a_HAMZA = "\u0621" +let s:a_s_HAMZA = "\ufe80" + +let s:a_ALEF_MADDA = "\u0622" +let s:a_s_ALEF_MADDA = "\ufe81" + +let s:a_ALEF_HAMZA_ABOVE = "\u0623" +let s:a_s_ALEF_HAMZA_ABOVE = "\ufe83" + +let s:a_GHAIN = "\u063a" +let s:a_f_GHAIN = "\ufece" +let s:a_s_GHAIN = "\ufecd" + +func Test_shape_initial() + new + set arabicshape + + " Shaping arabic {testchar} non-arabic Uses chg_c_a2i(). + " pair[0] = testchar, pair[1] = next-result, pair[2] = current-result + for pair in [[s:a_YEH_HAMZA, s:a_f_GHAIN, s:a_i_YEH_HAMZA], + \ [s:a_HAMZA, s:a_s_GHAIN, s:a_s_HAMZA], + \ [s:a_ALEF_MADDA, s:a_s_GHAIN, s:a_s_ALEF_MADDA], + \ [s:a_ALEF_HAMZA_ABOVE, s:a_s_GHAIN, s:a_s_ALEF_HAMZA_ABOVE], + \ ] + call setline(1, s:a_GHAIN . pair[0] . ' ') + call assert_equal([pair[1] . pair[2] . ' '], ScreenLines(1, 3)) + endfor + + set arabicshape& + bwipe! +endfunc -- cgit From 770ec228c7e47c63ba549e67bfdaae9aa574ecce Mon Sep 17 00:00:00 2001 From: ckelsel Date: Mon, 15 Jan 2018 17:33:34 +0800 Subject: vim-patch:8.0.0391: arabic support is verbose and not well tested Problem: Arabic support is verbose and not well tested. Solution: Simplify the code. Add more tests. https://github.com/vim/vim/commit/5f53dd3f747711be90879fa2f22a207970b86750 --- src/nvim/arabic.c | 382 ++++++++------------------------------- src/nvim/testdir/test_arabic.vim | 359 +++++++++++++++++++++++++++++++++++- 2 files changed, 425 insertions(+), 316 deletions(-) (limited to 'src') diff --git a/src/nvim/arabic.c b/src/nvim/arabic.c index 1ef51d2a2a..f0370af704 100644 --- a/src/nvim/arabic.c +++ b/src/nvim/arabic.c @@ -393,323 +393,93 @@ static bool A_is_f(int cur_c) // Change shape - from ISO-8859-6/Isolated to Form-B Isolated static int chg_c_a2s(int cur_c) { - int tempc; - switch (cur_c) { - case a_HAMZA: - tempc = a_s_HAMZA; - break; - - case a_ALEF_MADDA: - tempc = a_s_ALEF_MADDA; - break; - - case a_ALEF_HAMZA_ABOVE: - tempc = a_s_ALEF_HAMZA_ABOVE; - break; - - case a_WAW_HAMZA: - tempc = a_s_WAW_HAMZA; - break; - - case a_ALEF_HAMZA_BELOW: - tempc = a_s_ALEF_HAMZA_BELOW; - break; - - case a_YEH_HAMZA: - tempc = a_s_YEH_HAMZA; - break; - - case a_ALEF: - tempc = a_s_ALEF; - break; - - case a_TEH_MARBUTA: - tempc = a_s_TEH_MARBUTA; - break; - - case a_DAL: - tempc = a_s_DAL; - break; - - case a_THAL: - tempc = a_s_THAL; - break; - - case a_REH: - tempc = a_s_REH; - break; - - case a_ZAIN: - tempc = a_s_ZAIN; - break; - - case a_TATWEEL: // exceptions - tempc = cur_c; - break; - - case a_WAW: - tempc = a_s_WAW; - break; - - case a_ALEF_MAKSURA: - tempc = a_s_ALEF_MAKSURA; - break; - - case a_BEH: - tempc = a_s_BEH; - break; - - case a_TEH: - tempc = a_s_TEH; - break; - - case a_THEH: - tempc = a_s_THEH; - break; - - case a_JEEM: - tempc = a_s_JEEM; - break; - - case a_HAH: - tempc = a_s_HAH; - break; - - case a_KHAH: - tempc = a_s_KHAH; - break; - - case a_SEEN: - tempc = a_s_SEEN; - break; - - case a_SHEEN: - tempc = a_s_SHEEN; - break; - - case a_SAD: - tempc = a_s_SAD; - break; - - case a_DAD: - tempc = a_s_DAD; - break; - - case a_TAH: - tempc = a_s_TAH; - break; - - case a_ZAH: - tempc = a_s_ZAH; - break; - - case a_AIN: - tempc = a_s_AIN; - break; - - case a_GHAIN: - tempc = a_s_GHAIN; - break; - - case a_FEH: - tempc = a_s_FEH; - break; - - case a_QAF: - tempc = a_s_QAF; - break; - - case a_KAF: - tempc = a_s_KAF; - break; - - case a_LAM: - tempc = a_s_LAM; - break; - - case a_MEEM: - tempc = a_s_MEEM; - break; - - case a_NOON: - tempc = a_s_NOON; - break; - - case a_HEH: - tempc = a_s_HEH; - break; - - case a_YEH: - tempc = a_s_YEH; - break; - - default: - tempc = 0; + case a_HAMZA: return a_s_HAMZA; + case a_ALEF_MADDA: return a_s_ALEF_MADDA; + case a_ALEF_HAMZA_ABOVE: return a_s_ALEF_HAMZA_ABOVE; + case a_WAW_HAMZA: return a_s_WAW_HAMZA; + case a_ALEF_HAMZA_BELOW: return a_s_ALEF_HAMZA_BELOW; + case a_YEH_HAMZA: return a_s_YEH_HAMZA; + case a_ALEF: return a_s_ALEF; + case a_TEH_MARBUTA: return a_s_TEH_MARBUTA; + case a_DAL: return a_s_DAL; + case a_THAL: return a_s_THAL; + case a_REH: return a_s_REH; + case a_ZAIN: return a_s_ZAIN; + case a_TATWEEL: return cur_c; // exceptions + case a_WAW: return a_s_WAW; + case a_ALEF_MAKSURA: return a_s_ALEF_MAKSURA; + case a_BEH: return a_s_BEH; + case a_TEH: return a_s_TEH; + case a_THEH: return a_s_THEH; + case a_JEEM: return a_s_JEEM; + case a_HAH: return a_s_HAH; + case a_KHAH: return a_s_KHAH; + case a_SEEN: return a_s_SEEN; + case a_SHEEN: return a_s_SHEEN; + case a_SAD: return a_s_SAD; + case a_DAD: return a_s_DAD; + case a_TAH: return a_s_TAH; + case a_ZAH: return a_s_ZAH; + case a_AIN: return a_s_AIN; + case a_GHAIN: return a_s_GHAIN; + case a_FEH: return a_s_FEH; + case a_QAF: return a_s_QAF; + case a_KAF: return a_s_KAF; + case a_LAM: return a_s_LAM; + case a_MEEM: return a_s_MEEM; + case a_NOON: return a_s_NOON; + case a_HEH: return a_s_HEH; + case a_YEH: return a_s_YEH; } - return tempc; + return 0; } // Change shape - from ISO-8859-6/Isolated to Initial static int chg_c_a2i(int cur_c) { - int tempc; - switch (cur_c) { - case a_YEH_HAMZA: - tempc = a_i_YEH_HAMZA; - break; - - case a_HAMZA: // exceptions - tempc = a_s_HAMZA; - break; - - case a_ALEF_MADDA: // exceptions - tempc = a_s_ALEF_MADDA; - break; - - case a_ALEF_HAMZA_ABOVE: // exceptions - tempc = a_s_ALEF_HAMZA_ABOVE; - break; - - case a_WAW_HAMZA: // exceptions - tempc = a_s_WAW_HAMZA; - break; - - case a_ALEF_HAMZA_BELOW: // exceptions - tempc = a_s_ALEF_HAMZA_BELOW; - break; - - case a_ALEF: // exceptions - tempc = a_s_ALEF; - break; - - case a_TEH_MARBUTA: // exceptions - tempc = a_s_TEH_MARBUTA; - break; - - case a_DAL: // exceptions - tempc = a_s_DAL; - break; - - case a_THAL: // exceptions - tempc = a_s_THAL; - break; - - case a_REH: // exceptions - tempc = a_s_REH; - break; - - case a_ZAIN: // exceptions - tempc = a_s_ZAIN; - break; - - case a_TATWEEL: // exceptions - tempc = cur_c; - break; - - case a_WAW: // exceptions - tempc = a_s_WAW; - break; - - case a_ALEF_MAKSURA: // exceptions - tempc = a_s_ALEF_MAKSURA; - break; - - case a_BEH: - tempc = a_i_BEH; - break; - - case a_TEH: - tempc = a_i_TEH; - break; - - case a_THEH: - tempc = a_i_THEH; - break; - - case a_JEEM: - tempc = a_i_JEEM; - break; - - case a_HAH: - tempc = a_i_HAH; - break; - - case a_KHAH: - tempc = a_i_KHAH; - break; - - case a_SEEN: - tempc = a_i_SEEN; - break; - - case a_SHEEN: - tempc = a_i_SHEEN; - break; - - case a_SAD: - tempc = a_i_SAD; - break; - - case a_DAD: - tempc = a_i_DAD; - break; - - case a_TAH: - tempc = a_i_TAH; - break; - - case a_ZAH: - tempc = a_i_ZAH; - break; - - case a_AIN: - tempc = a_i_AIN; - break; - - case a_GHAIN: - tempc = a_i_GHAIN; - break; - - case a_FEH: - tempc = a_i_FEH; - break; - - case a_QAF: - tempc = a_i_QAF; - break; - - case a_KAF: - tempc = a_i_KAF; - break; - - case a_LAM: - tempc = a_i_LAM; - break; - - case a_MEEM: - tempc = a_i_MEEM; - break; - - case a_NOON: - tempc = a_i_NOON; - break; - - case a_HEH: - tempc = a_i_HEH; - break; - - case a_YEH: - tempc = a_i_YEH; - break; - - default: - tempc = 0; + case a_YEH_HAMZA: return a_i_YEH_HAMZA; + case a_HAMZA: return a_s_HAMZA; // exceptions + case a_ALEF_MADDA: return a_s_ALEF_MADDA; // exceptions + case a_ALEF_HAMZA_ABOVE: return a_s_ALEF_HAMZA_ABOVE; // exceptions + case a_WAW_HAMZA: return a_s_WAW_HAMZA; // exceptions + case a_ALEF_HAMZA_BELOW: return a_s_ALEF_HAMZA_BELOW; // exceptions + case a_ALEF: return a_s_ALEF; // exceptions + case a_TEH_MARBUTA: return a_s_TEH_MARBUTA; // exceptions + case a_DAL: return a_s_DAL; // exceptions + case a_THAL: return a_s_THAL; // exceptions + case a_REH: return a_s_REH; // exceptions + case a_ZAIN: return a_s_ZAIN; // exceptions + case a_TATWEEL: return cur_c; // exceptions + case a_WAW: return a_s_WAW; // exceptions + case a_ALEF_MAKSURA: return a_s_ALEF_MAKSURA; // exceptions + case a_BEH: return a_i_BEH; + case a_TEH: return a_i_TEH; + case a_THEH: return a_i_THEH; + case a_JEEM: return a_i_JEEM; + case a_HAH: return a_i_HAH; + case a_KHAH: return a_i_KHAH; + case a_SEEN: return a_i_SEEN; + case a_SHEEN: return a_i_SHEEN; + case a_SAD: return a_i_SAD; + case a_DAD: return a_i_DAD; + case a_TAH: return a_i_TAH; + case a_ZAH: return a_i_ZAH; + case a_AIN: return a_i_AIN; + case a_GHAIN: return a_i_GHAIN; + case a_FEH: return a_i_FEH; + case a_QAF: return a_i_QAF; + case a_KAF: return a_i_KAF; + case a_LAM: return a_i_LAM; + case a_MEEM: return a_i_MEEM; + case a_NOON: return a_i_NOON; + case a_HEH: return a_i_HEH; + case a_YEH: return a_i_YEH; } - return tempc; + return 0; } // Change shape - from ISO-8859-6/Isolated to Medial diff --git a/src/nvim/testdir/test_arabic.vim b/src/nvim/testdir/test_arabic.vim index 9e648a9617..9a16833a4c 100644 --- a/src/nvim/testdir/test_arabic.vim +++ b/src/nvim/testdir/test_arabic.vim @@ -97,32 +97,265 @@ func Test_delcombine() bwipe! endfunc +" Values from src/arabic.h (not all used yet) +let s:a_COMMA = "\u060C" +let s:a_SEMICOLON = "\u061B" +let s:a_QUESTION = "\u061F" +let s:a_HAMZA = "\u0621" +let s:a_ALEF_MADDA = "\u0622" +let s:a_ALEF_HAMZA_ABOVE = "\u0623" +let s:a_WAW_HAMZA = "\u0624" +let s:a_ALEF_HAMZA_BELOW = "\u0625" let s:a_YEH_HAMZA = "\u0626" -let s:a_i_YEH_HAMZA = "\ufe8b" +let s:a_ALEF = "\u0627" +let s:a_BEH = "\u0628" +let s:a_TEH_MARBUTA = "\u0629" +let s:a_TEH = "\u062a" +let s:a_THEH = "\u062b" +let s:a_JEEM = "\u062c" +let s:a_HAH = "\u062d" +let s:a_KHAH = "\u062e" +let s:a_DAL = "\u062f" +let s:a_THAL = "\u0630" +let s:a_REH = "\u0631" +let s:a_ZAIN = "\u0632" +let s:a_SEEN = "\u0633" +let s:a_SHEEN = "\u0634" +let s:a_SAD = "\u0635" +let s:a_DAD = "\u0636" +let s:a_TAH = "\u0637" +let s:a_ZAH = "\u0638" +let s:a_AIN = "\u0639" +let s:a_GHAIN = "\u063a" +let s:a_TATWEEL = "\u0640" +let s:a_FEH = "\u0641" +let s:a_QAF = "\u0642" +let s:a_KAF = "\u0643" +let s:a_LAM = "\u0644" +let s:a_MEEM = "\u0645" +let s:a_NOON = "\u0646" +let s:a_HEH = "\u0647" +let s:a_WAW = "\u0648" +let s:a_ALEF_MAKSURA = "\u0649" +let s:a_YEH = "\u064a" -let s:a_HAMZA = "\u0621" -let s:a_s_HAMZA = "\ufe80" +let s:a_FATHATAN = "\u064b" +let s:a_DAMMATAN = "\u064c" +let s:a_KASRATAN = "\u064d" +let s:a_FATHA = "\u064e" +let s:a_DAMMA = "\u064f" +let s:a_KASRA = "\u0650" +let s:a_SHADDA = "\u0651" +let s:a_SUKUN = "\u0652" -let s:a_ALEF_MADDA = "\u0622" -let s:a_s_ALEF_MADDA = "\ufe81" +let s:a_MADDA_ABOVE = "\u0653" +let s:a_HAMZA_ABOVE = "\u0654" +let s:a_HAMZA_BELOW = "\u0655" -let s:a_ALEF_HAMZA_ABOVE = "\u0623" -let s:a_s_ALEF_HAMZA_ABOVE = "\ufe83" +let s:a_ZERO = "\u0660" +let s:a_ONE = "\u0661" +let s:a_TWO = "\u0662" +let s:a_THREE = "\u0663" +let s:a_FOUR = "\u0664" +let s:a_FIVE = "\u0665" +let s:a_SIX = "\u0666" +let s:a_SEVEN = "\u0667" +let s:a_EIGHT = "\u0668" +let s:a_NINE = "\u0669" +let s:a_PERCENT = "\u066a" +let s:a_DECIMAL = "\u066b" +let s:a_THOUSANDS = "\u066c" +let s:a_STAR = "\u066d" +let s:a_MINI_ALEF = "\u0670" -let s:a_GHAIN = "\u063a" -let s:a_f_GHAIN = "\ufece" +let s:a_s_FATHATAN = "\ufe70" +let s:a_m_TATWEEL_FATHATAN = "\ufe71" +let s:a_s_DAMMATAN = "\ufe72" + +let s:a_s_KASRATAN = "\ufe74" + +let s:a_s_FATHA = "\ufe76" +let s:a_m_FATHA = "\ufe77" +let s:a_s_DAMMA = "\ufe78" +let s:a_m_DAMMA = "\ufe79" +let s:a_s_KASRA = "\ufe7a" +let s:a_m_KASRA = "\ufe7b" +let s:a_s_SHADDA = "\ufe7c" +let s:a_m_SHADDA = "\ufe7d" +let s:a_s_SUKUN = "\ufe7e" +let s:a_m_SUKUN = "\ufe7f" + +let s:a_s_HAMZA = "\ufe80" +let s:a_s_ALEF_MADDA = "\ufe81" +let s:a_f_ALEF_MADDA = "\ufe82" +let s:a_s_ALEF_HAMZA_ABOVE = "\ufe83" +let s:a_f_ALEF_HAMZA_ABOVE = "\ufe84" +let s:a_s_WAW_HAMZA = "\ufe85" +let s:a_f_WAW_HAMZA = "\ufe86" +let s:a_s_ALEF_HAMZA_BELOW = "\ufe87" +let s:a_f_ALEF_HAMZA_BELOW = "\ufe88" +let s:a_s_YEH_HAMZA = "\ufe89" +let s:a_f_YEH_HAMZA = "\ufe8a" +let s:a_i_YEH_HAMZA = "\ufe8b" +let s:a_m_YEH_HAMZA = "\ufe8c" +let s:a_s_ALEF = "\ufe8d" +let s:a_f_ALEF = "\ufe8e" +let s:a_s_BEH = "\ufe8f" +let s:a_f_BEH = "\ufe90" +let s:a_i_BEH = "\ufe91" +let s:a_m_BEH = "\ufe92" +let s:a_s_TEH_MARBUTA = "\ufe93" +let s:a_f_TEH_MARBUTA = "\ufe94" +let s:a_s_TEH = "\ufe95" +let s:a_f_TEH = "\ufe96" +let s:a_i_TEH = "\ufe97" +let s:a_m_TEH = "\ufe98" +let s:a_s_THEH = "\ufe99" +let s:a_f_THEH = "\ufe9a" +let s:a_i_THEH = "\ufe9b" +let s:a_m_THEH = "\ufe9c" +let s:a_s_JEEM = "\ufe9d" +let s:a_f_JEEM = "\ufe9e" +let s:a_i_JEEM = "\ufe9f" +let s:a_m_JEEM = "\ufea0" +let s:a_s_HAH = "\ufea1" +let s:a_f_HAH = "\ufea2" +let s:a_i_HAH = "\ufea3" +let s:a_m_HAH = "\ufea4" +let s:a_s_KHAH = "\ufea5" +let s:a_f_KHAH = "\ufea6" +let s:a_i_KHAH = "\ufea7" +let s:a_m_KHAH = "\ufea8" +let s:a_s_DAL = "\ufea9" +let s:a_f_DAL = "\ufeaa" +let s:a_s_THAL = "\ufeab" +let s:a_f_THAL = "\ufeac" +let s:a_s_REH = "\ufead" +let s:a_f_REH = "\ufeae" +let s:a_s_ZAIN = "\ufeaf" +let s:a_f_ZAIN = "\ufeb0" +let s:a_s_SEEN = "\ufeb1" +let s:a_f_SEEN = "\ufeb2" +let s:a_i_SEEN = "\ufeb3" +let s:a_m_SEEN = "\ufeb4" +let s:a_s_SHEEN = "\ufeb5" +let s:a_f_SHEEN = "\ufeb6" +let s:a_i_SHEEN = "\ufeb7" +let s:a_m_SHEEN = "\ufeb8" +let s:a_s_SAD = "\ufeb9" +let s:a_f_SAD = "\ufeba" +let s:a_i_SAD = "\ufebb" +let s:a_m_SAD = "\ufebc" +let s:a_s_DAD = "\ufebd" +let s:a_f_DAD = "\ufebe" +let s:a_i_DAD = "\ufebf" +let s:a_m_DAD = "\ufec0" +let s:a_s_TAH = "\ufec1" +let s:a_f_TAH = "\ufec2" +let s:a_i_TAH = "\ufec3" +let s:a_m_TAH = "\ufec4" +let s:a_s_ZAH = "\ufec5" +let s:a_f_ZAH = "\ufec6" +let s:a_i_ZAH = "\ufec7" +let s:a_m_ZAH = "\ufec8" +let s:a_s_AIN = "\ufec9" +let s:a_f_AIN = "\ufeca" +let s:a_i_AIN = "\ufecb" +let s:a_m_AIN = "\ufecc" let s:a_s_GHAIN = "\ufecd" +let s:a_f_GHAIN = "\ufece" +let s:a_i_GHAIN = "\ufecf" +let s:a_m_GHAIN = "\ufed0" +let s:a_s_FEH = "\ufed1" +let s:a_f_FEH = "\ufed2" +let s:a_i_FEH = "\ufed3" +let s:a_m_FEH = "\ufed4" +let s:a_s_QAF = "\ufed5" +let s:a_f_QAF = "\ufed6" +let s:a_i_QAF = "\ufed7" +let s:a_m_QAF = "\ufed8" +let s:a_s_KAF = "\ufed9" +let s:a_f_KAF = "\ufeda" +let s:a_i_KAF = "\ufedb" +let s:a_m_KAF = "\ufedc" +let s:a_s_LAM = "\ufedd" +let s:a_f_LAM = "\ufede" +let s:a_i_LAM = "\ufedf" +let s:a_m_LAM = "\ufee0" +let s:a_s_MEEM = "\ufee1" +let s:a_f_MEEM = "\ufee2" +let s:a_i_MEEM = "\ufee3" +let s:a_m_MEEM = "\ufee4" +let s:a_s_NOON = "\ufee5" +let s:a_f_NOON = "\ufee6" +let s:a_i_NOON = "\ufee7" +let s:a_m_NOON = "\ufee8" +let s:a_s_HEH = "\ufee9" +let s:a_f_HEH = "\ufeea" +let s:a_i_HEH = "\ufeeb" +let s:a_m_HEH = "\ufeec" +let s:a_s_WAW = "\ufeed" +let s:a_f_WAW = "\ufeee" +let s:a_s_ALEF_MAKSURA = "\ufeef" +let s:a_f_ALEF_MAKSURA = "\ufef0" +let s:a_s_YEH = "\ufef1" +let s:a_f_YEH = "\ufef2" +let s:a_i_YEH = "\ufef3" +let s:a_m_YEH = "\ufef4" +let s:a_s_LAM_ALEF_MADDA_ABOVE = "\ufef5" +let s:a_f_LAM_ALEF_MADDA_ABOVE = "\ufef6" +let s:a_s_LAM_ALEF_HAMZA_ABOVE = "\ufef7" +let s:a_f_LAM_ALEF_HAMZA_ABOVE = "\ufef8" +let s:a_s_LAM_ALEF_HAMZA_BELOW = "\ufef9" +let s:a_f_LAM_ALEF_HAMZA_BELOW = "\ufefa" +let s:a_s_LAM_ALEF = "\ufefb" +let s:a_f_LAM_ALEF = "\ufefc" + +let s:a_BYTE_ORDER_MARK = "\ufeff" func Test_shape_initial() new set arabicshape - " Shaping arabic {testchar} non-arabic Uses chg_c_a2i(). + " Shaping arabic {testchar} non-arabic Tests chg_c_a2i(). " pair[0] = testchar, pair[1] = next-result, pair[2] = current-result for pair in [[s:a_YEH_HAMZA, s:a_f_GHAIN, s:a_i_YEH_HAMZA], \ [s:a_HAMZA, s:a_s_GHAIN, s:a_s_HAMZA], \ [s:a_ALEF_MADDA, s:a_s_GHAIN, s:a_s_ALEF_MADDA], \ [s:a_ALEF_HAMZA_ABOVE, s:a_s_GHAIN, s:a_s_ALEF_HAMZA_ABOVE], + \ [s:a_WAW_HAMZA, s:a_s_GHAIN, s:a_s_WAW_HAMZA], + \ [s:a_ALEF_HAMZA_BELOW, s:a_s_GHAIN, s:a_s_ALEF_HAMZA_BELOW], + \ [s:a_ALEF, s:a_s_GHAIN, s:a_s_ALEF], + \ [s:a_TEH_MARBUTA, s:a_s_GHAIN, s:a_s_TEH_MARBUTA], + \ [s:a_DAL, s:a_s_GHAIN, s:a_s_DAL], + \ [s:a_THAL, s:a_s_GHAIN, s:a_s_THAL], + \ [s:a_REH, s:a_s_GHAIN, s:a_s_REH], + \ [s:a_ZAIN, s:a_s_GHAIN, s:a_s_ZAIN], + \ [s:a_TATWEEL, s:a_f_GHAIN, s:a_TATWEEL], + \ [s:a_WAW, s:a_s_GHAIN, s:a_s_WAW], + \ [s:a_ALEF_MAKSURA, s:a_s_GHAIN, s:a_s_ALEF_MAKSURA], + \ [s:a_BEH, s:a_f_GHAIN, s:a_i_BEH], + \ [s:a_TEH, s:a_f_GHAIN, s:a_i_TEH], + \ [s:a_THEH, s:a_f_GHAIN, s:a_i_THEH], + \ [s:a_JEEM, s:a_f_GHAIN, s:a_i_JEEM], + \ [s:a_HAH, s:a_f_GHAIN, s:a_i_HAH], + \ [s:a_KHAH, s:a_f_GHAIN, s:a_i_KHAH], + \ [s:a_SEEN, s:a_f_GHAIN, s:a_i_SEEN], + \ [s:a_SHEEN, s:a_f_GHAIN, s:a_i_SHEEN], + \ [s:a_SAD, s:a_f_GHAIN, s:a_i_SAD], + \ [s:a_DAD, s:a_f_GHAIN, s:a_i_DAD], + \ [s:a_TAH, s:a_f_GHAIN, s:a_i_TAH], + \ [s:a_ZAH, s:a_f_GHAIN, s:a_i_ZAH], + \ [s:a_AIN, s:a_f_GHAIN, s:a_i_AIN], + \ [s:a_GHAIN, s:a_f_GHAIN, s:a_i_GHAIN], + \ [s:a_FEH, s:a_f_GHAIN, s:a_i_FEH], + \ [s:a_QAF, s:a_f_GHAIN, s:a_i_QAF], + \ [s:a_KAF, s:a_f_GHAIN, s:a_i_KAF], + \ [s:a_LAM, s:a_f_GHAIN, s:a_i_LAM], + \ [s:a_MEEM, s:a_f_GHAIN, s:a_i_MEEM], + \ [s:a_NOON, s:a_f_GHAIN, s:a_i_NOON], + \ [s:a_HEH, s:a_f_GHAIN, s:a_i_HEH], + \ [s:a_YEH, s:a_f_GHAIN, s:a_i_YEH], \ ] call setline(1, s:a_GHAIN . pair[0] . ' ') call assert_equal([pair[1] . pair[2] . ' '], ScreenLines(1, 3)) @@ -131,3 +364,109 @@ func Test_shape_initial() set arabicshape& bwipe! endfunc + +func Test_shape_isolated() + new + set arabicshape + + " Shaping non-arabic {testchar} non-arabic Tests chg_c_a2s(). + " pair[0] = testchar, pair[1] = current-result + for pair in [[s:a_HAMZA, s:a_s_HAMZA], + \ [s:a_ALEF_MADDA, s:a_s_ALEF_MADDA], + \ [s:a_ALEF_HAMZA_ABOVE, s:a_s_ALEF_HAMZA_ABOVE], + \ [s:a_WAW_HAMZA, s:a_s_WAW_HAMZA], + \ [s:a_ALEF_HAMZA_BELOW, s:a_s_ALEF_HAMZA_BELOW], + \ [s:a_YEH_HAMZA, s:a_s_YEH_HAMZA], + \ [s:a_ALEF, s:a_s_ALEF], + \ [s:a_TEH_MARBUTA, s:a_s_TEH_MARBUTA], + \ [s:a_DAL, s:a_s_DAL], + \ [s:a_THAL, s:a_s_THAL], + \ [s:a_REH, s:a_s_REH], + \ [s:a_ZAIN, s:a_s_ZAIN], + \ [s:a_TATWEEL, s:a_TATWEEL], + \ [s:a_WAW, s:a_s_WAW], + \ [s:a_ALEF_MAKSURA, s:a_s_ALEF_MAKSURA], + \ [s:a_BEH, s:a_s_BEH], + \ [s:a_TEH, s:a_s_TEH], + \ [s:a_THEH, s:a_s_THEH], + \ [s:a_JEEM, s:a_s_JEEM], + \ [s:a_HAH, s:a_s_HAH], + \ [s:a_KHAH, s:a_s_KHAH], + \ [s:a_SEEN, s:a_s_SEEN], + \ [s:a_SHEEN, s:a_s_SHEEN], + \ [s:a_SAD, s:a_s_SAD], + \ [s:a_DAD, s:a_s_DAD], + \ [s:a_TAH, s:a_s_TAH], + \ [s:a_ZAH, s:a_s_ZAH], + \ [s:a_AIN, s:a_s_AIN], + \ [s:a_GHAIN, s:a_s_GHAIN], + \ [s:a_FEH, s:a_s_FEH], + \ [s:a_QAF, s:a_s_QAF], + \ [s:a_KAF, s:a_s_KAF], + \ [s:a_LAM, s:a_s_LAM], + \ [s:a_MEEM, s:a_s_MEEM], + \ [s:a_NOON, s:a_s_NOON], + \ [s:a_HEH, s:a_s_HEH], + \ [s:a_YEH, s:a_s_YEH], + \ ] + call setline(1, ' ' . pair[0] . ' ') + call assert_equal([' ' . pair[1] . ' '], ScreenLines(1, 3)) + endfor + + set arabicshape& + bwipe! +endfunc + +func Test_shape_medial() + new + set arabicshape + + " Shaping arabic {testchar} arabic Tests chg_c_a2m(). + " pair[0] = testchar, pair[1] = next-result, pair[2] = current-result, + " pair[3] = previous-result + for pair in [[s:a_HAMZA, s:a_s_GHAIN, s:a_s_HAMZA, s:a_s_BEH], + \[s:a_ALEF_MADDA, s:a_s_GHAIN, s:a_f_ALEF_MADDA, s:a_i_BEH], + \[s:a_ALEF_HAMZA_ABOVE, s:a_s_GHAIN, s:a_f_ALEF_HAMZA_ABOVE, s:a_i_BEH], + \[s:a_WAW_HAMZA, s:a_s_GHAIN, s:a_f_WAW_HAMZA, s:a_i_BEH], + \[s:a_ALEF_HAMZA_BELOW, s:a_s_GHAIN, s:a_f_ALEF_HAMZA_BELOW, s:a_i_BEH], + \[s:a_YEH_HAMZA, s:a_f_GHAIN, s:a_m_YEH_HAMZA, s:a_i_BEH], + \[s:a_ALEF, s:a_s_GHAIN, s:a_f_ALEF, s:a_i_BEH], + \[s:a_BEH, s:a_f_GHAIN, s:a_m_BEH, s:a_i_BEH], + \[s:a_TEH_MARBUTA, s:a_s_GHAIN, s:a_f_TEH_MARBUTA, s:a_i_BEH], + \[s:a_TEH, s:a_f_GHAIN, s:a_m_TEH, s:a_i_BEH], + \[s:a_THEH, s:a_f_GHAIN, s:a_m_THEH, s:a_i_BEH], + \[s:a_JEEM, s:a_f_GHAIN, s:a_m_JEEM, s:a_i_BEH], + \[s:a_HAH, s:a_f_GHAIN, s:a_m_HAH, s:a_i_BEH], + \[s:a_KHAH, s:a_f_GHAIN, s:a_m_KHAH, s:a_i_BEH], + \[s:a_DAL, s:a_s_GHAIN, s:a_f_DAL, s:a_i_BEH], + \[s:a_THAL, s:a_s_GHAIN, s:a_f_THAL, s:a_i_BEH], + \[s:a_REH, s:a_s_GHAIN, s:a_f_REH, s:a_i_BEH], + \[s:a_ZAIN, s:a_s_GHAIN, s:a_f_ZAIN, s:a_i_BEH], + \[s:a_SEEN, s:a_f_GHAIN, s:a_m_SEEN, s:a_i_BEH], + \[s:a_SHEEN, s:a_f_GHAIN, s:a_m_SHEEN, s:a_i_BEH], + \[s:a_SAD, s:a_f_GHAIN, s:a_m_SAD, s:a_i_BEH], + \[s:a_DAD, s:a_f_GHAIN, s:a_m_DAD, s:a_i_BEH], + \[s:a_TAH, s:a_f_GHAIN, s:a_m_TAH, s:a_i_BEH], + \[s:a_ZAH, s:a_f_GHAIN, s:a_m_ZAH, s:a_i_BEH], + \[s:a_AIN, s:a_f_GHAIN, s:a_m_AIN, s:a_i_BEH], + \[s:a_GHAIN, s:a_f_GHAIN, s:a_m_GHAIN, s:a_i_BEH], + \[s:a_TATWEEL, s:a_f_GHAIN, s:a_TATWEEL, s:a_i_BEH], + \[s:a_FEH, s:a_f_GHAIN, s:a_m_FEH, s:a_i_BEH], + \[s:a_QAF, s:a_f_GHAIN, s:a_m_QAF, s:a_i_BEH], + \[s:a_KAF, s:a_f_GHAIN, s:a_m_KAF, s:a_i_BEH], + \[s:a_LAM, s:a_f_GHAIN, s:a_m_LAM, s:a_i_BEH], + \[s:a_MEEM, s:a_f_GHAIN, s:a_m_MEEM, s:a_i_BEH], + \[s:a_NOON, s:a_f_GHAIN, s:a_m_NOON, s:a_i_BEH], + \[s:a_HEH, s:a_f_GHAIN, s:a_m_HEH, s:a_i_BEH], + \[s:a_WAW, s:a_s_GHAIN, s:a_f_WAW, s:a_i_BEH], + \[s:a_ALEF_MAKSURA, s:a_s_GHAIN, s:a_f_ALEF_MAKSURA, s:a_i_BEH], + \[s:a_YEH, s:a_f_GHAIN, s:a_m_YEH, s:a_i_BEH], + \ ] + call setline(1, s:a_GHAIN . pair[0] . s:a_BEH) + call assert_equal([pair[1] . pair[2] . pair[3]], ScreenLines(1, 3)) + endfor + + set arabicshape& + bwipe! +endfunc + -- cgit From b9f3805447f9e0ff277aa655dfb9513f41db5ce6 Mon Sep 17 00:00:00 2001 From: ckelsel Date: Mon, 15 Jan 2018 18:19:38 +0800 Subject: vim-patch:8.0.0406: arabic shaping code is verbose Problem: The arabic shaping code is verbose. Solution: Shorten the code without changing the functionality. https://github.com/vim/vim/commit/7f73b54631af3f0e6f0acd1a1b4c9e8436784705 --- src/nvim/arabic.c | 688 +++++++++++------------------------------------------- 1 file changed, 135 insertions(+), 553 deletions(-) (limited to 'src') diff --git a/src/nvim/arabic.c b/src/nvim/arabic.c index f0370af704..e120e6d492 100644 --- a/src/nvim/arabic.c +++ b/src/nvim/arabic.c @@ -432,7 +432,6 @@ static int chg_c_a2s(int cur_c) case a_HEH: return a_s_HEH; case a_YEH: return a_s_YEH; } - return 0; } @@ -478,176 +477,57 @@ static int chg_c_a2i(int cur_c) case a_HEH: return a_i_HEH; case a_YEH: return a_i_YEH; } - return 0; } // Change shape - from ISO-8859-6/Isolated to Medial static int chg_c_a2m(int cur_c) { - int tempc; - switch (cur_c) { - case a_HAMZA: // exception - tempc = a_s_HAMZA; - break; - - case a_ALEF_MADDA: // exception - tempc = a_f_ALEF_MADDA; - break; - - case a_ALEF_HAMZA_ABOVE: // exception - tempc = a_f_ALEF_HAMZA_ABOVE; - break; - - case a_WAW_HAMZA: // exception - tempc = a_f_WAW_HAMZA; - break; - - case a_ALEF_HAMZA_BELOW: // exception - tempc = a_f_ALEF_HAMZA_BELOW; - break; - - case a_YEH_HAMZA: - tempc = a_m_YEH_HAMZA; - break; - - case a_ALEF: // exception - tempc = a_f_ALEF; - break; - - case a_BEH: - tempc = a_m_BEH; - break; - - case a_TEH_MARBUTA: // exception - tempc = a_f_TEH_MARBUTA; - break; - - case a_TEH: - tempc = a_m_TEH; - break; - - case a_THEH: - tempc = a_m_THEH; - break; - - case a_JEEM: - tempc = a_m_JEEM; - break; - - case a_HAH: - tempc = a_m_HAH; - break; - - case a_KHAH: - tempc = a_m_KHAH; - break; - - case a_DAL: // exception - tempc = a_f_DAL; - break; - - case a_THAL: // exception - tempc = a_f_THAL; - break; - - case a_REH: // exception - tempc = a_f_REH; - break; - - case a_ZAIN: // exception - tempc = a_f_ZAIN; - break; - - case a_SEEN: - tempc = a_m_SEEN; - break; - - case a_SHEEN: - tempc = a_m_SHEEN; - break; - - case a_SAD: - tempc = a_m_SAD; - break; - - case a_DAD: - tempc = a_m_DAD; - break; - - case a_TAH: - tempc = a_m_TAH; - break; - - case a_ZAH: - tempc = a_m_ZAH; - break; - - case a_AIN: - tempc = a_m_AIN; - break; - - case a_GHAIN: - tempc = a_m_GHAIN; - break; - - case a_TATWEEL: // exception - tempc = cur_c; - break; - - case a_FEH: - tempc = a_m_FEH; - break; - - case a_QAF: - tempc = a_m_QAF; - break; - - case a_KAF: - tempc = a_m_KAF; - break; - - case a_LAM: - tempc = a_m_LAM; - break; - - case a_MEEM: - tempc = a_m_MEEM; - break; - - case a_NOON: - tempc = a_m_NOON; - break; - - case a_HEH: - tempc = a_m_HEH; - break; - - case a_WAW: // exception - tempc = a_f_WAW; - break; - - case a_ALEF_MAKSURA: // exception - tempc = a_f_ALEF_MAKSURA; - break; - - case a_YEH: - tempc = a_m_YEH; - break; - - default: - tempc = 0; + case a_HAMZA: return a_s_HAMZA; // exception + case a_ALEF_MADDA: return a_f_ALEF_MADDA; // exception + case a_ALEF_HAMZA_ABOVE: return a_f_ALEF_HAMZA_ABOVE; // exception + case a_WAW_HAMZA: return a_f_WAW_HAMZA; // exception + case a_ALEF_HAMZA_BELOW: return a_f_ALEF_HAMZA_BELOW; // exception + case a_YEH_HAMZA: return a_m_YEH_HAMZA; + case a_ALEF: return a_f_ALEF; // exception + case a_BEH: return a_m_BEH; + case a_TEH_MARBUTA: return a_f_TEH_MARBUTA; // exception + case a_TEH: return a_m_TEH; + case a_THEH: return a_m_THEH; + case a_JEEM: return a_m_JEEM; + case a_HAH: return a_m_HAH; + case a_KHAH: return a_m_KHAH; + case a_DAL: return a_f_DAL; // exception + case a_THAL: return a_f_THAL; // exception + case a_REH: return a_f_REH; // exception + case a_ZAIN: return a_f_ZAIN; // exception + case a_SEEN: return a_m_SEEN; + case a_SHEEN: return a_m_SHEEN; + case a_SAD: return a_m_SAD; + case a_DAD: return a_m_DAD; + case a_TAH: return a_m_TAH; + case a_ZAH: return a_m_ZAH; + case a_AIN: return a_m_AIN; + case a_GHAIN: return a_m_GHAIN; + case a_TATWEEL: return cur_c; // exception + case a_FEH: return a_m_FEH; + case a_QAF: return a_m_QAF; + case a_KAF: return a_m_KAF; + case a_LAM: return a_m_LAM; + case a_MEEM: return a_m_MEEM; + case a_NOON: return a_m_NOON; + case a_HEH: return a_m_HEH; + case a_WAW: return a_f_WAW; // exception + case a_ALEF_MAKSURA: return a_f_ALEF_MAKSURA; // exception + case a_YEH: return a_m_YEH; } - - return tempc; + return 0; } // Change shape - from ISO-8859-6/Isolated to final static int chg_c_a2f(int cur_c) { - int tempc; - // NOTE: these encodings need to be accounted for // // a_f_ALEF_MADDA; @@ -658,280 +538,87 @@ static int chg_c_a2f(int cur_c) // a_f_LAM_ALEF_HAMZA_BELOW; switch (cur_c) { - case a_HAMZA: // exception - tempc = a_s_HAMZA; - break; - - case a_ALEF_MADDA: - tempc = a_f_ALEF_MADDA; - break; - - case a_ALEF_HAMZA_ABOVE: - tempc = a_f_ALEF_HAMZA_ABOVE; - break; - - case a_WAW_HAMZA: - tempc = a_f_WAW_HAMZA; - break; - - case a_ALEF_HAMZA_BELOW: - tempc = a_f_ALEF_HAMZA_BELOW; - break; - - case a_YEH_HAMZA: - tempc = a_f_YEH_HAMZA; - break; - - case a_ALEF: - tempc = a_f_ALEF; - break; - - case a_BEH: - tempc = a_f_BEH; - break; - - case a_TEH_MARBUTA: - tempc = a_f_TEH_MARBUTA; - break; - - case a_TEH: - tempc = a_f_TEH; - break; - - case a_THEH: - tempc = a_f_THEH; - break; - - case a_JEEM: - tempc = a_f_JEEM; - break; - - case a_HAH: - tempc = a_f_HAH; - break; - - case a_KHAH: - tempc = a_f_KHAH; - break; - - case a_DAL: - tempc = a_f_DAL; - break; - - case a_THAL: - tempc = a_f_THAL; - break; - - case a_REH: - tempc = a_f_REH; - break; - - case a_ZAIN: - tempc = a_f_ZAIN; - break; - - case a_SEEN: - tempc = a_f_SEEN; - break; - - case a_SHEEN: - tempc = a_f_SHEEN; - break; - - case a_SAD: - tempc = a_f_SAD; - break; - - case a_DAD: - tempc = a_f_DAD; - break; - - case a_TAH: - tempc = a_f_TAH; - break; - - case a_ZAH: - tempc = a_f_ZAH; - break; - - case a_AIN: - tempc = a_f_AIN; - break; - - case a_GHAIN: - tempc = a_f_GHAIN; - break; - - case a_TATWEEL: // exception - tempc = cur_c; - break; - - case a_FEH: - tempc = a_f_FEH; - break; - - case a_QAF: - tempc = a_f_QAF; - break; - - case a_KAF: - tempc = a_f_KAF; - break; - - case a_LAM: - tempc = a_f_LAM; - break; - - case a_MEEM: - tempc = a_f_MEEM; - break; - - case a_NOON: - tempc = a_f_NOON; - break; - - case a_HEH: - tempc = a_f_HEH; - break; - - case a_WAW: - tempc = a_f_WAW; - break; - - case a_ALEF_MAKSURA: - tempc = a_f_ALEF_MAKSURA; - break; - - case a_YEH: - tempc = a_f_YEH; - break; - - default: - tempc = 0; + case a_HAMZA: return a_s_HAMZA; // exception + case a_ALEF_MADDA: return a_f_ALEF_MADDA; + case a_ALEF_HAMZA_ABOVE: return a_f_ALEF_HAMZA_ABOVE; + case a_WAW_HAMZA: return a_f_WAW_HAMZA; + case a_ALEF_HAMZA_BELOW: return a_f_ALEF_HAMZA_BELOW; + case a_YEH_HAMZA: return a_f_YEH_HAMZA; + case a_ALEF: return a_f_ALEF; + case a_BEH: return a_f_BEH; + case a_TEH_MARBUTA: return a_f_TEH_MARBUTA; + case a_TEH: return a_f_TEH; + case a_THEH: return a_f_THEH; + case a_JEEM: return a_f_JEEM; + case a_HAH: return a_f_HAH; + case a_KHAH: return a_f_KHAH; + case a_DAL: return a_f_DAL; + case a_THAL: return a_f_THAL; + case a_REH: return a_f_REH; + case a_ZAIN: return a_f_ZAIN; + case a_SEEN: return a_f_SEEN; + case a_SHEEN: return a_f_SHEEN; + case a_SAD: return a_f_SAD; + case a_DAD: return a_f_DAD; + case a_TAH: return a_f_TAH; + case a_ZAH: return a_f_ZAH; + case a_AIN: return a_f_AIN; + case a_GHAIN: return a_f_GHAIN; + case a_TATWEEL: return cur_c; // exception + case a_FEH: return a_f_FEH; + case a_QAF: return a_f_QAF; + case a_KAF: return a_f_KAF; + case a_LAM: return a_f_LAM; + case a_MEEM: return a_f_MEEM; + case a_NOON: return a_f_NOON; + case a_HEH: return a_f_HEH; + case a_WAW: return a_f_WAW; + case a_ALEF_MAKSURA: return a_f_ALEF_MAKSURA; + case a_YEH: return a_f_YEH; } - - return tempc; + return 0; } // Change shape - from Initial to Medial static int chg_c_i2m(int cur_c) { - int tempc; - switch (cur_c) { - case a_i_YEH_HAMZA: - tempc = a_m_YEH_HAMZA; - break; - - case a_i_BEH: - tempc = a_m_BEH; - break; - - case a_i_TEH: - tempc = a_m_TEH; - break; - - case a_i_THEH: - tempc = a_m_THEH; - break; - - case a_i_JEEM: - tempc = a_m_JEEM; - break; - - case a_i_HAH: - tempc = a_m_HAH; - break; - - case a_i_KHAH: - tempc = a_m_KHAH; - break; - - case a_i_SEEN: - tempc = a_m_SEEN; - break; - - case a_i_SHEEN: - tempc = a_m_SHEEN; - break; - - case a_i_SAD: - tempc = a_m_SAD; - break; - - case a_i_DAD: - tempc = a_m_DAD; - break; - - case a_i_TAH: - tempc = a_m_TAH; - break; - - case a_i_ZAH: - tempc = a_m_ZAH; - break; - - case a_i_AIN: - tempc = a_m_AIN; - break; - - case a_i_GHAIN: - tempc = a_m_GHAIN; - break; - - case a_i_FEH: - tempc = a_m_FEH; - break; - - case a_i_QAF: - tempc = a_m_QAF; - break; - - case a_i_KAF: - tempc = a_m_KAF; - break; - - case a_i_LAM: - tempc = a_m_LAM; - break; - - case a_i_MEEM: - tempc = a_m_MEEM; - break; - - case a_i_NOON: - tempc = a_m_NOON; - break; - - case a_i_HEH: - tempc = a_m_HEH; - break; - - case a_i_YEH: - tempc = a_m_YEH; - break; - - default: - tempc = 0; + case a_i_YEH_HAMZA: return a_m_YEH_HAMZA; + case a_i_BEH: return a_m_BEH; + case a_i_TEH: return a_m_TEH; + case a_i_THEH: return a_m_THEH; + case a_i_JEEM: return a_m_JEEM; + case a_i_HAH: return a_m_HAH; + case a_i_KHAH: return a_m_KHAH; + case a_i_SEEN: return a_m_SEEN; + case a_i_SHEEN: return a_m_SHEEN; + case a_i_SAD: return a_m_SAD; + case a_i_DAD: return a_m_DAD; + case a_i_TAH: return a_m_TAH; + case a_i_ZAH: return a_m_ZAH; + case a_i_AIN: return a_m_AIN; + case a_i_GHAIN: return a_m_GHAIN; + case a_i_FEH: return a_m_FEH; + case a_i_QAF: return a_m_QAF; + case a_i_KAF: return a_m_KAF; + case a_i_LAM: return a_m_LAM; + case a_i_MEEM: return a_m_MEEM; + case a_i_NOON: return a_m_NOON; + case a_i_HEH: return a_m_HEH; + case a_i_YEH: return a_m_YEH; } - - return tempc; + return 0; } // Change shape - from Final to Medial static int chg_c_f2m(int cur_c) { - int tempc; - switch (cur_c) { // NOTE: these encodings are multi-positional, no ? // case a_f_ALEF_MADDA: // case a_f_ALEF_HAMZA_ABOVE: // case a_f_ALEF_HAMZA_BELOW: - case a_f_YEH_HAMZA: - tempc = a_m_YEH_HAMZA; - break; - + case a_f_YEH_HAMZA: return a_m_YEH_HAMZA; case a_f_WAW_HAMZA: // exceptions case a_f_ALEF: case a_f_TEH_MARBUTA: @@ -941,165 +628,60 @@ static int chg_c_f2m(int cur_c) case a_f_ZAIN: case a_f_WAW: case a_f_ALEF_MAKSURA: - tempc = cur_c; - break; - - case a_f_BEH: - tempc = a_m_BEH; - break; - - case a_f_TEH: - tempc = a_m_TEH; - break; - - case a_f_THEH: - tempc = a_m_THEH; - break; - - case a_f_JEEM: - tempc = a_m_JEEM; - break; - - case a_f_HAH: - tempc = a_m_HAH; - break; - - case a_f_KHAH: - tempc = a_m_KHAH; - break; - - case a_f_SEEN: - tempc = a_m_SEEN; - break; - - case a_f_SHEEN: - tempc = a_m_SHEEN; - break; - - case a_f_SAD: - tempc = a_m_SAD; - break; - - case a_f_DAD: - tempc = a_m_DAD; - break; - - case a_f_TAH: - tempc = a_m_TAH; - break; - - case a_f_ZAH: - tempc = a_m_ZAH; - break; - - case a_f_AIN: - tempc = a_m_AIN; - break; - - case a_f_GHAIN: - tempc = a_m_GHAIN; - break; - - case a_f_FEH: - tempc = a_m_FEH; - break; - - case a_f_QAF: - tempc = a_m_QAF; - break; - - case a_f_KAF: - tempc = a_m_KAF; - break; - - case a_f_LAM: - tempc = a_m_LAM; - break; - - case a_f_MEEM: - tempc = a_m_MEEM; - break; - - case a_f_NOON: - tempc = a_m_NOON; - break; - - case a_f_HEH: - tempc = a_m_HEH; - break; - - case a_f_YEH: - tempc = a_m_YEH; - break; - + return cur_c; + case a_f_BEH: return a_m_BEH; + case a_f_TEH: return a_m_TEH; + case a_f_THEH: return a_m_THEH; + case a_f_JEEM: return a_m_JEEM; + case a_f_HAH: return a_m_HAH; + case a_f_KHAH: return a_m_KHAH; + case a_f_SEEN: return a_m_SEEN; + case a_f_SHEEN: return a_m_SHEEN; + case a_f_SAD: return a_m_SAD; + case a_f_DAD: return a_m_DAD; + case a_f_TAH: return a_m_TAH; + case a_f_ZAH: return a_m_ZAH; + case a_f_AIN: return a_m_AIN; + case a_f_GHAIN: return a_m_GHAIN; + case a_f_FEH: return a_m_FEH; + case a_f_QAF: return a_m_QAF; + case a_f_KAF: return a_m_KAF; + case a_f_LAM: return a_m_LAM; + case a_f_MEEM: return a_m_MEEM; + case a_f_NOON: return a_m_NOON; + case a_f_HEH: return a_m_HEH; + case a_f_YEH: return a_m_YEH; // NOTE: these encodings are multi-positional, no ? // case a_f_LAM_ALEF_MADDA_ABOVE: // case a_f_LAM_ALEF_HAMZA_ABOVE: // case a_f_LAM_ALEF_HAMZA_BELOW: // case a_f_LAM_ALEF: - default: - tempc = 0; } - - return tempc; + return 0; } // Change shape - from Combination (2 char) to an Isolated. static int chg_c_laa2i(int hid_c) { - int tempc; - switch (hid_c) { - case a_ALEF_MADDA: - tempc = a_s_LAM_ALEF_MADDA_ABOVE; - break; - - case a_ALEF_HAMZA_ABOVE: - tempc = a_s_LAM_ALEF_HAMZA_ABOVE; - break; - - case a_ALEF_HAMZA_BELOW: - tempc = a_s_LAM_ALEF_HAMZA_BELOW; - break; - - case a_ALEF: - tempc = a_s_LAM_ALEF; - break; - - default: - tempc = 0; + case a_ALEF_MADDA: return a_s_LAM_ALEF_MADDA_ABOVE; + case a_ALEF_HAMZA_ABOVE: return a_s_LAM_ALEF_HAMZA_ABOVE; + case a_ALEF_HAMZA_BELOW: return a_s_LAM_ALEF_HAMZA_BELOW; + case a_ALEF: return a_s_LAM_ALEF; } - - return tempc; + return 0; } // Change shape - from Combination-Isolated to Final. static int chg_c_laa2f(int hid_c) { - int tempc; - switch (hid_c) { - case a_ALEF_MADDA: - tempc = a_f_LAM_ALEF_MADDA_ABOVE; - break; - - case a_ALEF_HAMZA_ABOVE: - tempc = a_f_LAM_ALEF_HAMZA_ABOVE; - break; - - case a_ALEF_HAMZA_BELOW: - tempc = a_f_LAM_ALEF_HAMZA_BELOW; - break; - - case a_ALEF: - tempc = a_f_LAM_ALEF; - break; - - default: - tempc = 0; + case a_ALEF_MADDA: return a_f_LAM_ALEF_MADDA_ABOVE; + case a_ALEF_HAMZA_ABOVE: return a_f_LAM_ALEF_HAMZA_ABOVE; + case a_ALEF_HAMZA_BELOW: return a_f_LAM_ALEF_HAMZA_BELOW; + case a_ALEF: return a_f_LAM_ALEF; } - - return tempc; + return 0; } // Do "half-shaping" on character "c". Return zero if no shaping. -- cgit From 63bb7198dfded8a5c37195ebc8503516a1eda0f3 Mon Sep 17 00:00:00 2001 From: ckelsel Date: Mon, 15 Jan 2018 19:27:10 +0800 Subject: vim-patch:8.0.0398: illegal memory access with "t" Problem: Illegal memory access with "t". Solution: Use strncmp() instead of memcmp(). (Dominique Pelle, closes vim/vim#1528) https://github.com/vim/vim/commit/66727e16079fbac6db3897b5c3736ec9fba995bb --- src/nvim/search.c | 8 ++++---- src/nvim/testdir/test_search.vim | 7 +++++++ 2 files changed, 11 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/search.c b/src/nvim/search.c index 1eb1a25a19..0a266382ec 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -1382,13 +1382,13 @@ int searchc(cmdarg_T *cap, int t_cmd) col -= (*mb_head_off)(p, p + col - 1) + 1; } if (lastc_bytelen == 1) { - if (p[col] == c && stop) + if (p[col] == c && stop) { break; - } else { - if (memcmp(p + col, lastc_bytes, lastc_bytelen) == 0 && stop) + } + } else if (STRNCMP(p + col, lastc_bytes, lastc_bytelen) == 0 && stop) { break; } - stop = TRUE; + stop = true; } } else { for (;; ) { diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index a333e7f206..03112df46f 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -298,3 +298,10 @@ func Test_searchpair() q! endfunc +func Test_searchc() + " These commands used to cause memory overflow in searchc(). + new + norm ixx + exe "norm 0t\u93cf" + bw! +endfunc -- cgit From 28998cfd815abd690ffa0b9bab786263af619008 Mon Sep 17 00:00:00 2001 From: ckelsel Date: Mon, 15 Jan 2018 19:41:29 +0800 Subject: vim-patch:8.0.0402: :map completion does not have Problem: :map completion does not have . (Dominique Pelle) Solution: Recognize in completion. Add a test. https://github.com/vim/vim/commit/cf5fdf7d1689ecb145b634dcb9c6e9fc60f63869 --- src/nvim/getchar.c | 23 +++++++++++++++-------- src/nvim/testdir/test_cmdline.vim | 20 ++++++++++++++++++++ 2 files changed, 35 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 1b5d3472ab..7df1bf8429 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -3366,6 +3366,10 @@ set_context_in_map_cmd ( arg = skipwhite(arg + 8); continue; } + if (STRNCMP(arg, "", 9) == 0) { + arg = skipwhite(arg + 9); + continue; + } if (STRNCMP(arg, "