diff options
Diffstat (limited to 'src')
58 files changed, 1253 insertions, 553 deletions
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 2c9d655a15..8b422b3abe 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -506,11 +506,9 @@ if(WIN32) "file(MAKE_DIRECTORY \"${PROJECT_BINARY_DIR}/windows_runtime_deps/platforms\")") foreach(DEP_FILE IN ITEMS ca-bundle.crt - cat.exe curl.exe diff.exe tee.exe - tidy.exe win32yank.exe winpty-agent.exe winpty.dll diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index c55dc39605..cc5a62a170 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1426,6 +1426,10 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, /// - "eol": right after eol character (default) /// - "overlay": display over the specified column, without /// shifting the underlying text. +/// - "right_align": display right aligned in the window. +/// - virt_text_win_col : position the virtual text at a fixed +/// window column (starting from the first +/// text column) /// - virt_text_hide : hide the virtual text when the background /// text is selected or hidden due to /// horizontal scroll 'nowrap' @@ -1437,6 +1441,10 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, /// default /// - "combine": combine with background text color /// - "blend": blend with background text color. +/// - hl_eol : when true, for a multiline highlight covering the +/// EOL of a line, continue the highlight for the rest +/// of the screen line (just like for diff and +/// cursorline highlight). /// /// - ephemeral : for use with |nvim_set_decoration_provider| /// callbacks. The mark will only be used for the current @@ -1570,17 +1578,33 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, decor.virt_text_pos = kVTEndOfLine; } else if (strequal("overlay", str.data)) { decor.virt_text_pos = kVTOverlay; + } else if (strequal("right_align", str.data)) { + decor.virt_text_pos = kVTRightAlign; } else { api_set_error(err, kErrorTypeValidation, "virt_text_pos: invalid value"); goto error; } + } else if (strequal("virt_text_win_col", k.data)) { + if (v->type != kObjectTypeInteger) { + api_set_error(err, kErrorTypeValidation, + "virt_text_win_col is not a Number of the correct size"); + goto error; + } + + decor.col = (int)v->data.integer; + decor.virt_text_pos = kVTWinCol; } else if (strequal("virt_text_hide", k.data)) { decor.virt_text_hide = api_object_to_bool(*v, "virt_text_hide", false, err); if (ERROR_SET(err)) { goto error; } + } else if (strequal("hl_eol", k.data)) { + decor.hl_eol = api_object_to_bool(*v, "hl_eol", false, err); + if (ERROR_SET(err)) { + goto error; + } } else if (strequal("hl_mode", k.data)) { if (v->type != kObjectTypeString) { api_set_error(err, kErrorTypeValidation, @@ -1664,12 +1688,21 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, col2 = 0; } + if (decor.virt_text_pos == kVTRightAlign) { + decor.col = 0; + for (size_t i = 0; i < kv_size(decor.virt_text); i++) { + decor.col += mb_string2cells((char_u *)kv_A(decor.virt_text, i).text); + } + } + + Decoration *d = NULL; if (ephemeral) { d = &decor; } else if (kv_size(decor.virt_text) - || decor.priority != DECOR_PRIORITY_BASE) { + || decor.priority != DECOR_PRIORITY_BASE + || decor.hl_eol) { // TODO(bfredl): this is a bit sketchy. eventually we should // have predefined decorations for both marks/ephemerals d = xcalloc(1, sizeof(*d)); @@ -1680,7 +1713,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, // TODO(bfredl): synergize these two branches even more if (ephemeral && decor_state.buf == buf) { - decor_add_ephemeral((int)line, (int)col, line2, col2, &decor, 0); + decor_add_ephemeral((int)line, (int)col, line2, col2, &decor); } else { if (ephemeral) { api_set_error(err, kErrorTypeException, "not yet implemented"); diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index c73a9195c3..24ba6110c4 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1765,6 +1765,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) { "double", { "╔", "═", "╗", "║", "╝", "═", "╚", "║" }, false }, { "single", { "┌", "─", "┐", "│", "┘", "─", "└", "│" }, false }, { "shadow", { "", "", " ", " ", " ", " ", " ", "" }, true }, + { "solid", { " ", " ", " ", " ", " ", " ", " ", " " }, false }, { NULL, { { NUL } } , false }, }; diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index b5e53beabe..c363c77afb 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -104,10 +104,14 @@ String nvim_exec(String src, Boolean output, Error *err) } try_start(); - msg_silent++; + if (output) { + msg_silent++; + } do_source_str(src.data, "nvim_exec()"); - capture_ga = save_capture_ga; - msg_silent = save_msg_silent; + if (output) { + capture_ga = save_capture_ga; + msg_silent = save_msg_silent; + } try_end(err); if (ERROR_SET(err)) { @@ -1263,6 +1267,7 @@ fail: /// @param buffer the buffer to use (expected to be empty) /// @param opts Optional parameters. Reserved for future use. /// @param[out] err Error details, if any +/// @return Channel id, or 0 on error Integer nvim_open_term(Buffer buffer, Dictionary opts, Error *err) FUNC_API_SINCE(7) { diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 89fa2f86fb..f942d6b19f 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -381,7 +381,7 @@ Integer nvim_win_get_number(Window window, Error *err) } int tabnr; - win_get_tabwin(window, &tabnr, &rv); + win_get_tabwin(win->handle, &tabnr, &rv); return rv; } diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index c98f2786c2..ce4163fccf 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1844,7 +1844,7 @@ buf_T *buflist_new(char_u *ffname_arg, char_u *sfname_arg, linenr_T lnum, EMSG(_("W14: Warning: List of file names overflow")); if (emsg_silent == 0) { ui_flush(); - os_delay(3000L, true); // make sure it is noticed + os_delay(3001L, true); // make sure it is noticed } top_file_num = 1; } diff --git a/src/nvim/change.c b/src/nvim/change.c index 38bd591eca..74e27ca880 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -68,7 +68,7 @@ void change_warning(int col) (void)msg_end(); if (msg_silent == 0 && !silent_mode && ui_active()) { ui_flush(); - os_delay(1000L, true); // give the user time to think about it + os_delay(1002L, true); // give the user time to think about it } curbuf->b_did_warn = true; redraw_cmdline = false; // don't redraw and erase the message @@ -109,7 +109,7 @@ void changed(void) // and don't let the emsg() set msg_scroll. if (need_wait_return && emsg_silent == 0) { ui_flush(); - os_delay(2000L, true); + os_delay(2002L, true); wait_return(true); msg_scroll = save_msg_scroll; } else { diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 52a48ae6fb..ca1d141dd8 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -144,9 +144,9 @@ bool decor_redraw_reset(buf_T *buf, DecorState *state) state->row = -1; state->buf = buf; for (size_t i = 0; i < kv_size(state->active); i++) { - HlRange item = kv_A(state->active, i); + DecorRange item = kv_A(state->active, i); if (item.virt_text_owned) { - clear_virttext(&item.virt_text); + clear_virttext(&item.decor.virt_text); } } kv_size(state->active) = 0; @@ -190,14 +190,14 @@ bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state) if (mark.id&MARKTREE_END_FLAG) { decor_add(state, altpos.row, altpos.col, mark.row, mark.col, - decor, false, 0); + decor, false); } else { if (altpos.row == -1) { altpos.row = mark.row; altpos.col = mark.col; } decor_add(state, mark.row, mark.col, altpos.row, altpos.col, - decor, false, 0); + decor, false); } next_mark: @@ -222,22 +222,23 @@ bool decor_redraw_line(buf_T *buf, int row, DecorState *state) } static void decor_add(DecorState *state, int start_row, int start_col, - int end_row, int end_col, Decoration *decor, bool owned, - DecorPriority priority) + int end_row, int end_col, Decoration *decor, bool owned) { int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0; - HlRange range = { start_row, start_col, end_row, end_col, - attr_id, MAX(priority, decor->priority), - decor->virt_text, - decor->virt_text_pos, decor->virt_text_hide, decor->hl_mode, + DecorRange range = { start_row, start_col, end_row, end_col, + *decor, attr_id, kv_size(decor->virt_text) && owned, -1 }; + if (decor->virt_text_pos == kVTEndOfLine) { + range.win_col = -2; // handled separately + } + kv_pushp(state->active); size_t index; for (index = kv_size(state->active)-1; index > 0; index--) { - HlRange item = kv_A(state->active, index-1); - if (item.priority <= range.priority) { + DecorRange item = kv_A(state->active, index-1); + if (item.decor.priority <= range.decor.priority) { break; } kv_A(state->active, index) = kv_A(state->active, index-1); @@ -245,7 +246,7 @@ static void decor_add(DecorState *state, int start_row, int start_col, kv_A(state->active, index) = range; } -int decor_redraw_col(buf_T *buf, int col, int virt_col, bool hidden, +int decor_redraw_col(buf_T *buf, int col, int win_col, bool hidden, DecorState *state) { if (col <= state->col_until) { @@ -291,7 +292,7 @@ int decor_redraw_col(buf_T *buf, int col, int virt_col, bool hidden, } decor_add(state, mark.row, mark.col, endpos.row, endpos.col, - decor, false, 0); + decor, false); next_mark: marktree_itr_next(buf->b_marktree, state->itr); @@ -300,11 +301,11 @@ next_mark: int attr = 0; size_t j = 0; for (size_t i = 0; i < kv_size(state->active); i++) { - HlRange item = kv_A(state->active, i); + DecorRange item = kv_A(state->active, i); bool active = false, keep = true; if (item.end_row < state->row || (item.end_row == state->row && item.end_col <= col)) { - if (!(item.start_row >= state->row && kv_size(item.virt_text))) { + if (!(item.start_row >= state->row && kv_size(item.decor.virt_text))) { keep = false; } } else { @@ -324,13 +325,14 @@ next_mark: attr = hl_combine_attr(attr, item.attr_id); } if ((item.start_row == state->row && item.start_col <= col) - && kv_size(item.virt_text) && item.virt_col == -1) { - item.virt_col = (item.virt_text_hide && hidden) ? -2 : virt_col; + && kv_size(item.decor.virt_text) + && item.decor.virt_text_pos == kVTOverlay && item.win_col == -1) { + item.win_col = (item.decor.virt_text_hide && hidden) ? -2 : win_col; } if (keep) { kv_A(state->active, j++) = item; } else if (item.virt_text_owned) { - clear_virttext(&item.virt_text); + clear_virttext(&item.decor.virt_text); } } kv_size(state->active) = j; @@ -343,28 +345,39 @@ void decor_redraw_end(DecorState *state) state->buf = NULL; } -VirtText decor_redraw_virt_text(buf_T *buf, DecorState *state) +VirtText decor_redraw_eol(buf_T *buf, DecorState *state, int *eol_attr, + bool *aligned) { decor_redraw_col(buf, MAXCOL, MAXCOL, false, state); + VirtText text = VIRTTEXT_EMPTY; for (size_t i = 0; i < kv_size(state->active); i++) { - HlRange item = kv_A(state->active, i); - if (item.start_row == state->row && kv_size(item.virt_text) - && item.virt_text_pos == kVTEndOfLine) { - return item.virt_text; + DecorRange item = kv_A(state->active, i); + if (item.start_row == state->row && kv_size(item.decor.virt_text)) { + if (!kv_size(text) && item.decor.virt_text_pos == kVTEndOfLine) { + text = item.decor.virt_text; + } else if (item.decor.virt_text_pos == kVTRightAlign + || item.decor.virt_text_pos == kVTWinCol) { + *aligned = true; + } + } + + + if (item.decor.hl_eol && item.start_row <= state->row) { + *eol_attr = hl_combine_attr(*eol_attr, item.attr_id); } } - return VIRTTEXT_EMPTY; + + return text; } void decor_add_ephemeral(int start_row, int start_col, int end_row, int end_col, - Decoration *decor, DecorPriority priority) + Decoration *decor) { if (end_row == -1) { end_row = start_row; end_col = start_col; } - decor_add(&decor_state, start_row, start_col, end_row, end_col, decor, true, - priority); + decor_add(&decor_state, start_row, start_col, end_row, end_col, decor, true); } diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index c5424a1642..4cebc0b731 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -21,6 +21,8 @@ typedef uint16_t DecorPriority; typedef enum { kVTEndOfLine, kVTOverlay, + kVTWinCol, + kVTRightAlign, } VirtTextPos; typedef enum { @@ -37,33 +39,29 @@ struct Decoration VirtTextPos virt_text_pos; bool virt_text_hide; HlMode hl_mode; + bool hl_eol; // TODO(bfredl): style, signs, etc DecorPriority priority; bool shared; // shared decoration, don't free + int col; // fixed col value, like win_col }; #define DECORATION_INIT { 0, KV_INITIAL_VALUE, kVTEndOfLine, false, \ - kHlModeUnknown, DECOR_PRIORITY_BASE, false } + kHlModeUnknown, false, DECOR_PRIORITY_BASE, false, 0 } typedef struct { int start_row; int start_col; int end_row; int end_col; - int attr_id; - // TODO(bfredl): embed decoration instead, perhaps using an arena - // for ephemerals? - DecorPriority priority; - VirtText virt_text; - VirtTextPos virt_text_pos; - bool virt_text_hide; - HlMode hl_mode; + Decoration decor; + int attr_id; // cached lookup of decor.hl_id bool virt_text_owned; - int virt_col; -} HlRange; + int win_col; +} DecorRange; typedef struct { MarkTreeIter itr[1]; - kvec_t(HlRange) active; + kvec_t(DecorRange) active; buf_T *buf; int top_row; int row; diff --git a/src/nvim/edit.c b/src/nvim/edit.c index ea13052f25..999cc74185 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -1604,13 +1604,20 @@ void edit_putchar(int c, bool highlight) } } +/// Return the effective prompt for the specified buffer. +char_u *buf_prompt_text(const buf_T *const buf) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (buf->b_prompt_text == NULL) { + return (char_u *)"% "; + } + return buf->b_prompt_text; +} + // Return the effective prompt for the current buffer. -char_u *prompt_text(void) +char_u *prompt_text(void) FUNC_ATTR_WARN_UNUSED_RESULT { - if (curbuf->b_prompt_text == NULL) { - return (char_u *)"% "; - } - return curbuf->b_prompt_text; + return buf_prompt_text(curbuf); } // Prepare for prompt mode: Make sure the last line has the prompt text. @@ -2058,7 +2065,7 @@ static bool check_compl_option(bool dict_opt) vim_beep(BO_COMPL); setcursor(); ui_flush(); - os_delay(2000L, false); + os_delay(2004L, false); } return false; } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index b310fd49b0..05d429c7d5 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -3417,8 +3417,7 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) { typval_T var2; char_u *p; - exptype_T type = TYPE_UNKNOWN; - bool type_is = false; // true for "is" and "isnot" + exprtype_T type = EXPR_UNKNOWN; int len = 2; bool ic; @@ -3430,35 +3429,42 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) p = *arg; switch (p[0]) { - case '=': if (p[1] == '=') - type = TYPE_EQUAL; - else if (p[1] == '~') - type = TYPE_MATCH; + case '=': + if (p[1] == '=') { + type = EXPR_EQUAL; + } else if (p[1] == '~') { + type = EXPR_MATCH; + } break; - case '!': if (p[1] == '=') - type = TYPE_NEQUAL; - else if (p[1] == '~') - type = TYPE_NOMATCH; + case '!': + if (p[1] == '=') { + type = EXPR_NEQUAL; + } else if (p[1] == '~') { + type = EXPR_NOMATCH; + } break; - case '>': if (p[1] != '=') { - type = TYPE_GREATER; + case '>': + if (p[1] != '=') { + type = EXPR_GREATER; len = 1; - } else - type = TYPE_GEQUAL; + } else { + type = EXPR_GEQUAL; + } break; - case '<': if (p[1] != '=') { - type = TYPE_SMALLER; + case '<': + if (p[1] != '=') { + type = EXPR_SMALLER; len = 1; - } else - type = TYPE_SEQUAL; + } else { + type = EXPR_SEQUAL; + } break; case 'i': if (p[1] == 's') { if (p[2] == 'n' && p[3] == 'o' && p[4] == 't') { len = 5; } if (!isalnum(p[len]) && p[len] != '_') { - type = len == 2 ? TYPE_EQUAL : TYPE_NEQUAL; - type_is = true; + type = len == 2 ? EXPR_IS : EXPR_ISNOT; } } break; @@ -3467,7 +3473,7 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) /* * If there is a comparative operator, use it. */ - if (type != TYPE_UNKNOWN) { + if (type != EXPR_UNKNOWN) { // extra question mark appended: ignore case if (p[len] == '?') { ic = true; @@ -3486,7 +3492,7 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) return FAIL; } if (evaluate) { - const int ret = typval_compare(rettv, &var2, type, type_is, ic); + const int ret = typval_compare(rettv, &var2, type, ic); tv_clear(&var2); return ret; @@ -10582,27 +10588,27 @@ bool invoke_prompt_interrupt(void) int typval_compare( typval_T *typ1, // first operand typval_T *typ2, // second operand - exptype_T type, // operator - bool type_is, // true for "is" and "isnot" + exprtype_T type, // operator bool ic // ignore case ) FUNC_ATTR_NONNULL_ALL { varnumber_T n1, n2; + const bool type_is = type == EXPR_IS || type == EXPR_ISNOT; if (type_is && typ1->v_type != typ2->v_type) { // For "is" a different type always means false, for "notis" // it means true. - n1 = type == TYPE_NEQUAL; + n1 = type == EXPR_ISNOT; } else if (typ1->v_type == VAR_LIST || typ2->v_type == VAR_LIST) { if (type_is) { n1 = typ1->v_type == typ2->v_type && typ1->vval.v_list == typ2->vval.v_list; - if (type == TYPE_NEQUAL) { + if (type == EXPR_ISNOT) { n1 = !n1; } } else if (typ1->v_type != typ2->v_type - || (type != TYPE_EQUAL && type != TYPE_NEQUAL)) { + || (type != EXPR_EQUAL && type != EXPR_NEQUAL)) { if (typ1->v_type != typ2->v_type) { EMSG(_("E691: Can only compare List with List")); } else { @@ -10613,7 +10619,7 @@ int typval_compare( } else { // Compare two Lists for being equal or unequal. n1 = tv_list_equal(typ1->vval.v_list, typ2->vval.v_list, ic, false); - if (type == TYPE_NEQUAL) { + if (type == EXPR_NEQUAL) { n1 = !n1; } } @@ -10621,11 +10627,11 @@ int typval_compare( if (type_is) { n1 = typ1->v_type == typ2->v_type && typ1->vval.v_dict == typ2->vval.v_dict; - if (type == TYPE_NEQUAL) { + if (type == EXPR_ISNOT) { n1 = !n1; } } else if (typ1->v_type != typ2->v_type - || (type != TYPE_EQUAL && type != TYPE_NEQUAL)) { + || (type != EXPR_EQUAL && type != EXPR_NEQUAL)) { if (typ1->v_type != typ2->v_type) { EMSG(_("E735: Can only compare Dictionary with Dictionary")); } else { @@ -10636,12 +10642,13 @@ int typval_compare( } else { // Compare two Dictionaries for being equal or unequal. n1 = tv_dict_equal(typ1->vval.v_dict, typ2->vval.v_dict, ic, false); - if (type == TYPE_NEQUAL) { + if (type == EXPR_NEQUAL) { n1 = !n1; } } } else if (tv_is_func(*typ1) || tv_is_func(*typ2)) { - if (type != TYPE_EQUAL && type != TYPE_NEQUAL) { + if (type != EXPR_EQUAL && type != EXPR_NEQUAL + && type != EXPR_IS && type != EXPR_ISNOT) { EMSG(_("E694: Invalid operation for Funcrefs")); tv_clear(typ1); return FAIL; @@ -10663,43 +10670,47 @@ int typval_compare( } else { n1 = tv_equal(typ1, typ2, ic, false); } - if (type == TYPE_NEQUAL) { + if (type == EXPR_NEQUAL || type == EXPR_ISNOT) { n1 = !n1; } } else if ((typ1->v_type == VAR_FLOAT || typ2->v_type == VAR_FLOAT) - && type != TYPE_MATCH && type != TYPE_NOMATCH) { + && type != EXPR_MATCH && type != EXPR_NOMATCH) { // If one of the two variables is a float, compare as a float. // When using "=~" or "!~", always compare as string. const float_T f1 = tv_get_float(typ1); const float_T f2 = tv_get_float(typ2); n1 = false; switch (type) { - case TYPE_EQUAL: n1 = f1 == f2; break; - case TYPE_NEQUAL: n1 = f1 != f2; break; - case TYPE_GREATER: n1 = f1 > f2; break; - case TYPE_GEQUAL: n1 = f1 >= f2; break; - case TYPE_SMALLER: n1 = f1 < f2; break; - case TYPE_SEQUAL: n1 = f1 <= f2; break; - case TYPE_UNKNOWN: - case TYPE_MATCH: - case TYPE_NOMATCH: break; + case EXPR_IS: + case EXPR_EQUAL: n1 = f1 == f2; break; + case EXPR_ISNOT: + case EXPR_NEQUAL: n1 = f1 != f2; break; + case EXPR_GREATER: n1 = f1 > f2; break; + case EXPR_GEQUAL: n1 = f1 >= f2; break; + case EXPR_SMALLER: n1 = f1 < f2; break; + case EXPR_SEQUAL: n1 = f1 <= f2; break; + case EXPR_UNKNOWN: + case EXPR_MATCH: + case EXPR_NOMATCH: break; // avoid gcc warning } } else if ((typ1->v_type == VAR_NUMBER || typ2->v_type == VAR_NUMBER) - && type != TYPE_MATCH && type != TYPE_NOMATCH) { + && type != EXPR_MATCH && type != EXPR_NOMATCH) { // If one of the two variables is a number, compare as a number. // When using "=~" or "!~", always compare as string. n1 = tv_get_number(typ1); n2 = tv_get_number(typ2); switch (type) { - case TYPE_EQUAL: n1 = n1 == n2; break; - case TYPE_NEQUAL: n1 = n1 != n2; break; - case TYPE_GREATER: n1 = n1 > n2; break; - case TYPE_GEQUAL: n1 = n1 >= n2; break; - case TYPE_SMALLER: n1 = n1 < n2; break; - case TYPE_SEQUAL: n1 = n1 <= n2; break; - case TYPE_UNKNOWN: - case TYPE_MATCH: - case TYPE_NOMATCH: break; + case EXPR_IS: + case EXPR_EQUAL: n1 = n1 == n2; break; + case EXPR_ISNOT: + case EXPR_NEQUAL: n1 = n1 != n2; break; + case EXPR_GREATER: n1 = n1 > n2; break; + case EXPR_GEQUAL: n1 = n1 >= n2; break; + case EXPR_SMALLER: n1 = n1 < n2; break; + case EXPR_SEQUAL: n1 = n1 <= n2; break; + case EXPR_UNKNOWN: + case EXPR_MATCH: + case EXPR_NOMATCH: break; // avoid gcc warning } } else { char buf1[NUMBUFLEN]; @@ -10707,28 +10718,30 @@ int typval_compare( const char *const s1 = tv_get_string_buf(typ1, buf1); const char *const s2 = tv_get_string_buf(typ2, buf2); int i; - if (type != TYPE_MATCH && type != TYPE_NOMATCH) { + if (type != EXPR_MATCH && type != EXPR_NOMATCH) { i = mb_strcmp_ic(ic, s1, s2); } else { i = 0; } n1 = false; switch (type) { - case TYPE_EQUAL: n1 = i == 0; break; - case TYPE_NEQUAL: n1 = i != 0; break; - case TYPE_GREATER: n1 = i > 0; break; - case TYPE_GEQUAL: n1 = i >= 0; break; - case TYPE_SMALLER: n1 = i < 0; break; - case TYPE_SEQUAL: n1 = i <= 0; break; - - case TYPE_MATCH: - case TYPE_NOMATCH: + case EXPR_IS: + case EXPR_EQUAL: n1 = i == 0; break; + case EXPR_ISNOT: + case EXPR_NEQUAL: n1 = i != 0; break; + case EXPR_GREATER: n1 = i > 0; break; + case EXPR_GEQUAL: n1 = i >= 0; break; + case EXPR_SMALLER: n1 = i < 0; break; + case EXPR_SEQUAL: n1 = i <= 0; break; + + case EXPR_MATCH: + case EXPR_NOMATCH: n1 = pattern_match((char_u *)s2, (char_u *)s1, ic); - if (type == TYPE_NOMATCH) { + if (type == EXPR_NOMATCH) { n1 = !n1; } break; - case TYPE_UNKNOWN: break; // Avoid gcc warning. + case EXPR_UNKNOWN: break; // avoid gcc warning } } tv_clear(typ1); diff --git a/src/nvim/eval.h b/src/nvim/eval.h index a62d87fcc4..3da4bb8655 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -230,16 +230,18 @@ typedef enum /// types for expressions. typedef enum { - TYPE_UNKNOWN = 0, - TYPE_EQUAL, ///< == - TYPE_NEQUAL, ///< != - TYPE_GREATER, ///< > - TYPE_GEQUAL, ///< >= - TYPE_SMALLER, ///< < - TYPE_SEQUAL, ///< <= - TYPE_MATCH, ///< =~ - TYPE_NOMATCH, ///< !~ -} exptype_T; + EXPR_UNKNOWN = 0, + EXPR_EQUAL, ///< == + EXPR_NEQUAL, ///< != + EXPR_GREATER, ///< > + EXPR_GEQUAL, ///< >= + EXPR_SMALLER, ///< < + EXPR_SEQUAL, ///< <= + EXPR_MATCH, ///< =~ + EXPR_NOMATCH, ///< !~ + EXPR_IS, ///< is + EXPR_ISNOT, ///< isnot +} exprtype_T; /// Type for dict_list function typedef enum { diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index b10e99fc08..77e7c7b3a9 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -251,6 +251,7 @@ return { pow={args=2}, prevnonblank={args=1}, printf={args=varargs(1)}, + prompt_getprompt={args=1}, prompt_setcallback={args={2, 2}}, prompt_setinterrupt={args={2, 2}}, prompt_setprompt={args={2, 2}}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 0d288e2cc2..6d328953f6 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -602,12 +602,7 @@ static void f_bufname(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[0].v_type == VAR_UNKNOWN) { buf = curbuf; } else { - if (!tv_check_str_or_nr(&argvars[0])) { - return; - } - emsg_off++; - buf = tv_get_buf(&argvars[0], false); - emsg_off--; + buf = tv_get_buf_from_arg(&argvars[0]); } if (buf != NULL && buf->b_fname != NULL) { rettv->vval.v_string = (char_u *)xstrdup((char *)buf->b_fname); @@ -627,6 +622,9 @@ static void f_bufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[0].v_type == VAR_UNKNOWN) { buf = curbuf; } else { + // Don't use tv_get_buf_from_arg(); we continue if the buffer wasn't found + // and the second argument isn't zero, but we want to return early if the + // first argument isn't a string or number so only one error is shown. if (!tv_check_str_or_nr(&argvars[0])) { return; } @@ -653,18 +651,12 @@ static void f_bufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void buf_win_common(typval_T *argvars, typval_T *rettv, bool get_nr) { - if (!tv_check_str_or_nr(&argvars[0])) { + const buf_T *const buf = tv_get_buf_from_arg(&argvars[0]); + if (buf == NULL) { // no need to search if invalid arg or buffer not found rettv->vval.v_number = -1; return; } - emsg_off++; - buf_T *buf = tv_get_buf(&argvars[0], true); - if (buf == NULL) { // no need to search if buffer was not found - rettv->vval.v_number = -1; - goto end; - } - int winnr = 0; int winid; bool found_buf = false; @@ -677,8 +669,6 @@ static void buf_win_common(typval_T *argvars, typval_T *rettv, bool get_nr) } } rettv->vval.v_number = (found_buf ? (get_nr ? winnr : winid) : -1); -end: - emsg_off--; } /// "bufwinid(nr)" function @@ -731,6 +721,18 @@ buf_T *tv_get_buf(typval_T *tv, int curtab_only) return buf; } +/// Like tv_get_buf() but give an error message if the type is wrong. +buf_T *tv_get_buf_from_arg(typval_T *const tv) FUNC_ATTR_NONNULL_ALL +{ + if (!tv_check_str_or_nr(tv)) { + return NULL; + } + emsg_off++; + buf_T *const buf = tv_get_buf(tv, false); + emsg_off--; + return buf; +} + /// Get the buffer from "arg" and give an error and return NULL if it is not /// valid. buf_T * get_buf_arg(typval_T *arg) @@ -2799,13 +2801,9 @@ static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } else if (argvars[0].v_type != VAR_UNKNOWN) { // Information about one buffer. Argument specifies the buffer - if (tv_check_num(&argvars[0])) { // issue errmsg if type error - emsg_off++; - argbuf = tv_get_buf(&argvars[0], false); - emsg_off--; - if (argbuf == NULL) { - return; - } + argbuf = tv_get_buf_from_arg(&argvars[0]); + if (argbuf == NULL) { + return; } } @@ -2875,13 +2873,7 @@ static void get_buffer_lines(buf_T *buf, */ static void f_getbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - buf_T *buf = NULL; - - if (tv_check_str_or_nr(&argvars[0])) { - emsg_off++; - buf = tv_get_buf(&argvars[0], false); - emsg_off--; - } + buf_T *const buf = tv_get_buf_from_arg(&argvars[0]); const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf); const linenr_T end = (argvars[2].v_type == VAR_UNKNOWN @@ -6499,6 +6491,26 @@ static void f_prompt_setinterrupt(typval_T *argvars, buf->b_prompt_interrupt= interrupt_callback; } +/// "prompt_getprompt({buffer})" function +void f_prompt_getprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) + FUNC_ATTR_NONNULL_ALL +{ + // return an empty string by default, e.g. it's not a prompt buffer + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + buf_T *const buf = tv_get_buf_from_arg(&argvars[0]); + if (buf == NULL) { + return; + } + + if (!bt_prompt(buf)) { + return; + } + + rettv->vval.v_string = vim_strsave(buf_prompt_text(buf)); +} + // "prompt_setprompt({buffer}, {text})" function static void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 689d05e079..00260bc3f7 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -833,6 +833,8 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, bool islambda = false; char_u numbuf[NUMBUFLEN]; char_u *name; + typval_T *tv_to_free[MAX_FUNC_ARGS]; + int tv_to_free_len = 0; proftime_T wait_start; proftime_T call_start; int started_profiling = false; @@ -985,6 +987,11 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, v->di_tv = isdefault ? def_rettv : argvars[i]; v->di_tv.v_lock = VAR_FIXED; + if (isdefault) { + // Need to free this later, no matter where it's stored. + tv_to_free[tv_to_free_len++] = &v->di_tv; + } + if (addlocal) { // Named arguments can be accessed without the "a:" prefix in lambda // expressions. Add to the l: dict. @@ -1209,7 +1216,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, did_emsg |= save_did_emsg; depth--; - + for (int i = 0; i < tv_to_free_len; i++) { + tv_clear(tv_to_free[i]); + } cleanup_function_call(fc); if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) { diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c index 0b1ecb12e2..c02f730431 100644 --- a/src/nvim/event/libuv_process.c +++ b/src/nvim/event/libuv_process.c @@ -82,7 +82,7 @@ int libuv_process_spawn(LibuvProcess *uvproc) int status; if ((status = uv_spawn(&proc->loop->uv, &uvproc->uv, &uvproc->uvopts))) { - ELOG("uv_spawn failed: %s", uv_strerror(status)); + ELOG("uv_spawn(%s) failed: %s", uvproc->uvopts.file, uv_strerror(status)); if (uvproc->uvopts.env) { os_free_fullenv(uvproc->uvopts.env); } diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index b191e8cf67..3e330b88a2 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -968,12 +968,6 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) mark_adjust_nofold(last_line - num_lines + 1, last_line, -(last_line - dest - extra), 0L, kExtmarkNOOP); - // extmarks are handled separately - extmark_move_region(curbuf, line1-1, 0, start_byte, - line2-line1+1, 0, extent_byte, - dest+line_off, 0, dest_byte+byte_off, - kExtmarkUndo); - changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra, false); // send update regarding the new lines that were added @@ -995,6 +989,11 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) smsg(_("%" PRId64 " lines moved"), (int64_t)num_lines); } + extmark_move_region(curbuf, line1-1, 0, start_byte, + line2-line1+1, 0, extent_byte, + dest+line_off, 0, dest_byte+byte_off, + kExtmarkUndo); + /* * Leave the cursor on the last of the moved lines. */ diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 2965ea7496..d99383303b 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -928,6 +928,12 @@ module.cmds = { func='ex_edit', }, { + command='eval', + flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN), + addr_type='ADDR_NONE', + func='ex_eval', + }, + { command='ex', flags=bit.bor(BANG, FILE1, CMDARG, ARGOPT, TRLBAR), addr_type='ADDR_NONE', diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index e394edb032..317ca465e1 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -876,7 +876,7 @@ debuggy_find( debug_newval = typval_tostring(bp->dbg_val); line = true; } else { - if (typval_compare(tv, bp->dbg_val, TYPE_EQUAL, true, false) == OK + if (typval_compare(tv, bp->dbg_val, EXPR_IS, false) == OK && tv->vval.v_number == false) { line = true; debug_oldval = typval_tostring(bp->dbg_val); @@ -2719,16 +2719,13 @@ static char_u *get_str_line(int c, void *cookie, int indent, bool do_concat) while (!(p->buf[i] == '\n' || p->buf[i] == '\0')) { i++; } - char buf[2046]; - char *dst; - dst = xstpncpy(buf, (char *)p->buf + p->offset, i - p->offset); - if ((uint32_t)(dst - buf) != i - p->offset) { - smsg(_(":source error parsing command %s"), p->buf); - return NULL; - } - buf[i - p->offset] = '\0'; + size_t line_length = i - p->offset; + garray_T ga; + ga_init(&ga, (int)sizeof(char_u), (int)line_length); + ga_concat_len(&ga, (char *)p->buf + p->offset, line_length); + ga_append(&ga, '\0'); p->offset = i + 1; - return (char_u *)xstrdup(buf); + return ga.ga_data; } static int source_using_linegetter(void *cookie, diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index d1eddfc74f..ae5c334592 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -1857,6 +1857,7 @@ static char_u * do_one_cmd(char_u **cmdlinep, case CMD_echoerr: case CMD_echomsg: case CMD_echon: + case CMD_eval: case CMD_execute: case CMD_filter: case CMD_help: diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index 0917c6dd02..5ca88002f1 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -788,6 +788,15 @@ void report_discard_pending(int pending, void *value) } } +// ":eval". +void ex_eval(exarg_T *eap) +{ + typval_T tv; + + if (eval0(eap->arg, &tv, &eap->nextcmd, !eap->skip) == OK) { + tv_clear(&tv); + } +} /* * ":if". diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c index 09453e100d..b11ec4ad05 100644 --- a/src/nvim/ex_session.c +++ b/src/nvim/ex_session.c @@ -690,18 +690,16 @@ static int makeopens(FILE *fd, char_u *dirnow) return FAIL; } - // - // Save current window layout. - // - PUTLINE_FAIL("set splitbelow splitright"); - if (ses_win_rec(fd, tab_topframe) == FAIL) { - return FAIL; - } - if (!p_sb && put_line(fd, "set nosplitbelow") == FAIL) { - return FAIL; - } - if (!p_spr && put_line(fd, "set nosplitright") == FAIL) { - return FAIL; + if (tab_topframe->fr_layout != FR_LEAF) { + // Save current window layout. + PUTLINE_FAIL("let s:save_splitbelow = &splitbelow"); + PUTLINE_FAIL("let s:save_splitright = &splitright"); + PUTLINE_FAIL("set splitbelow splitright"); + if (ses_win_rec(fd, tab_topframe) == FAIL) { + return FAIL; + } + PUTLINE_FAIL("let &splitbelow = s:save_splitbelow"); + PUTLINE_FAIL("let &splitright = s:save_splitright"); } // @@ -720,22 +718,26 @@ static int makeopens(FILE *fd, char_u *dirnow) } } - // Go to the first window. - PUTLINE_FAIL("wincmd t"); - - // If more than one window, see if sizes can be restored. - // First set 'winheight' and 'winwidth' to 1 to avoid the windows being - // resized when moving between windows. - // Do this before restoring the view, so that the topline and the - // cursor can be set. This is done again below. - // winminheight and winminwidth need to be set to avoid an error if the - // user has set winheight or winwidth. - if (fprintf(fd, - "set winminheight=0\n" - "set winheight=1\n" - "set winminwidth=0\n" - "set winwidth=1\n") < 0) { - return FAIL; + if (tab_firstwin->w_next != NULL) { + // Go to the first window. + PUTLINE_FAIL("wincmd t"); + + // If more than one window, see if sizes can be restored. + // First set 'winheight' and 'winwidth' to 1 to avoid the windows + // being resized when moving between windows. + // Do this before restoring the view, so that the topline and the + // cursor can be set. This is done again below. + // winminheight and winminwidth need to be set to avoid an error if + // the user has set winheight or winwidth. + PUTLINE_FAIL("let s:save_winminheight = &winminheight"); + PUTLINE_FAIL("let s:save_winminwidth = &winminwidth"); + if (fprintf(fd, + "set winminheight=0\n" + "set winheight=1\n" + "set winminwidth=0\n" + "set winwidth=1\n") < 0) { + return FAIL; + } } if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) { return FAIL; @@ -817,18 +819,20 @@ static int makeopens(FILE *fd, char_u *dirnow) return FAIL; } - // Re-apply options. + // Re-apply 'winheight', 'winwidth' and 'shortmess'. if (fprintf(fd, "set winheight=%" PRId64 " winwidth=%" PRId64 - " winminheight=%" PRId64 " winminwidth=%" PRId64 " shortmess=%s\n", (int64_t)p_wh, (int64_t)p_wiw, - (int64_t)p_wmh, - (int64_t)p_wmw, p_shm) < 0) { return FAIL; } + if (tab_firstwin->w_next != NULL) { + // Restore 'winminheight' and 'winminwidth'. + PUTLINE_FAIL("let &winminheight = s:save_winminheight"); + PUTLINE_FAIL("let &winminwidth = s:save_winminwidth"); + } // // Lastly, execute the x.vim file if it exists. diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index cacbeddb32..2906a2196b 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -702,6 +702,7 @@ void extmark_move_region( int new_row, colnr_T new_col, bcount_t new_byte, ExtmarkOp undo) { + curbuf->deleted_bytes2 = 0; // TODO(bfredl): this is not synced to the buffer state inside the callback. // But unless we make the undo implementation smarter, this is not ensured // anyway. diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 65bd809436..792ef81665 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -4947,11 +4947,11 @@ int buf_check_timestamp(buf_T *buf) (void)msg_end(); if (emsg_silent == 0) { ui_flush(); - /* give the user some time to think about it */ - os_delay(1000L, true); + // give the user some time to think about it + os_delay(1004L, true); - /* don't redraw and erase the message */ - redraw_cmdline = FALSE; + // don't redraw and erase the message + redraw_cmdline = false; } } already_warned = TRUE; diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 9b8e9ff8cc..f99a2dd0fe 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -17,6 +17,7 @@ #include "nvim/api/vim.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/vim.h" +#include "nvim/extmark.h" #include "nvim/ex_getln.h" #include "nvim/ex_cmds2.h" #include "nvim/map.h" @@ -1243,13 +1244,16 @@ void ex_luado(exarg_T *const eap) break; } lua_pushvalue(lstate, -1); - lua_pushstring(lstate, (const char *)ml_get_buf(curbuf, l, false)); + const char *old_line = (const char *)ml_get_buf(curbuf, l, false); + lua_pushstring(lstate, old_line); lua_pushnumber(lstate, (lua_Number)l); if (lua_pcall(lstate, 2, 1, 0)) { nlua_error(lstate, _("E5111: Error calling lua: %.*s")); break; } if (lua_isstring(lstate, -1)) { + size_t old_line_len = STRLEN(old_line); + size_t new_line_len; const char *const new_line = lua_tolstring(lstate, -1, &new_line_len); char *const new_line_transformed = xmemdupz(new_line, new_line_len); @@ -1259,7 +1263,7 @@ void ex_luado(exarg_T *const eap) } } ml_replace(l, (char_u *)new_line_transformed, false); - changed_bytes(l, 0); + inserted_bytes(l, 0, (int)old_line_len, (int)new_line_len); } lua_pop(lstate, 1); } diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index eb54ff28ee..3994c5bc5b 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -309,7 +309,9 @@ setmetatable(vim, { }) -- An easier alias for commands. -vim.cmd = vim.api.nvim_command +vim.cmd = function(command) + return vim.api.nvim_exec(command, false) +end -- These are the vim.env/v/g/o/bo/wo variable magic accessors. do @@ -398,7 +400,10 @@ do wfw = true; winbl = true; winblend = true; winfixheight = true; winfixwidth = true; winhighlight = true; winhl = true; wrap = true; } + + --@private local function new_buf_opt_accessor(bufnr) + --@private local function get(k) if window_options[k] then return a.nvim_err_writeln(k.." is a window option, not a buffer option") @@ -408,23 +413,34 @@ do end return a.nvim_buf_get_option(bufnr or 0, k) end + + --@private local function set(k, v) if window_options[k] then return a.nvim_err_writeln(k.." is a window option, not a buffer option") end return a.nvim_buf_set_option(bufnr or 0, k, v) end + return make_meta_accessor(get, set) end vim.bo = new_buf_opt_accessor(nil) + + --@private local function new_win_opt_accessor(winnr) + + --@private local function get(k) if winnr == nil and type(k) == "number" then return new_win_opt_accessor(k) end return a.nvim_win_get_option(winnr or 0, k) end - local function set(k, v) return a.nvim_win_set_option(winnr or 0, k, v) end + + --@private + local function set(k, v) + return a.nvim_win_set_option(winnr or 0, k, v) + end return make_meta_accessor(get, set) end vim.wo = new_win_opt_accessor(nil) diff --git a/src/nvim/main.c b/src/nvim/main.c index 7064f2a068..56cd97f133 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -375,7 +375,7 @@ int main(int argc, char **argv) // Does ":filetype plugin indent on". filetype_maybe_enable(); // Sources syntax/syntax.vim, which calls `:filetype on`. - syn_maybe_on(); + syn_maybe_enable(); } // Read all the plugin files. diff --git a/src/nvim/message.c b/src/nvim/message.c index 7c98d3c6b5..1783f62247 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -2265,12 +2265,14 @@ void msg_scroll_up(bool may_throttle) /// per screen update. /// /// NB: The bookkeeping is quite messy, and rests on a bunch of poorly -/// documented assumtions. For instance that the message area always grows while -/// being throttled, messages are only being output on the last line etc. +/// documented assumptions. For instance that the message area always grows +/// while being throttled, messages are only being output on the last line +/// etc. /// -/// Probably message scrollback storage should reimplented as a file_buffer, and -/// message scrolling in TUI be reimplemented as a modal floating window. Then -/// we get throttling "for free" using standard redraw_later code paths. +/// Probably message scrollback storage should be reimplemented as a +/// file_buffer, and message scrolling in TUI be reimplemented as a modal +/// floating window. Then we get throttling "for free" using standard +/// redraw_later code paths. void msg_scroll_flush(void) { if (msg_grid.throttled) { diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index a0b439ac45..a2d8859c68 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -219,7 +219,7 @@ static void receive_msgpack(Stream *stream, RBuffer *rbuf, size_t c, char buf[256]; snprintf(buf, sizeof(buf), "ch %" PRIu64 " was closed by the client", channel->id); - call_set_error(channel, buf, WARN_LOG_LEVEL); + call_set_error(channel, buf, INFO_LOG_LEVEL); goto end; } diff --git a/src/nvim/normal.c b/src/nvim/normal.c index f016ef6813..c948881eca 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -630,9 +630,9 @@ static void normal_redraw_mode_message(NormalState *s) ui_cursor_shape(); // show different cursor shape ui_flush(); if (msg_scroll || emsg_on_display) { - os_delay(1000L, true); // wait at least one second + os_delay(1003L, true); // wait at least one second } - os_delay(3000L, false); // wait up to three seconds + os_delay(3003L, false); // wait up to three seconds State = save_State; msg_scroll = false; diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 2cd71f2360..190ca2e93b 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -1676,12 +1676,18 @@ int op_delete(oparg_T *oap) curbuf_splice_pending++; pos_T startpos = curwin->w_cursor; // start position for delete + bcount_t deleted_bytes = (bcount_t)STRLEN( + ml_get(startpos.lnum)) + 1 - startpos.col; truncate_line(true); // delete from cursor to end of line curpos = curwin->w_cursor; // remember curwin->w_cursor curwin->w_cursor.lnum++; + + for (linenr_T i = 1; i <= oap->line_count - 2; i++) { + deleted_bytes += (bcount_t)STRLEN( + ml_get(startpos.lnum + i)) + 1; + } del_lines(oap->line_count - 2, false); - bcount_t deleted_bytes = (bcount_t)curbuf->deleted_bytes2 - startpos.col; // delete from start of line until op_end n = (oap->end.col + 1 - !oap->inclusive); diff --git a/src/nvim/option.c b/src/nvim/option.c index 914b92618c..666c526a18 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -7583,9 +7583,19 @@ int csh_like_shell(void) /// buffer signs and on user configuration. int win_signcol_count(win_T *wp) { + return win_signcol_configured(wp, NULL); +} + +/// Return the number of requested sign columns, based on user / configuration. +int win_signcol_configured(win_T *wp, int *is_fixed) +{ int minimum = 0, maximum = 1, needed_signcols; const char *scl = (const char *)wp->w_p_scl; + if (is_fixed) { + *is_fixed = 1; + } + // Note: It checks "no" or "number" in 'signcolumn' option if (*scl == 'n' && (*(scl + 1) == 'o' || (*(scl + 1) == 'u' @@ -7603,7 +7613,11 @@ int win_signcol_count(win_T *wp) return 1; } - // auto or auto:<NUM> + if (is_fixed) { + // auto or auto:<NUM> + *is_fixed = 0; + } + if (!strncmp(scl, "auto:", 5)) { // Variable depending on a configuration maximum = scl[5] - '0'; diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index d794969ab5..36d6dbe2db 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -175,7 +175,7 @@ static void init_child(PtyProcess *ptyproc) Process *proc = (Process *)ptyproc; if (proc->cwd && os_chdir(proc->cwd) != 0) { - ELOG("chdir failed: %s", strerror(errno)); + ELOG("chdir(%s) failed: %s", proc->cwd, strerror(errno)); return; } @@ -184,7 +184,7 @@ static void init_child(PtyProcess *ptyproc) assert(proc->env); environ = tv_dict_to_env(proc->env); execvp(prog, proc->argv); - ELOG("execvp failed: %s: %s", strerror(errno), prog); + ELOG("execvp(%s) failed: %s", prog, strerror(errno)); _exit(122); // 122 is EXEC_FAILED in the Vim source. } diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index 94444e4d23..2bf73d08e6 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -203,11 +203,13 @@ int pty_process_spawn(PtyProcess *ptyproc) cleanup: if (status) { // In the case of an error of MultiByteToWideChar or CreateProcessW. - ELOG("pty_process_spawn: %s: error code: %d", emsg, status); + ELOG("pty_process_spawn(%s): %s: error code: %d", + proc->argv[0], emsg, status); status = os_translate_sys_error(status); } else if (err != NULL) { status = (int)winpty_error_code(err); - ELOG("pty_process_spawn: %s: error code: %d", emsg, status); + ELOG("pty_process_spawn(%s): %s: error code: %d", + proc->argv[0], emsg, status); status = translate_winpty_error(status); } winpty_error_free(err); diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c index e7e0dc4013..9ea74716aa 100644 --- a/src/nvim/os/time.c +++ b/src/nvim/os/time.c @@ -62,6 +62,7 @@ uint64_t os_now(void) /// @param ignoreinput If true, only SIGINT (CTRL-C) can interrupt. void os_delay(uint64_t ms, bool ignoreinput) { + DLOG("%" PRIu64 " ms", ms); if (ignoreinput) { if (ms > INT_MAX) { ms = INT_MAX; diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index d7693c7a6f..184f5da97d 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -6665,6 +6665,10 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int len = 0; /* init for GCC */ static char_u *eval_result = NULL; + // We need to keep track of how many backslashes we escape, so that the byte + // counts for `extmark_splice` are correct. + int num_escaped = 0; + // Be paranoid... if ((source == NULL && expr == NULL) || dest == NULL) { EMSG(_(e_null)); @@ -6840,6 +6844,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, // later. Used to insert a literal CR. default: if (backslash) { + num_escaped += 1; if (copy) { *dst = '\\'; } @@ -6979,7 +6984,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, *dst = NUL; exit: - return (int)((dst - dest) + 1); + return (int)((dst - dest) + 1 - num_escaped); } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 9fb2eb2772..5151d82c1b 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -2101,6 +2101,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool search_attr_from_match = false; // if search_attr is from :match bool has_decor = false; // this buffer has decoration bool do_virttext = false; // draw virtual text for this line + int win_col_offset; // offsett for window columns char_u buf_fold[FOLD_TEXT_LEN + 1]; // Hold value returned by get_foldtext @@ -2790,6 +2791,10 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, } } + if (draw_state == WL_NR && n_extra == 0) { + win_col_offset = off; + } + if (wp->w_briopt_sbr && draw_state == WL_BRI - 1 && n_extra == 0 && *p_sbr != NUL) { // draw indent after showbreak value @@ -2904,7 +2909,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, && vcol >= (long)wp->w_virtcol) || (number_only && draw_state > WL_NR)) && filler_todo <= 0) { - draw_virt_text(buf, &col, grid->Columns); + draw_virt_text(buf, win_col_offset, &col, grid->Columns); grid_put_linebuf(grid, row, 0, col, -grid->Columns, wp->w_p_rl, wp, wp->w_hl_attr_normal, false); // Pretend we have finished updating the window. Except when @@ -3945,13 +3950,15 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, draw_color_col = advance_color_col(VCOL_HLC, &color_cols); VirtText virt_text = KV_INITIAL_VALUE; + bool has_aligned = false; if (err_text) { int hl_err = syn_check_group((char_u *)S_LEN("ErrorMsg")); kv_push(virt_text, ((VirtTextChunk){ .text = err_text, .hl_id = hl_err })); do_virttext = true; } else if (has_decor) { - virt_text = decor_redraw_virt_text(wp->w_buffer, &decor_state); + virt_text = decor_redraw_eol(wp->w_buffer, &decor_state, &line_attr, + &has_aligned); if (kv_size(virt_text)) { do_virttext = true; } @@ -3963,7 +3970,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, grid->Columns * (row - startrow + 1) + v && lnum != wp->w_cursor.lnum) || draw_color_col || line_attr_lowprio || line_attr - || diff_hlf != (hlf_T)0 || do_virttext)) { + || diff_hlf != (hlf_T)0 || do_virttext + || has_aligned)) { int rightmost_vcol = 0; int i; @@ -4001,7 +4009,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, } int base_attr = hl_combine_attr(line_attr_lowprio, diff_attr); - if (base_attr || line_attr) { + if (base_attr || line_attr || has_aligned) { rightmost_vcol = INT_MAX; } @@ -4079,7 +4087,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, } } - draw_virt_text(buf, &col, grid->Columns); + draw_virt_text(buf, win_col_offset, &col, grid->Columns); grid_put_linebuf(grid, row, 0, col, grid->Columns, wp->w_p_rl, wp, wp->w_hl_attr_normal, false); row++; @@ -4300,7 +4308,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, && !wp->w_p_rl; // Not right-to-left. int draw_col = col - boguscols; - draw_virt_text(buf, &draw_col, grid->Columns); + draw_virt_text(buf, win_col_offset, &draw_col, grid->Columns); grid_put_linebuf(grid, row, 0, draw_col, grid->Columns, wp->w_p_rl, wp, wp->w_hl_attr_normal, wrap); if (wrap) { @@ -4377,51 +4385,62 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, return row; } -void draw_virt_text(buf_T *buf, int *end_col, int max_col) +void draw_virt_text(buf_T *buf, int col_off, int *end_col, int max_col) { DecorState *state = &decor_state; + int right_pos = max_col; for (size_t i = 0; i < kv_size(state->active); i++) { - HlRange *item = &kv_A(state->active, i); - if (item->start_row == state->row && kv_size(item->virt_text) - && item->virt_text_pos == kVTOverlay - && item->virt_col >= 0) { - VirtText vt = item->virt_text; - LineState s = LINE_STATE(""); - int virt_attr = 0; - int col = item->virt_col; - size_t virt_pos = 0; - item->virt_col = -2; // deactivate + DecorRange *item = &kv_A(state->active, i); + if (item->start_row == state->row && kv_size(item->decor.virt_text)) { + if (item->win_col == -1) { + if (item->decor.virt_text_pos == kVTRightAlign) { + right_pos -= item->decor.col; + item->win_col = right_pos; + } else if (item->decor.virt_text_pos == kVTWinCol) { + item->win_col = MAX(item->decor.col+col_off, 0); + } + } + if (item->win_col < 0) { + continue; + } + VirtText vt = item->decor.virt_text; + HlMode hl_mode = item->decor.hl_mode; + LineState s = LINE_STATE(""); + int virt_attr = 0; + int col = item->win_col; + size_t virt_pos = 0; + item->win_col = -2; // deactivate - while (col < max_col) { - if (!*s.p) { - if (virt_pos == kv_size(vt)) { - break; - } - s.p = kv_A(vt, virt_pos).text; - int hl_id = kv_A(vt, virt_pos).hl_id; - virt_attr = hl_id > 0 ? syn_id2attr(hl_id) : 0; - virt_pos++; - continue; - } - int attr; - bool through = false; - if (item->hl_mode == kHlModeCombine) { - attr = hl_combine_attr(linebuf_attr[col], virt_attr); - } else if (item->hl_mode == kHlModeBlend) { - through = (*s.p == ' '); - attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through); - } else { - attr = virt_attr; + while (col < max_col) { + if (!*s.p) { + if (virt_pos == kv_size(vt)) { + break; } - schar_T dummy[2]; - int cells = line_putchar(&s, through ? dummy : &linebuf_char[col], - max_col-col, false); + s.p = kv_A(vt, virt_pos).text; + int hl_id = kv_A(vt, virt_pos).hl_id; + virt_attr = hl_id > 0 ? syn_id2attr(hl_id) : 0; + virt_pos++; + continue; + } + int attr; + bool through = false; + if (hl_mode == kHlModeCombine) { + attr = hl_combine_attr(linebuf_attr[col], virt_attr); + } else if (hl_mode == kHlModeBlend) { + through = (*s.p == ' '); + attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through); + } else { + attr = virt_attr; + } + schar_T dummy[2]; + int cells = line_putchar(&s, through ? dummy : &linebuf_char[col], + max_col-col, false); + linebuf_attr[col++] = attr; + if (cells > 1) { linebuf_attr[col++] = attr; - if (cells > 1) { - linebuf_attr[col++] = attr; - } } - *end_col = MAX(*end_col, col); + } + *end_col = MAX(*end_col, col); } } } @@ -6226,7 +6245,7 @@ void check_for_delay(int check_msg_scroll) && !did_wait_return && emsg_silent == 0) { ui_flush(); - os_delay(1000L, true); + os_delay(1006L, true); emsg_on_display = false; if (check_msg_scroll) { msg_scroll = false; diff --git a/src/nvim/search.c b/src/nvim/search.c index c4479a077e..abe05bbd12 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -2373,10 +2373,11 @@ showmatch( * brief pause, unless 'm' is present in 'cpo' and a character is * available. */ - if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL) - os_delay(p_mat * 100L, true); - else if (!char_avail()) - os_delay(p_mat * 100L, false); + if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL) { + os_delay(p_mat * 100L + 8, true); + } else if (!char_avail()) { + os_delay(p_mat * 100L + 9, false); + } curwin->w_cursor = save_cursor; // restore cursor position *so = save_so; *siso = save_siso; diff --git a/src/nvim/sign.c b/src/nvim/sign.c index c7dc1a5b22..97e64c6c4c 100644 --- a/src/nvim/sign.c +++ b/src/nvim/sign.c @@ -18,6 +18,7 @@ #include "nvim/move.h" #include "nvim/screen.h" #include "nvim/syntax.h" +#include "nvim/option.h" /// Struct to hold the sign properties. typedef struct sign sign_T; @@ -726,16 +727,30 @@ void sign_mark_adjust( long amount_after ) { - sign_entry_T *sign; // a sign in a b_signlist - linenr_T new_lnum; // new line number to assign to sign + sign_entry_T *sign; // a sign in a b_signlist + sign_entry_T *next; // the next sign in a b_signlist + sign_entry_T *last = NULL; // pointer to pointer to current sign + sign_entry_T **lastp = NULL; // pointer to pointer to current sign + linenr_T new_lnum; // new line number to assign to sign + int is_fixed = 0; + int signcol = win_signcol_configured(curwin, &is_fixed); curbuf->b_signcols_max = -1; + lastp = &curbuf->b_signlist; - FOR_ALL_SIGNS_IN_BUF(curbuf, sign) { + for (sign = curbuf->b_signlist; sign != NULL; sign = next) { + next = sign->se_next; new_lnum = sign->se_lnum; if (sign->se_lnum >= line1 && sign->se_lnum <= line2) { if (amount != MAXLNUM) { new_lnum += amount; + } else if (!is_fixed || signcol >= 2) { + *lastp = next; + if (next) { + next->se_prev = last; + } + xfree(sign); + continue; } } else if (sign->se_lnum > line2) { new_lnum += amount_after; @@ -746,6 +761,9 @@ void sign_mark_adjust( if (sign->se_lnum >= line1 && new_lnum <= curbuf->b_ml.ml_line_count) { sign->se_lnum = new_lnum; } + + last = sign; + lastp = &sign->se_next; } } diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 825aef1465..ed886ab7f9 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -3469,13 +3469,13 @@ static void syn_cmd_onoff(exarg_T *eap, char *name) } } -void syn_maybe_on(void) +void syn_maybe_enable(void) { if (!did_syntax_onoff) { exarg_T ea; ea.arg = (char_u *)""; ea.skip = false; - syn_cmd_onoff(&ea, "syntax"); + syn_cmd_enable(&ea, false); } } @@ -5306,13 +5306,17 @@ get_id_list( xfree(name); break; } - if (name[1] == 'A') - id = SYNID_ALLBUT; - else if (name[1] == 'T') - id = SYNID_TOP; - else - id = SYNID_CONTAINED; - id += current_syn_inc_tag; + if (name[1] == 'A') { + id = SYNID_ALLBUT + current_syn_inc_tag; + } else if (name[1] == 'T') { + if (curwin->w_s->b_syn_topgrp >= SYNID_CLUSTER) { + id = curwin->w_s->b_syn_topgrp; + } else { + id = SYNID_TOP + current_syn_inc_tag; + } + } else { + id = SYNID_CONTAINED + current_syn_inc_tag; + } } else if (name[1] == '@') { if (skip) { id = -1; diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 588821f260..a6310344e9 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -625,7 +625,7 @@ do_tag( } if (ic && !msg_scrolled && msg_silent == 0) { ui_flush(); - os_delay(1000L, true); + os_delay(1007L, true); } } @@ -2853,7 +2853,7 @@ static int jumpto_tag( MSG(_("E435: Couldn't find tag, just guessing!")); if (!msg_scrolled && msg_silent == 0) { ui_flush(); - os_delay(1000L, true); + os_delay(1010L, true); } } retval = OK; diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index 71af3eead7..e50602ccad 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -35,6 +35,7 @@ source test_popup.vim source test_put.vim source test_rename.vim source test_scroll_opt.vim +source test_shift.vim source test_sort.vim source test_sha256.vim source test_suspend.vim diff --git a/src/nvim/testdir/test_alot_utf8.vim b/src/nvim/testdir/test_alot_utf8.vim index be0bd01413..70f14320a6 100644 --- a/src/nvim/testdir/test_alot_utf8.vim +++ b/src/nvim/testdir/test_alot_utf8.vim @@ -6,7 +6,6 @@ source test_charsearch_utf8.vim source test_expr_utf8.vim -source test_listlbr_utf8.vim source test_matchadd_conceal_utf8.vim source test_mksession_utf8.vim source test_regexp_utf8.vim diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 5e99edf233..5611560b1b 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -276,28 +276,28 @@ func Test_augroup_warning() augroup TheWarning au VimEnter * echo 'entering' augroup END - call assert_true(match(execute('au VimEnter'), "TheWarning.*VimEnter") >= 0) + call assert_match("TheWarning.*VimEnter", execute('au VimEnter')) redir => res augroup! TheWarning redir END - call assert_true(match(res, "W19:") >= 0) - call assert_true(match(execute('au VimEnter'), "-Deleted-.*VimEnter") >= 0) + call assert_match("W19:", res) + call assert_match("-Deleted-.*VimEnter", execute('au VimEnter')) " check "Another" does not take the pace of the deleted entry augroup Another augroup END - call assert_true(match(execute('au VimEnter'), "-Deleted-.*VimEnter") >= 0) + call assert_match("-Deleted-.*VimEnter", execute('au VimEnter')) augroup! Another " no warning for postpone aucmd delete augroup StartOK au VimEnter * call RemoveGroup() augroup END - call assert_true(match(execute('au VimEnter'), "StartOK.*VimEnter") >= 0) + call assert_match("StartOK.*VimEnter", execute('au VimEnter')) redir => res doautocmd VimEnter redir END - call assert_true(match(res, "W19:") < 0) + call assert_notmatch("W19:", res) au! VimEnter endfunc @@ -325,7 +325,7 @@ func Test_augroup_deleted() au VimEnter * echo augroup end augroup! x - call assert_true(match(execute('au VimEnter'), "-Deleted-.*VimEnter") >= 0) + call assert_match("-Deleted-.*VimEnter", execute('au VimEnter')) au! VimEnter endfunc diff --git a/src/nvim/testdir/test_backspace_opt.vim b/src/nvim/testdir/test_backspace_opt.vim index d680b442db..11459991ea 100644 --- a/src/nvim/testdir/test_backspace_opt.vim +++ b/src/nvim/testdir/test_backspace_opt.vim @@ -1,15 +1,5 @@ " Tests for 'backspace' settings -func Exec(expr) - let str='' - try - exec a:expr - catch /.*/ - let str=v:exception - endtry - return str -endfunc - func Test_backspace_option() set backspace= call assert_equal('', &backspace) @@ -41,10 +31,10 @@ func Test_backspace_option() set backspace-=eol call assert_equal('', &backspace) " Check the error - call assert_equal(0, match(Exec('set backspace=ABC'), '.*E474')) - call assert_equal(0, match(Exec('set backspace+=def'), '.*E474')) + call assert_fails('set backspace=ABC', 'E474:') + call assert_fails('set backspace+=def', 'E474:') " NOTE: Vim doesn't check following error... - "call assert_equal(0, match(Exec('set backspace-=ghi'), '.*E474')) + "call assert_fails('set backspace-=ghi', 'E474:') " Check backwards compatibility with version 5.4 and earlier set backspace=0 @@ -55,8 +45,8 @@ func Test_backspace_option() call assert_equal('2', &backspace) set backspace=3 call assert_equal('3', &backspace) - call assert_false(match(Exec('set backspace=4'), '.*E474')) - call assert_false(match(Exec('set backspace=10'), '.*E474')) + call assert_fails('set backspace=4', 'E474:') + call assert_fails('set backspace=10', 'E474:') " Cleared when 'compatible' is set " set compatible diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim index 98a3e60368..15557056ee 100644 --- a/src/nvim/testdir/test_excmd.vim +++ b/src/nvim/testdir/test_excmd.vim @@ -47,7 +47,7 @@ func Test_buffers_lastused() endfor call assert_equal(['bufb', 'bufa', 'bufc'], names) - call assert_match('[0-2] seconds ago', bufs[1][1]) + call assert_match('[0-2] seconds\= ago', bufs[1][1]) bwipeout bufa bwipeout bufb diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim index 09d79979ce..0b41a1127a 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -501,3 +501,12 @@ func Test_empty_concatenate() call assert_equal('b', 'a'[4:0] . 'b') call assert_equal('b', 'b' . 'a'[4:0]) endfunc + +func Test_eval_after_if() + let s:val = '' + func SetVal(x) + let s:val ..= a:x + endfunc + if 0 | eval SetVal('a') | endif | call SetVal('b') + call assert_equal('b', s:val) +endfunc diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 1a98dc6451..3cfc964f0a 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -275,6 +275,8 @@ let s:filename_checks = { \ 'lss': ['file.lss'], \ 'lua': ['file.lua', 'file.rockspec', 'file.nse'], \ 'lynx': ['lynx.cfg'], + \ 'm3build': ['m3makefile', 'm3overrides'], + \ 'm3quake': ['file.quake', 'cm3.cfg'], \ 'm4': ['file.at'], \ 'mail': ['snd.123', '.letter', '.letter.123', '.followup', '.article', '.article.123', 'pico.123', 'mutt-xx-xxx', 'muttng-xx-xxx', 'ae123.txt', 'file.eml'], \ 'mailaliases': ['/etc/mail/aliases', '/etc/aliases'], @@ -374,6 +376,7 @@ let s:filename_checks = { \ 'ps1': ['file.ps1', 'file.psd1', 'file.psm1', 'file.pssc'], \ 'ps1xml': ['file.ps1xml'], \ 'psf': ['file.psf'], + \ 'psl': ['file.psl'], \ 'puppet': ['file.pp'], \ 'pyrex': ['file.pyx', 'file.pxd'], \ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', 'file.ptl', 'file.pyi', 'SConstruct'], diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 555f549743..93f567b3a0 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -1071,10 +1071,10 @@ func Test_inputlist() endfunc func Test_balloon_show() - if has('balloon_eval') - " This won't do anything but must not crash either. - call balloon_show('hi!') - endif + CheckFeature balloon_eval + + " This won't do anything but must not crash either. + call balloon_show('hi!') endfunc func Test_shellescape() @@ -1448,4 +1448,12 @@ func Test_nr2char() call assert_equal("\x80\xfc\b\xfd\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX", eval('"\<M-' .. nr2char(0x40000000) .. '>"')) endfunc +func HasDefault(msg = 'msg') + return a:msg +endfunc + +func Test_default_arg_value() + call assert_equal('msg', HasDefault()) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_menu.vim b/src/nvim/testdir/test_menu.vim index 055d944b15..de6d4aa359 100644 --- a/src/nvim/testdir/test_menu.vim +++ b/src/nvim/testdir/test_menu.vim @@ -11,7 +11,13 @@ func Test_load_menu() call assert_report('error while loading menus: ' . v:exception) endtry call assert_match('browse confirm w', execute(':menu File.Save')) + + let v:errmsg = '' + doautocmd LoadBufferMenu VimEnter + call assert_equal('', v:errmsg) + source $VIMRUNTIME/delmenu.vim + call assert_equal('', v:errmsg) endfunc func Test_translate_menu() diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim index 8486f3ff68..7bb76ad9eb 100644 --- a/src/nvim/testdir/test_mksession.vim +++ b/src/nvim/testdir/test_mksession.vim @@ -680,6 +680,24 @@ func Test_mksession_winpos() set sessionoptions& endfunc +" Test for mksession without options restores winminheight +func Test_mksession_winminheight() + set sessionoptions-=options + split + mksession! Xtest_mks.out + let found_restore = 0 + let lines = readfile('Xtest_mks.out') + for line in lines + if line =~ '= s:save_winmin\(width\|height\)' + let found_restore += 1 + endif + endfor + call assert_equal(2, found_restore) + call delete('Xtest_mks.out') + close + set sessionoptions& +endfunc + " Test for mksession with 'compatible' option func Test_mksession_compatible() throw 'skipped: Nvim does not support "compatible" option' diff --git a/src/nvim/testdir/test_prompt_buffer.vim b/src/nvim/testdir/test_prompt_buffer.vim new file mode 100644 index 0000000000..6fc5850be3 --- /dev/null +++ b/src/nvim/testdir/test_prompt_buffer.vim @@ -0,0 +1,195 @@ +" Tests for setting 'buftype' to "prompt" + +source check.vim +" Nvim's channel implementation differs from Vim's +" CheckFeature channel + +source shared.vim +source screendump.vim + +func CanTestPromptBuffer() + " We need to use a terminal window to be able to feed keys without leaving + " Insert mode. + " Nvim's terminal implementation differs from Vim's + " CheckFeature terminal + + " TODO: make the tests work on MS-Windows + CheckNotMSWindows +endfunc + +func WriteScript(name) + call writefile([ + \ 'func TextEntered(text)', + \ ' if a:text == "exit"', + \ ' " Reset &modified to allow the buffer to be closed.', + \ ' set nomodified', + \ ' stopinsert', + \ ' close', + \ ' else', + \ ' " Add the output above the current prompt.', + \ ' call append(line("$") - 1, "Command: \"" . a:text . "\"")', + \ ' " Reset &modified to allow the buffer to be closed.', + \ ' set nomodified', + \ ' call timer_start(20, {id -> TimerFunc(a:text)})', + \ ' endif', + \ 'endfunc', + \ '', + \ 'func TimerFunc(text)', + \ ' " Add the output above the current prompt.', + \ ' call append(line("$") - 1, "Result: \"" . a:text . "\"")', + \ ' " Reset &modified to allow the buffer to be closed.', + \ ' set nomodified', + \ 'endfunc', + \ '', + \ 'call setline(1, "other buffer")', + \ 'set nomodified', + \ 'new', + \ 'set buftype=prompt', + \ 'call prompt_setcallback(bufnr(""), function("TextEntered"))', + \ 'eval bufnr("")->prompt_setprompt("cmd: ")', + \ 'startinsert', + \ ], a:name) +endfunc + +func Test_prompt_basic() + throw 'skipped: TODO' + call CanTestPromptBuffer() + let scriptName = 'XpromptscriptBasic' + call WriteScript(scriptName) + + let buf = RunVimInTerminal('-S ' . scriptName, {}) + call WaitForAssert({-> assert_equal('cmd:', term_getline(buf, 1))}) + + call term_sendkeys(buf, "hello\<CR>") + call WaitForAssert({-> assert_equal('cmd: hello', term_getline(buf, 1))}) + call WaitForAssert({-> assert_equal('Command: "hello"', term_getline(buf, 2))}) + call WaitForAssert({-> assert_equal('Result: "hello"', term_getline(buf, 3))}) + + call term_sendkeys(buf, "exit\<CR>") + call WaitForAssert({-> assert_equal('other buffer', term_getline(buf, 1))}) + + call StopVimInTerminal(buf) + call delete(scriptName) +endfunc + +func Test_prompt_editing() + throw 'skipped: TODO' + call CanTestPromptBuffer() + let scriptName = 'XpromptscriptEditing' + call WriteScript(scriptName) + + let buf = RunVimInTerminal('-S ' . scriptName, {}) + call WaitForAssert({-> assert_equal('cmd:', term_getline(buf, 1))}) + + let bs = "\<BS>" + call term_sendkeys(buf, "hello" . bs . bs) + call WaitForAssert({-> assert_equal('cmd: hel', term_getline(buf, 1))}) + + let left = "\<Left>" + call term_sendkeys(buf, left . left . left . bs . '-') + call WaitForAssert({-> assert_equal('cmd: -hel', term_getline(buf, 1))}) + + let end = "\<End>" + call term_sendkeys(buf, end . "x") + call WaitForAssert({-> assert_equal('cmd: -helx', term_getline(buf, 1))}) + + call term_sendkeys(buf, "\<C-U>exit\<CR>") + call WaitForAssert({-> assert_equal('other buffer', term_getline(buf, 1))}) + + call StopVimInTerminal(buf) + call delete(scriptName) +endfunc + +func Test_prompt_garbage_collect() + func MyPromptCallback(x, text) + " NOP + endfunc + func MyPromptInterrupt(x) + " NOP + endfunc + + new + set buftype=prompt + " Nvim doesn't support method call syntax yet. + " eval bufnr('')->prompt_setcallback(function('MyPromptCallback', [{}])) + " eval bufnr('')->prompt_setinterrupt(function('MyPromptInterrupt', [{}])) + eval prompt_setcallback(bufnr(''), function('MyPromptCallback', [{}])) + eval prompt_setinterrupt(bufnr(''), function('MyPromptInterrupt', [{}])) + call test_garbagecollect_now() + " Must not crash + call feedkeys("\<CR>\<C-C>", 'xt') + call assert_true(v:true) + + call assert_fails("call prompt_setcallback(bufnr(), [])", 'E921:') + call assert_equal(0, prompt_setcallback({}, '')) + call assert_fails("call prompt_setinterrupt(bufnr(), [])", 'E921:') + call assert_equal(0, prompt_setinterrupt({}, '')) + + delfunc MyPromptCallback + bwipe! +endfunc + +" Test for editing the prompt buffer +func Test_prompt_buffer_edit() + new + set buftype=prompt + normal! i + call assert_beeps('normal! dd') + call assert_beeps('normal! ~') + call assert_beeps('normal! o') + call assert_beeps('normal! O') + call assert_beeps('normal! p') + call assert_beeps('normal! P') + call assert_beeps('normal! u') + call assert_beeps('normal! ra') + call assert_beeps('normal! s') + call assert_beeps('normal! S') + call assert_beeps("normal! \<C-A>") + call assert_beeps("normal! \<C-X>") + " pressing CTRL-W in the prompt buffer should trigger the window commands + call assert_equal(1, winnr()) + " In Nvim, CTRL-W commands aren't usable from insert mode in a prompt buffer + " exe "normal A\<C-W>\<C-W>" + " call assert_equal(2, winnr()) + " wincmd w + close! + call assert_equal(0, prompt_setprompt([], '')) +endfunc + +func Test_prompt_buffer_getbufinfo() + new + call assert_equal('', prompt_getprompt('%')) + call assert_equal('', prompt_getprompt(bufnr('%'))) + let another_buffer = bufnr('%') + + set buftype=prompt + call assert_equal('% ', prompt_getprompt('%')) + call prompt_setprompt( bufnr( '%' ), 'This is a test: ' ) + call assert_equal('This is a test: ', prompt_getprompt('%')) + + call prompt_setprompt( bufnr( '%' ), '' ) + " Nvim doesn't support method call syntax yet. + " call assert_equal('', '%'->prompt_getprompt()) + call assert_equal('', prompt_getprompt('%')) + + call prompt_setprompt( bufnr( '%' ), 'Another: ' ) + call assert_equal('Another: ', prompt_getprompt('%')) + let another = bufnr('%') + + new + + call assert_equal('', prompt_getprompt('%')) + call assert_equal('Another: ', prompt_getprompt(another)) + + " Doesn't exist + let buffers_before = len( getbufinfo() ) + call assert_equal('', prompt_getprompt( bufnr('$') + 1)) + call assert_equal(buffers_before, len( getbufinfo())) + + " invalid type + call assert_fails('call prompt_getprompt({})', 'E728:') + + %bwipe! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index 7aa01c61ca..75d42b986b 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -7,9 +7,8 @@ source check.vim " See test/functional/legacy/search_spec.lua func Test_search_cmdline() CheckFunction test_override - if !exists('+incsearch') - return - endif + CheckOption incsearch + " need to disable char_avail, " so that expansion of commandline works call test_override("char_avail", 1) @@ -206,9 +205,8 @@ endfunc " See test/functional/legacy/search_spec.lua func Test_search_cmdline2() CheckFunction test_override - if !exists('+incsearch') - return - endif + CheckOption incsearch + " need to disable char_avail, " so that expansion of commandline works call test_override("char_avail", 1) @@ -369,9 +367,8 @@ func Incsearch_cleanup() endfunc func Test_search_cmdline3() - if !exists('+incsearch') - return - endif + CheckOption incsearch + call Cmdline3_prep() 1 " first match @@ -382,9 +379,8 @@ func Test_search_cmdline3() endfunc func Test_search_cmdline3s() - if !exists('+incsearch') - return - endif + CheckOption incsearch + call Cmdline3_prep() 1 call feedkeys(":%s/the\<c-l>/xxx\<cr>", 'tx') @@ -408,9 +404,8 @@ func Test_search_cmdline3s() endfunc func Test_search_cmdline3g() - if !exists('+incsearch') - return - endif + CheckOption incsearch + call Cmdline3_prep() 1 call feedkeys(":g/the\<c-l>/d\<cr>", 'tx') @@ -431,9 +426,8 @@ func Test_search_cmdline3g() endfunc func Test_search_cmdline3v() - if !exists('+incsearch') - return - endif + CheckOption incsearch + call Cmdline3_prep() 1 call feedkeys(":v/the\<c-l>/d\<cr>", 'tx') @@ -450,9 +444,8 @@ endfunc " See test/functional/legacy/search_spec.lua func Test_search_cmdline4() CheckFunction test_override - if !exists('+incsearch') - return - endif + CheckOption incsearch + " need to disable char_avail, " so that expansion of commandline works call test_override("char_avail", 1) @@ -484,9 +477,8 @@ func Test_search_cmdline4() endfunc func Test_search_cmdline5() - if !exists('+incsearch') - return - endif + CheckOption incsearch + " Do not call test_override("char_avail", 1) so that <C-g> and <C-t> work " regardless char_avail. new @@ -503,6 +495,46 @@ func Test_search_cmdline5() bw! endfunc +func Test_search_cmdline6() + " Test that consecutive matches + " are caught by <c-g>/<c-t> + CheckFunction test_override + CheckOption incsearch + + " need to disable char_avail, + " so that expansion of commandline works + call test_override("char_avail", 1) + new + call setline(1, [' bbvimb', '']) + set incsearch + " first match + norm! gg0 + call feedkeys("/b\<cr>", 'tx') + call assert_equal([0,1,2,0], getpos('.')) + " second match + norm! gg0 + call feedkeys("/b\<c-g>\<cr>", 'tx') + call assert_equal([0,1,3,0], getpos('.')) + " third match + norm! gg0 + call feedkeys("/b\<c-g>\<c-g>\<cr>", 'tx') + call assert_equal([0,1,7,0], getpos('.')) + " first match again + norm! gg0 + call feedkeys("/b\<c-g>\<c-g>\<c-g>\<cr>", 'tx') + call assert_equal([0,1,2,0], getpos('.')) + set nowrapscan + " last match + norm! gg0 + call feedkeys("/b\<c-g>\<c-g>\<c-g>\<cr>", 'tx') + call assert_equal([0,1,7,0], getpos('.')) + " clean up + set wrapscan&vim + set noincsearch + call test_override("char_avail", 0) + bw! +endfunc + func Test_search_cmdline7() CheckFunction test_override " Test that pressing <c-g> in an empty command line @@ -598,26 +630,226 @@ func Test_search_regexp() enew! endfunc -" Test for search('multi-byte char', 'bce') -func Test_search_multibyte() - let save_enc = &encoding - set encoding=utf8 - enew! - call append('$', 'A') - call cursor(2, 1) - call assert_equal(2, search('A', 'bce', line('.'))) - enew! - let &encoding = save_enc +func Test_search_cmdline_incsearch_highlight() + CheckFunction test_override + CheckOption incsearch + + set incsearch hlsearch + " need to disable char_avail, + " so that expansion of commandline works + call test_override("char_avail", 1) + new + call setline(1, ['aaa 1 the first', ' 2 the second', ' 3 the third']) + + 1 + call feedkeys("/second\<cr>", 'tx') + call assert_equal('second', @/) + call assert_equal(' 2 the second', getline('.')) + + " Canceling search won't change @/ + 1 + let @/ = 'last pattern' + call feedkeys("/third\<C-c>", 'tx') + call assert_equal('last pattern', @/) + call feedkeys("/third\<Esc>", 'tx') + call assert_equal('last pattern', @/) + call feedkeys("/3\<bs>\<bs>", 'tx') + call assert_equal('last pattern', @/) + call feedkeys("/third\<c-g>\<c-t>\<Esc>", 'tx') + call assert_equal('last pattern', @/) + + " clean up + set noincsearch nohlsearch + bw! endfunc -" Similar to Test_incsearch_substitute() but with a screendump halfway. -func Test_incsearch_substitute_dump() - if !exists('+incsearch') +func Test_search_cmdline_incsearch_highlight_attr() + CheckOption incsearch + CheckFeature terminal + CheckNotGui + + let h = winheight(0) + if h < 3 return endif + + " Prepare buffer text + let lines = ['abb vim vim vi', 'vimvivim'] + call writefile(lines, 'Xsearch.txt') + let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile', 'Xsearch.txt'], {'term_rows': 3}) + + call WaitForAssert({-> assert_equal(lines, [term_getline(buf, 1), term_getline(buf, 2)])}) + " wait for vim to complete initialization + call term_wait(buf) + + " Get attr of normal(a0), incsearch(a1), hlsearch(a2) highlight + call term_sendkeys(buf, ":set incsearch hlsearch\<cr>") + call term_sendkeys(buf, '/b') + call term_wait(buf, 200) + let screen_line1 = term_scrape(buf, 1) + call assert_true(len(screen_line1) > 2) + " a0: attr_normal + let a0 = screen_line1[0].attr + " a1: attr_incsearch + let a1 = screen_line1[1].attr + " a2: attr_hlsearch + let a2 = screen_line1[2].attr + call assert_notequal(a0, a1) + call assert_notequal(a0, a2) + call assert_notequal(a1, a2) + call term_sendkeys(buf, "\<cr>gg0") + + " Test incremental highlight search + call term_sendkeys(buf, "/vim") + call term_wait(buf, 200) + " Buffer: + " abb vim vim vi + " vimvivim + " Search: /vim + let attr_line1 = [a0,a0,a0,a0,a1,a1,a1,a0,a2,a2,a2,a0,a0,a0] + let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2] + call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr')) + call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr')) + + " Test <C-g> + call term_sendkeys(buf, "\<C-g>\<C-g>") + call term_wait(buf, 200) + let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a2,a2,a2,a0,a0,a0] + let attr_line2 = [a1,a1,a1,a0,a0,a2,a2,a2] + call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr')) + call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr')) + + " Test <C-t> + call term_sendkeys(buf, "\<C-t>") + call term_wait(buf, 200) + let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a1,a1,a1,a0,a0,a0] + let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2] + call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr')) + call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr')) + + " Type Enter and a1(incsearch highlight) should become a2(hlsearch highlight) + call term_sendkeys(buf, "\<cr>") + call term_wait(buf, 200) + let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a2,a2,a2,a0,a0,a0] + let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2] + call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr')) + call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr')) + + " Test nohlsearch. a2(hlsearch highlight) should become a0(normal highlight) + call term_sendkeys(buf, ":1\<cr>") + call term_sendkeys(buf, ":set nohlsearch\<cr>") + call term_sendkeys(buf, "/vim") + call term_wait(buf, 200) + let attr_line1 = [a0,a0,a0,a0,a1,a1,a1,a0,a0,a0,a0,a0,a0,a0] + let attr_line2 = [a0,a0,a0,a0,a0,a0,a0,a0] + call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr')) + call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr')) + call delete('Xsearch.txt') + + call delete('Xsearch.txt') + bwipe! +endfunc + +func Test_incsearch_cmdline_modifier() + CheckFunction test_override + CheckOption incsearch + + call test_override("char_avail", 1) + new + call setline(1, ['foo']) + set incsearch + " Test that error E14 does not occur in parsing command modifier. + call feedkeys("V:tab", 'tx') + + call Incsearch_cleanup() +endfunc + +func Test_incsearch_scrolling() if !CanRunVimInTerminal() throw 'Skipped: cannot make screendumps' endif + call assert_equal(0, &scrolloff) + call writefile([ + \ 'let dots = repeat(".", 120)', + \ 'set incsearch cmdheight=2 scrolloff=0', + \ 'call setline(1, [dots, dots, dots, "", "target", dots, dots])', + \ 'normal gg', + \ 'redraw', + \ ], 'Xscript') + let buf = RunVimInTerminal('-S Xscript', {'rows': 9, 'cols': 70}) + " Need to send one key at a time to force a redraw + call term_sendkeys(buf, '/') + sleep 100m + call term_sendkeys(buf, 't') + sleep 100m + call term_sendkeys(buf, 'a') + sleep 100m + call term_sendkeys(buf, 'r') + sleep 100m + call term_sendkeys(buf, 'g') + call VerifyScreenDump(buf, 'Test_incsearch_scrolling_01', {}) + + call term_sendkeys(buf, "\<Esc>") + call StopVimInTerminal(buf) + call delete('Xscript') +endfunc + +func Test_incsearch_search_dump() + CheckOption incsearch + CheckScreendump + + call writefile([ + \ 'set incsearch hlsearch scrolloff=0', + \ 'for n in range(1, 8)', + \ ' call setline(n, "foo " . n)', + \ 'endfor', + \ '3', + \ ], 'Xis_search_script') + let buf = RunVimInTerminal('-S Xis_search_script', {'rows': 9, 'cols': 70}) + " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by + " the 'ambiwidth' check. + sleep 100m + + " Need to send one key at a time to force a redraw. + call term_sendkeys(buf, '/fo') + call VerifyScreenDump(buf, 'Test_incsearch_search_01', {}) + call term_sendkeys(buf, "\<Esc>") + sleep 100m + + call term_sendkeys(buf, '/\v') + call VerifyScreenDump(buf, 'Test_incsearch_search_02', {}) + call term_sendkeys(buf, "\<Esc>") + + call StopVimInTerminal(buf) + call delete('Xis_search_script') +endfunc + +func Test_incsearch_substitute() + CheckFunction test_override + CheckOption incsearch + + call test_override("char_avail", 1) + new + set incsearch + for n in range(1, 10) + call setline(n, 'foo ' . n) + endfor + 4 + call feedkeys(":.,.+2s/foo\<BS>o\<BS>o/xxx\<cr>", 'tx') + call assert_equal('foo 3', getline(3)) + call assert_equal('xxx 4', getline(4)) + call assert_equal('xxx 5', getline(5)) + call assert_equal('xxx 6', getline(6)) + call assert_equal('foo 7', getline(7)) + + call Incsearch_cleanup() +endfunc + +" Similar to Test_incsearch_substitute() but with a screendump halfway. +func Test_incsearch_substitute_dump() + CheckOption incsearch + CheckScreendump + call writefile([ \ 'set incsearch hlsearch scrolloff=0', \ 'for n in range(1, 10)', @@ -724,12 +956,8 @@ func Test_incsearch_substitute_dump() endfunc func Test_incsearch_highlighting() - if !exists('+incsearch') - return - endif - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckOption incsearch + CheckScreendump call writefile([ \ 'set incsearch hlsearch', @@ -745,16 +973,40 @@ func Test_incsearch_highlighting() call term_sendkeys(buf, ":%s;ello/the") call VerifyScreenDump(buf, 'Test_incsearch_substitute_15', {}) call term_sendkeys(buf, "<Esc>") + + call StopVimInTerminal(buf) + call delete('Xis_subst_hl_script') +endfunc + +func Test_incsearch_with_change() + CheckFeature timers + CheckOption incsearch + CheckScreendump + + call writefile([ + \ 'set incsearch hlsearch scrolloff=0', + \ 'call setline(1, ["one", "two ------ X", "three"])', + \ 'call timer_start(200, { _ -> setline(2, "x")})', + \ ], 'Xis_change_script') + let buf = RunVimInTerminal('-S Xis_change_script', {'rows': 9, 'cols': 70}) + " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by + " the 'ambiwidth' check. + sleep 300m + + " Highlight X, it will be deleted by the timer callback. + call term_sendkeys(buf, ':%s/X') + call VerifyScreenDump(buf, 'Test_incsearch_change_01', {}) + call term_sendkeys(buf, "\<Esc>") + + call StopVimInTerminal(buf) + call delete('Xis_change_script') endfunc " Similar to Test_incsearch_substitute_dump() for :sort func Test_incsearch_sort_dump() - if !exists('+incsearch') - return - endif - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckOption incsearch + CheckScreendump + call writefile([ \ 'set incsearch hlsearch scrolloff=0', \ 'call setline(1, ["another one 2", "that one 3", "the one 1"])', @@ -778,12 +1030,9 @@ endfunc " Similar to Test_incsearch_substitute_dump() for :vimgrep famiry func Test_incsearch_vimgrep_dump() - if !exists('+incsearch') - return - endif - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckOption incsearch + CheckScreendump + call writefile([ \ 'set incsearch hlsearch scrolloff=0', \ 'call setline(1, ["another one 2", "that one 3", "the one 1"])', @@ -820,9 +1069,8 @@ endfunc func Test_keep_last_search_pattern() CheckFunction test_override - if !exists('+incsearch') - return - endif + CheckOption incsearch + new call setline(1, ['foo', 'foo', 'foo']) set incsearch @@ -842,9 +1090,8 @@ endfunc func Test_word_under_cursor_after_match() CheckFunction test_override - if !exists('+incsearch') - return - endif + CheckOption incsearch + new call setline(1, 'foo bar') set incsearch @@ -862,9 +1109,8 @@ endfunc func Test_subst_word_under_cursor() CheckFunction test_override - if !exists('+incsearch') - return - endif + CheckOption incsearch + new call setline(1, ['int SomeLongName;', 'for (xxx = 1; xxx < len; ++xxx)']) set incsearch @@ -878,130 +1124,6 @@ func Test_subst_word_under_cursor() set noincsearch endfunc -func Test_incsearch_with_change() - if !has('timers') || !exists('+incsearch') || !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps and/or timers feature and/or incsearch option missing' - endif - - call writefile([ - \ 'set incsearch hlsearch scrolloff=0', - \ 'call setline(1, ["one", "two ------ X", "three"])', - \ 'call timer_start(200, { _ -> setline(2, "x")})', - \ ], 'Xis_change_script') - let buf = RunVimInTerminal('-S Xis_change_script', {'rows': 9, 'cols': 70}) - " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by - " the 'ambiwidth' check. - sleep 300m - - " Highlight X, it will be deleted by the timer callback. - call term_sendkeys(buf, ':%s/X') - call VerifyScreenDump(buf, 'Test_incsearch_change_01', {}) - call term_sendkeys(buf, "\<Esc>") - - call StopVimInTerminal(buf) - call delete('Xis_change_script') -endfunc - -func Test_incsearch_cmdline_modifier() - CheckFunction test_override - if !exists('+incsearch') - return - endif - call test_override("char_avail", 1) - new - call setline(1, ['foo']) - set incsearch - " Test that error E14 does not occur in parsing command modifier. - call feedkeys("V:tab", 'tx') - - call Incsearch_cleanup() -endfunc - -func Test_incsearch_scrolling() - if !CanRunVimInTerminal() - return - endif - call assert_equal(0, &scrolloff) - call writefile([ - \ 'let dots = repeat(".", 120)', - \ 'set incsearch cmdheight=2 scrolloff=0', - \ 'call setline(1, [dots, dots, dots, "", "target", dots, dots])', - \ 'normal gg', - \ 'redraw', - \ ], 'Xscript') - let buf = RunVimInTerminal('-S Xscript', {'rows': 9, 'cols': 70}) - " Need to send one key at a time to force a redraw - call term_sendkeys(buf, '/') - sleep 100m - call term_sendkeys(buf, 't') - sleep 100m - call term_sendkeys(buf, 'a') - sleep 100m - call term_sendkeys(buf, 'r') - sleep 100m - call term_sendkeys(buf, 'g') - call VerifyScreenDump(buf, 'Test_incsearch_scrolling_01', {}) - - call term_sendkeys(buf, "\<Esc>") - call StopVimInTerminal(buf) - call delete('Xscript') -endfunc - -func Test_incsearch_search_dump() - if !exists('+incsearch') - return - endif - if !CanRunVimInTerminal() - return - endif - call writefile([ - \ 'set incsearch hlsearch scrolloff=0', - \ 'for n in range(1, 8)', - \ ' call setline(n, "foo " . n)', - \ 'endfor', - \ '3', - \ ], 'Xis_search_script') - let buf = RunVimInTerminal('-S Xis_search_script', {'rows': 9, 'cols': 70}) - " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by - " the 'ambiwidth' check. - sleep 100m - - " Need to send one key at a time to force a redraw. - call term_sendkeys(buf, '/fo') - call VerifyScreenDump(buf, 'Test_incsearch_search_01', {}) - call term_sendkeys(buf, "\<Esc>") - sleep 100m - - call term_sendkeys(buf, '/\v') - call VerifyScreenDump(buf, 'Test_incsearch_search_02', {}) - call term_sendkeys(buf, "\<Esc>") - - call StopVimInTerminal(buf) - call delete('Xis_search_script') -endfunc - -func Test_incsearch_substitute() - CheckFunction test_override - if !exists('+incsearch') - return - endif - call test_override("char_avail", 1) - new - set incsearch - for n in range(1, 10) - call setline(n, 'foo ' . n) - endfor - 4 - call feedkeys(":.,.+2s/foo\<BS>o\<BS>o/xxx\<cr>", 'tx') - call assert_equal('foo 3', getline(3)) - call assert_equal('xxx 4', getline(4)) - call assert_equal('xxx 5', getline(5)) - call assert_equal('xxx 6', getline(6)) - call assert_equal('foo 7', getline(7)) - - call Incsearch_cleanup() -endfunc - func Test_incsearch_substitute_long_line() CheckFunction test_override new @@ -1018,9 +1140,8 @@ func Test_incsearch_substitute_long_line() endfunc func Test_search_undefined_behaviour() - if !has("terminal") - return - endif + CheckFeature terminal + let h = winheight(0) if h < 3 return @@ -1036,6 +1157,18 @@ func Test_search_undefined_behaviour2() call search("\%UC0000000") endfunc +" Test for search('multi-byte char', 'bce') +func Test_search_multibyte() + let save_enc = &encoding + set encoding=utf8 + enew! + call append('$', 'A') + call cursor(2, 1) + call assert_equal(2, search('A', 'bce', line('.'))) + enew! + let &encoding = save_enc +endfunc + " This was causing E874. Also causes an invalid read? func Test_look_behind() new @@ -1074,9 +1207,8 @@ func Test_search_Ctrl_L_combining() " ' ̇' U+0307 Dec:775 COMBINING DOT ABOVE ̇ /\%u307\Z "\u0307" " ' ̣' U+0323 Dec:803 COMBINING DOT BELOW ̣ /\%u323 "\u0323" " Those should also appear on the commandline - if !exists('+incsearch') - return - endif + CheckOption incsearch + call Cmdline3_prep() 1 let bufcontent = ['', 'Miạ̀́̇m'] @@ -1126,9 +1258,8 @@ endfunc func Test_incsearch_add_char_under_cursor() CheckFunction test_override - if !exists('+incsearch') - return - endif + CheckOption incsearch + set incsearch new call setline(1, ['find match', 'anything']) @@ -1213,7 +1344,7 @@ func Test_search_smartcase_utf8() close! endfunc -func Test_zzzz_incsearch_highlighting_newline() +func Test_incsearch_highlighting_newline() CheckRunVimInTerminal CheckOption incsearch CheckScreendump @@ -1226,20 +1357,16 @@ func Test_zzzz_incsearch_highlighting_newline() [CODE] call writefile(commands, 'Xincsearch_nl') let buf = RunVimInTerminal('-S Xincsearch_nl', {'rows': 5, 'cols': 10}) - " Need to send one key at a time to force a redraw call term_sendkeys(buf, '/test') - sleep 100m call VerifyScreenDump(buf, 'Test_incsearch_newline1', {}) + " Need to send one key at a time to force a redraw call term_sendkeys(buf, '\n') - sleep 100m call VerifyScreenDump(buf, 'Test_incsearch_newline2', {}) call term_sendkeys(buf, 'x') - sleep 100m call VerifyScreenDump(buf, 'Test_incsearch_newline3', {}) call term_sendkeys(buf, 'x') call VerifyScreenDump(buf, 'Test_incsearch_newline4', {}) call term_sendkeys(buf, "\<CR>") - sleep 100m call VerifyScreenDump(buf, 'Test_incsearch_newline5', {}) call StopVimInTerminal(buf) diff --git a/src/nvim/testdir/test_shift.vim b/src/nvim/testdir/test_shift.vim new file mode 100644 index 0000000000..ec357dac88 --- /dev/null +++ b/src/nvim/testdir/test_shift.vim @@ -0,0 +1,117 @@ +" Test shifting lines with :> and :< + +source check.vim + +func Test_ex_shift_right() + set shiftwidth=2 + + " shift right current line. + call setline(1, range(1, 5)) + 2 + > + 3 + >> + call assert_equal(['1', + \ ' 2', + \ ' 3', + \ '4', + \ '5'], getline(1, '$')) + + " shift right with range. + call setline(1, range(1, 4)) + 2,3>> + call assert_equal(['1', + \ ' 2', + \ ' 3', + \ '4', + \ '5'], getline(1, '$')) + + " shift right with range and count. + call setline(1, range(1, 4)) + 2>3 + call assert_equal(['1', + \ ' 2', + \ ' 3', + \ ' 4', + \ '5'], getline(1, '$')) + + bw! + set shiftwidth& +endfunc + +func Test_ex_shift_left() + set shiftwidth=2 + + call setline(1, range(1, 5)) + %>>> + + " left shift current line. + 2< + 3<< + 4<<<<< + call assert_equal([' 1', + \ ' 2', + \ ' 3', + \ '4', + \ ' 5'], getline(1, '$')) + + " shift right with range. + call setline(1, range(1, 5)) + %>>> + 2,3<< + call assert_equal([' 1', + \ ' 2', + \ ' 3', + \ ' 4', + \ ' 5'], getline(1, '$')) + + " shift right with range and count. + call setline(1, range(1, 5)) + %>>> + 2<<3 + call assert_equal([' 1', + \ ' 2', + \ ' 3', + \ ' 4', + \ ' 5'], getline(1, '$')) + + bw! + set shiftwidth& +endfunc + +func Test_ex_shift_rightleft() + CheckFeature rightleft + + set shiftwidth=2 rightleft + + call setline(1, range(1, 4)) + 2,3<< + call assert_equal(['1', + \ ' 2', + \ ' 3', + \ '4'], getline(1, '$')) + + 3,4> + call assert_equal(['1', + \ ' 2', + \ ' 3', + \ '4'], getline(1, '$')) + + bw! + set rightleft& shiftwidth& +endfunc + +func Test_ex_shift_errors() + call assert_fails('><', 'E488:') + call assert_fails('<>', 'E488:') + + call assert_fails('>!', 'E477:') + call assert_fails('<!', 'E477:') + + " call assert_fails('2,1>', 'E493:') + call assert_fails('execute "2,1>"', 'E493:') + " call assert_fails('2,1<', 'E493:') + call assert_fails('execute "2,1<"', 'E493:') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim index f6b96c1e5d..9753100375 100644 --- a/src/nvim/testdir/test_signs.vim +++ b/src/nvim/testdir/test_signs.vim @@ -1628,26 +1628,7 @@ func Test_sign_lnum_adjust() " Delete the line with the sign call deletebufline('', 4) let l = sign_getplaced(bufnr('')) - call assert_equal(4, l[0].signs[0].lnum) - - " Undo the delete operation - undo - let l = sign_getplaced(bufnr('')) - call assert_equal(5, l[0].signs[0].lnum) - - " Break the undo - let &undolevels=&undolevels - - " Delete few lines at the end of the buffer including the line with the sign - " Sign line number should not change (as it is placed outside of the buffer) - call deletebufline('', 3, 6) - let l = sign_getplaced(bufnr('')) - call assert_equal(5, l[0].signs[0].lnum) - - " Undo the delete operation. Sign should be restored to the previous line - undo - let l = sign_getplaced(bufnr('')) - call assert_equal(5, l[0].signs[0].lnum) + call assert_equal(0, len(l[0].signs)) sign unplace * group=* sign undefine sign1 diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 66cb0bbe22..875e23894f 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -24,6 +24,32 @@ func GetSyntaxItem(pat) return c endfunc +func AssertHighlightGroups(lnum, startcol, expected, trans = 1, msg = "") + " Assert that the characters starting at a given (line, col) + " sequentially match the expected highlight groups. + " If groups are provided as a string, each character is assumed to be a + " group and spaces represent no group, useful for visually describing tests. + let l:expectedGroups = type(a:expected) == v:t_string + "\ ? a:expected->split('\zs')->map({_, v -> trim(v)}) + \ ? map(split(a:expected, '\zs'), {_, v -> trim(v)}) + \ : a:expected + let l:errors = 0 + " let l:msg = (a:msg->empty() ? "" : a:msg .. ": ") + let l:msg = (empty(a:msg) ? "" : a:msg .. ": ") + \ .. "Wrong highlight group at " .. a:lnum .. "," + + " for l:i in range(a:startcol, a:startcol + l:expectedGroups->len() - 1) + " let l:errors += synID(a:lnum, l:i, a:trans) + " \ ->synIDattr("name") + " \ ->assert_equal(l:expectedGroups[l:i - 1], + for l:i in range(a:startcol, a:startcol + len(l:expectedGroups) - 1) + let l:errors += + \ assert_equal(synIDattr(synID(a:lnum, l:i, a:trans), "name"), + \ l:expectedGroups[l:i - 1], + \ l:msg .. l:i) + endfor +endfunc + func Test_syn_iskeyword() new call setline(1, [ @@ -707,3 +733,22 @@ func Test_syntax_foldlevel() quit! endfunc + +func Test_syn_include_contains_TOP() + let l:case = "TOP in included syntax means its group list name" + new + syntax include @INCLUDED syntax/c.vim + syntax region FencedCodeBlockC start=/```c/ end=/```/ contains=@INCLUDED + + call setline(1, ['```c', '#if 0', 'int', '#else', 'int', '#endif', '```' ]) + let l:expected = ["cCppOutIf2"] + eval AssertHighlightGroups(3, 1, l:expected, 1) + " cCppOutElse has contains=TOP + let l:expected = ["cType"] + eval AssertHighlightGroups(5, 1, l:expected, 1, l:case) + syntax clear + bw! +endfunc + + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index a2e9266fbb..c1e4a40ef2 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -180,7 +180,8 @@ bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width, if (kv_A(layers, insert_at-1) == &pum_grid && (grid != &msg_grid)) { insert_at--; } - if (insert_at > 1 && !on_top) { + if (curwin && kv_A(layers, insert_at-1) == &curwin->w_grid_alloc + && !on_top) { insert_at--; } // not found: new grid diff --git a/src/nvim/window.c b/src/nvim/window.c index c482d265ff..d1163399f5 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -772,9 +772,8 @@ void ui_ext_win_position(win_T *wp) wp->w_winrow = comp_row; wp->w_wincol = comp_col; bool valid = (wp->w_redr_type == 0); - bool on_top = (curwin == wp) || !curwin->w_floating; ui_comp_put_grid(&wp->w_grid_alloc, comp_row, comp_col, - wp->w_height_outer, wp->w_width_outer, valid, on_top); + wp->w_height_outer, wp->w_width_outer, valid, false); ui_check_cursor_grid(wp->w_grid_alloc.handle); wp->w_grid_alloc.focusable = wp->w_float_config.focusable; if (!valid) { |