diff --git a/runtime/doc/vimeval.txt b/runtime/doc/vimeval.txt index 9a8faf756b..8fc5434180 100644 --- a/runtime/doc/vimeval.txt +++ b/runtime/doc/vimeval.txt @@ -2116,7 +2116,6 @@ text... # Number * Funcref - :unl[et][!] {name} ... *:unlet* *:unl* *E108* *E795* Remove the internal variable {name}. Several variable names can be given, they are all removed. The name diff --git a/runtime/doc/vvars.txt b/runtime/doc/vvars.txt index ec7dfe4d4b..0248fb1f69 100644 --- a/runtime/doc/vvars.txt +++ b/runtime/doc/vvars.txt @@ -727,6 +727,19 @@ v:version :if has("nvim-0.2.1") < + *v:versionlong* *versionlong-variable* +v:versionlong + Like v:version, but also including the patchlevel in the last + four digits. Version 8.1 with patch 123 has value 8010123. + This can be used like this: > + if v:versionlong >= 8010123 +< + However, if there are gaps in the list of patches included + this will not work well. This can happen if a recent patch + was included into an older version, e.g. for a security fix. + Use the has() function to make sure the patch is actually + included. + *v:vim_did_enter* *vim_did_enter-variable* v:vim_did_enter 0 during startup, 1 just before |VimEnter|. diff --git a/runtime/lua/vim/_meta/vvars.lua b/runtime/lua/vim/_meta/vvars.lua index 25e504a612..a5c6c6b984 100644 --- a/runtime/lua/vim/_meta/vvars.lua +++ b/runtime/lua/vim/_meta/vvars.lua @@ -772,6 +772,21 @@ vim.v.val = ... --- @type integer vim.v.version = ... +--- Like v:version, but also including the patchlevel in the last +--- four digits. Version 8.1 with patch 123 has value 8010123. +--- This can be used like this: +--- ``` +--- if v:versionlong >= 8010123 +--- ``` +--- +--- However, if there are gaps in the list of patches included +--- this will not work well. This can happen if a recent patch +--- was included into an older version, e.g. for a security fix. +--- Use the has() function to make sure the patch is actually +--- included. +--- @type integer +vim.v.versionlong = ... + --- 0 during startup, 1 just before `VimEnter`. --- Read-only. --- @type integer diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 2460119123..83f81309e0 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -1566,7 +1566,7 @@ bool has_event(event_T event) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT /// Return true when there is a CursorHold/CursorHoldI autocommand defined for /// the current mode. -bool has_cursorhold(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +static bool has_cursorhold(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return has_event((get_real_state() == MODE_NORMAL_BUSY ? EVENT_CURSORHOLD : EVENT_CURSORHOLDI)); } diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index c2e26e5f3d..3f88064ca8 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1683,7 +1683,7 @@ void set_curbuf(buf_T *buf, int action, bool update_jumplist) /// Enter a new current buffer. /// Old curbuf must have been abandoned already! This also means "curbuf" may /// be pointing to freed memory. -void enter_buffer(buf_T *buf) +static void enter_buffer(buf_T *buf) { // when closing the current buffer stop Visual mode if (VIsual_active @@ -2207,7 +2207,7 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit) } /// Go to the last known line number for the current buffer. -void buflist_getfpos(void) +static void buflist_getfpos(void) { pos_T *fpos = &buflist_findfmark(curbuf)->mark; diff --git a/src/nvim/change.c b/src/nvim/change.c index d70e0d4663..3e7b6defb8 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -957,118 +957,6 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) return OK; } -/// Copy the indent from ptr to the current line (and fill to size). -/// Leaves the cursor on the first non-blank in the line. -/// -/// @return true if the line was changed. -bool copy_indent(int size, char *src) -{ - char *p = NULL; - char *line = NULL; - int ind_len; - int line_len = 0; - int tab_pad; - - // Round 1: compute the number of characters needed for the indent - // Round 2: copy the characters. - for (int round = 1; round <= 2; round++) { - int todo = size; - ind_len = 0; - int ind_done = 0; - int ind_col = 0; - char *s = src; - - // Count/copy the usable portion of the source line. - while (todo > 0 && ascii_iswhite(*s)) { - if (*s == TAB) { - tab_pad = tabstop_padding(ind_done, - curbuf->b_p_ts, - curbuf->b_p_vts_array); - - // Stop if this tab will overshoot the target. - if (todo < tab_pad) { - break; - } - todo -= tab_pad; - ind_done += tab_pad; - ind_col += tab_pad; - } else { - todo--; - ind_done++; - ind_col++; - } - ind_len++; - - if (p != NULL) { - *p++ = *s; - } - s++; - } - - // Fill to next tabstop with a tab, if possible. - tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts, curbuf->b_p_vts_array); - - if ((todo >= tab_pad) && !curbuf->b_p_et) { - todo -= tab_pad; - ind_len++; - ind_col += tab_pad; - - if (p != NULL) { - *p++ = TAB; - } - } - - // Add tabs required for indent. - if (!curbuf->b_p_et) { - while (true) { - tab_pad = tabstop_padding(ind_col, - curbuf->b_p_ts, - curbuf->b_p_vts_array); - if (todo < tab_pad) { - break; - } - todo -= tab_pad; - ind_len++; - ind_col += tab_pad; - if (p != NULL) { - *p++ = TAB; - } - } - } - - // Count/add spaces required for indent. - while (todo > 0) { - todo--; - ind_len++; - - if (p != NULL) { - *p++ = ' '; - } - } - - if (p == NULL) { - // Allocate memory for the result: the copied indent, new indent - // and the rest of the line. - line_len = get_cursor_line_len() + 1; - assert(ind_len + line_len >= 0); - size_t line_size; - STRICT_ADD(ind_len, line_len, &line_size, size_t); - line = xmalloc(line_size); - p = line; - } - } - - // Append the original line - memmove(p, get_cursor_line_ptr(), (size_t)line_len); - - // Replace the line - ml_replace(curwin->w_cursor.lnum, line, false); - - // Put the cursor after the indent. - curwin->w_cursor.col = ind_len; - return true; -} - /// open_line: Add a new line below or above the current line. /// /// For MODE_VREPLACE state, we only add a new line when we get to the end of diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 899add944e..6a3d5394e2 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -17,6 +17,7 @@ #include "nvim/errors.h" #include "nvim/eval.h" #include "nvim/eval/encode.h" +#include "nvim/eval/funcs.h" #include "nvim/eval/typval.h" #include "nvim/event/loop.h" #include "nvim/event/multiqueue.h" @@ -24,6 +25,7 @@ #include "nvim/event/rstream.h" #include "nvim/event/socket.h" #include "nvim/event/wstream.h" +#include "nvim/ex_cmds.h" #include "nvim/garray.h" #include "nvim/gettext_defs.h" #include "nvim/globals.h" @@ -1001,3 +1003,65 @@ Array channel_all_info(Arena *arena) } return ret; } + +/// "prompt_setcallback({buffer}, {callback})" function +void f_prompt_setcallback(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + Callback prompt_callback = { .type = kCallbackNone }; + + if (check_secure()) { + return; + } + buf_T *buf = tv_get_buf(&argvars[0], false); + if (buf == NULL) { + return; + } + + if (argvars[1].v_type != VAR_STRING || *argvars[1].vval.v_string != NUL) { + if (!callback_from_typval(&prompt_callback, &argvars[1])) { + return; + } + } + + callback_free(&buf->b_prompt_callback); + buf->b_prompt_callback = prompt_callback; +} + +/// "prompt_setinterrupt({buffer}, {callback})" function +void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + Callback interrupt_callback = { .type = kCallbackNone }; + + if (check_secure()) { + return; + } + buf_T *buf = tv_get_buf(&argvars[0], false); + if (buf == NULL) { + return; + } + + if (argvars[1].v_type != VAR_STRING || *argvars[1].vval.v_string != NUL) { + if (!callback_from_typval(&interrupt_callback, &argvars[1])) { + return; + } + } + + callback_free(&buf->b_prompt_interrupt); + buf->b_prompt_interrupt = interrupt_callback; +} + +/// "prompt_setprompt({buffer}, {text})" function +void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + if (check_secure()) { + return; + } + buf_T *buf = tv_get_buf(&argvars[0], false); + if (buf == NULL) { + return; + } + + const char *text = tv_get_string(&argvars[1]); + xfree(buf->b_prompt_text); + buf->b_prompt_text = xstrdup(text); +} diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index f32123e686..143335b5b0 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -1791,7 +1791,7 @@ void digraph_getlist_common(bool list_all, typval_T *rettv) } } -struct dg_header_entry { +static struct dg_header_entry { int dg_start; const char *dg_header; } header_table[] = { diff --git a/src/nvim/edit.c b/src/nvim/edit.c index b1d32156c9..781b6f0c23 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -1636,204 +1636,6 @@ void undisplay_dollar(void) redrawWinline(curwin, curwin->w_cursor.lnum); } -/// Insert an indent (for or CTRL-T) or delete an indent (for CTRL-D). -/// Keep the cursor on the same character. -/// type == INDENT_INC increase indent (for CTRL-T or ) -/// type == INDENT_DEC decrease indent (for CTRL-D) -/// type == INDENT_SET set indent to "amount" -/// -/// @param round if true, round the indent to 'shiftwidth' (only with _INC and _Dec). -/// @param call_changed_bytes call changed_bytes() -void change_indent(int type, int amount, int round, bool call_changed_bytes) -{ - int insstart_less; // reduction for Insstart.col - colnr_T orig_col = 0; // init for GCC - char *orig_line = NULL; // init for GCC - - // MODE_VREPLACE state needs to know what the line was like before changing - if (State & VREPLACE_FLAG) { - orig_line = xstrnsave(get_cursor_line_ptr(), (size_t)get_cursor_line_len()); - orig_col = curwin->w_cursor.col; - } - - // for the following tricks we don't want list mode - int save_p_list = curwin->w_p_list; - curwin->w_p_list = false; - colnr_T vc = getvcol_nolist(&curwin->w_cursor); - int vcol = vc; - - // For Replace mode we need to fix the replace stack later, which is only - // possible when the cursor is in the indent. Remember the number of - // characters before the cursor if it's possible. - int start_col = curwin->w_cursor.col; - - // determine offset from first non-blank - int new_cursor_col = curwin->w_cursor.col; - beginline(BL_WHITE); - new_cursor_col -= curwin->w_cursor.col; - - insstart_less = curwin->w_cursor.col; - - // If the cursor is in the indent, compute how many screen columns the - // cursor is to the left of the first non-blank. - if (new_cursor_col < 0) { - vcol = get_indent() - vcol; - } - - if (new_cursor_col > 0) { // can't fix replace stack - start_col = -1; - } - - // Set the new indent. The cursor will be put on the first non-blank. - if (type == INDENT_SET) { - set_indent(amount, call_changed_bytes ? SIN_CHANGED : 0); - } else { - int save_State = State; - - // Avoid being called recursively. - if (State & VREPLACE_FLAG) { - State = MODE_INSERT; - } - shift_line(type == INDENT_DEC, round, 1, call_changed_bytes); - State = save_State; - } - insstart_less -= curwin->w_cursor.col; - - // Try to put cursor on same character. - // If the cursor is at or after the first non-blank in the line, - // compute the cursor column relative to the column of the first - // non-blank character. - // If we are not in insert mode, leave the cursor on the first non-blank. - // If the cursor is before the first non-blank, position it relative - // to the first non-blank, counted in screen columns. - if (new_cursor_col >= 0) { - // When changing the indent while the cursor is touching it, reset - // Insstart_col to 0. - if (new_cursor_col == 0) { - insstart_less = MAXCOL; - } - new_cursor_col += curwin->w_cursor.col; - } else if (!(State & MODE_INSERT)) { - new_cursor_col = curwin->w_cursor.col; - } else { - // Compute the screen column where the cursor should be. - vcol = get_indent() - vcol; - int const end_vcol = (colnr_T)((vcol < 0) ? 0 : vcol); - curwin->w_virtcol = end_vcol; - - // Advance the cursor until we reach the right screen column. - new_cursor_col = 0; - char *const line = get_cursor_line_ptr(); - vcol = 0; - if (*line != NUL) { - CharsizeArg csarg; - CSType cstype = init_charsize_arg(&csarg, curwin, 0, line); - StrCharInfo ci = utf_ptr2StrCharInfo(line); - while (true) { - int next_vcol = vcol + win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &csarg).width; - if (next_vcol > end_vcol) { - break; - } - vcol = next_vcol; - ci = utfc_next(ci); - if (*ci.ptr == NUL) { - break; - } - } - new_cursor_col = (int)(ci.ptr - line); - } - - // May need to insert spaces to be able to position the cursor on - // the right screen column. - if (vcol != (int)curwin->w_virtcol) { - curwin->w_cursor.col = (colnr_T)new_cursor_col; - const size_t ptrlen = (size_t)(curwin->w_virtcol - vcol); - char *ptr = xmallocz(ptrlen); - memset(ptr, ' ', ptrlen); - new_cursor_col += (int)ptrlen; - ins_str(ptr, ptrlen); - xfree(ptr); - } - - // When changing the indent while the cursor is in it, reset - // Insstart_col to 0. - insstart_less = MAXCOL; - } - - curwin->w_p_list = save_p_list; - curwin->w_cursor.col = MAX(0, (colnr_T)new_cursor_col); - curwin->w_set_curswant = true; - changed_cline_bef_curs(curwin); - - // May have to adjust the start of the insert. - if (State & MODE_INSERT) { - if (curwin->w_cursor.lnum == Insstart.lnum && Insstart.col != 0) { - if ((int)Insstart.col <= insstart_less) { - Insstart.col = 0; - } else { - Insstart.col -= insstart_less; - } - } - if ((int)ai_col <= insstart_less) { - ai_col = 0; - } else { - ai_col -= insstart_less; - } - } - - // For MODE_REPLACE state, may have to fix the replace stack, if it's - // possible. If the number of characters before the cursor decreased, need - // to pop a few characters from the replace stack. - // If the number of characters before the cursor increased, need to push a - // few NULs onto the replace stack. - if (REPLACE_NORMAL(State) && start_col >= 0) { - while (start_col > (int)curwin->w_cursor.col) { - replace_join(0); // remove a NUL from the replace stack - start_col--; - } - while (start_col < (int)curwin->w_cursor.col) { - replace_push_nul(); - start_col++; - } - } - - // For MODE_VREPLACE state, we also have to fix the replace stack. In this - // case it is always possible because we backspace over the whole line and - // then put it back again the way we wanted it. - if (State & VREPLACE_FLAG) { - // Save new line - char *new_line = xstrnsave(get_cursor_line_ptr(), (size_t)get_cursor_line_len()); - - // We only put back the new line up to the cursor - new_line[curwin->w_cursor.col] = NUL; - int new_col = curwin->w_cursor.col; - - // Put back original line - ml_replace(curwin->w_cursor.lnum, orig_line, false); - curwin->w_cursor.col = orig_col; - - curbuf_splice_pending++; - - // Backspace from cursor to start of line - backspace_until_column(0); - - // Insert new stuff into line again - ins_bytes(new_line); - - xfree(new_line); - - curbuf_splice_pending--; - - // TODO(bfredl): test for crazy edge cases, like we stand on a TAB or - // something? does this even do the right text change then? - int delta = orig_col - new_col; - extmark_splice_cols(curbuf, (int)curwin->w_cursor.lnum - 1, new_col, - delta < 0 ? -delta : 0, - delta > 0 ? delta : 0, - kExtmarkUndo); - } -} - /// Truncate the space at the end of a line. This is to be used only in an /// insert mode. It handles fixing the replace stack for MODE_REPLACE and /// MODE_VREPLACE modes. @@ -2885,7 +2687,7 @@ static int replace_pop_if_nul(void) /// encountered. /// /// @param off offset for which NUL to remove -static void replace_join(int off) +void replace_join(int off) { for (ssize_t i = (ssize_t)kv_size(replace_stack); --i >= 0;) { if (kv_A(replace_stack, i) == NUL && off-- <= 0) { @@ -2978,233 +2780,6 @@ static void replace_do_bs(int limit_col) } } -/// Check that C-indenting is on. -bool cindent_on(void) - FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT -{ - return !p_paste && (curbuf->b_p_cin || *curbuf->b_p_inde != NUL); -} - -/// Check that "cinkeys" contains the key "keytyped", -/// when == '*': Only if key is preceded with '*' (indent before insert) -/// when == '!': Only if key is preceded with '!' (don't insert) -/// when == ' ': Only if key is not preceded with '*' or '!' (indent afterwards) -/// -/// "keytyped" can have a few special values: -/// KEY_OPEN_FORW : -/// KEY_OPEN_BACK : -/// KEY_COMPLETE : Just finished completion. -/// -/// @param keytyped key that was typed -/// @param when condition on when to perform the check -/// @param line_is_empty when true, accept keys with '0' before them. -bool in_cinkeys(int keytyped, int when, bool line_is_empty) -{ - char *look; - bool try_match; - bool try_match_word; - char *p; - bool icase; - - if (keytyped == NUL) { - // Can happen with CTRL-Y and CTRL-E on a short line. - return false; - } - - if (*curbuf->b_p_inde != NUL) { - look = curbuf->b_p_indk; // 'indentexpr' set: use 'indentkeys' - } else { - look = curbuf->b_p_cink; // 'indentexpr' empty: use 'cinkeys' - } - while (*look) { - // Find out if we want to try a match with this key, depending on - // 'when' and a '*' or '!' before the key. - switch (when) { - case '*': - try_match = (*look == '*'); break; - case '!': - try_match = (*look == '!'); break; - default: - try_match = (*look != '*') && (*look != '!'); break; - } - if (*look == '*' || *look == '!') { - look++; - } - - // If there is a '0', only accept a match if the line is empty. - // But may still match when typing last char of a word. - if (*look == '0') { - try_match_word = try_match; - if (!line_is_empty) { - try_match = false; - } - look++; - } else { - try_match_word = false; - } - - // Does it look like a control character? - if (*look == '^' && look[1] >= '?' && look[1] <= '_') { - if (try_match && keytyped == CTRL_CHR(look[1])) { - return true; - } - look += 2; - - // 'o' means "o" command, open forward. - // 'O' means "O" command, open backward. - } else if (*look == 'o') { - if (try_match && keytyped == KEY_OPEN_FORW) { - return true; - } - look++; - } else if (*look == 'O') { - if (try_match && keytyped == KEY_OPEN_BACK) { - return true; - } - look++; - - // 'e' means to check for "else" at start of line and just before the - // cursor. - } else if (*look == 'e') { - if (try_match && keytyped == 'e' && curwin->w_cursor.col >= 4) { - p = get_cursor_line_ptr(); - if (skipwhite(p) == p + curwin->w_cursor.col - 4 - && strncmp(p + curwin->w_cursor.col - 4, "else", 4) == 0) { - return true; - } - } - look++; - - // ':' only causes an indent if it is at the end of a label or case - // statement, or when it was before typing the ':' (to fix - // class::method for C++). - } else if (*look == ':') { - if (try_match && keytyped == ':') { - p = get_cursor_line_ptr(); - if (cin_iscase(p, false) || cin_isscopedecl(p) || cin_islabel()) { - return true; - } - // Need to get the line again after cin_islabel(). - p = get_cursor_line_ptr(); - if (curwin->w_cursor.col > 2 - && p[curwin->w_cursor.col - 1] == ':' - && p[curwin->w_cursor.col - 2] == ':') { - p[curwin->w_cursor.col - 1] = ' '; - const bool i = cin_iscase(p, false) - || cin_isscopedecl(p) - || cin_islabel(); - p = get_cursor_line_ptr(); - p[curwin->w_cursor.col - 1] = ':'; - if (i) { - return true; - } - } - } - look++; - - // Is it a key in <>, maybe? - } else if (*look == '<') { - if (try_match) { - // make up some named keys , , , <0>, <>>, <<>, <*>, - // <:> and so that people can re-indent on o, O, e, 0, <, - // >, *, : and ! keys if they really really want to. - if (vim_strchr("<>!*oOe0:", (uint8_t)look[1]) != NULL - && keytyped == look[1]) { - return true; - } - - if (keytyped == get_special_key_code(look + 1)) { - return true; - } - } - while (*look && *look != '>') { - look++; - } - while (*look == '>') { - look++; - } - // Is it a word: "=word"? - } else if (*look == '=' && look[1] != ',' && look[1] != NUL) { - look++; - if (*look == '~') { - icase = true; - look++; - } else { - icase = false; - } - p = vim_strchr(look, ','); - if (p == NULL) { - p = look + strlen(look); - } - if ((try_match || try_match_word) - && curwin->w_cursor.col >= (colnr_T)(p - look)) { - bool match = false; - - if (keytyped == KEY_COMPLETE) { - char *n, *s; - - // Just completed a word, check if it starts with "look". - // search back for the start of a word. - char *line = get_cursor_line_ptr(); - for (s = line + curwin->w_cursor.col; s > line; s = n) { - n = mb_prevptr(line, s); - if (!vim_iswordp(n)) { - break; - } - } - assert(p >= look && (uintmax_t)(p - look) <= SIZE_MAX); - if (s + (p - look) <= line + curwin->w_cursor.col - && (icase - ? mb_strnicmp(s, look, (size_t)(p - look)) - : strncmp(s, look, (size_t)(p - look))) == 0) { - match = true; - } - } else { - // TODO(@brammool): multi-byte - if (keytyped == (int)(uint8_t)p[-1] - || (icase && keytyped < 256 && keytyped >= 0 - && TOLOWER_LOC(keytyped) == TOLOWER_LOC((uint8_t)p[-1]))) { - char *line = get_cursor_pos_ptr(); - assert(p >= look && (uintmax_t)(p - look) <= SIZE_MAX); - if ((curwin->w_cursor.col == (colnr_T)(p - look) - || !vim_iswordc((uint8_t)line[-(p - look) - 1])) - && (icase - ? mb_strnicmp(line - (p - look), look, (size_t)(p - look)) - : strncmp(line - (p - look), look, (size_t)(p - look))) == 0) { - match = true; - } - } - } - if (match && try_match_word && !try_match) { - // "0=word": Check if there are only blanks before the - // word. - if (getwhitecols_curline() != - (int)(curwin->w_cursor.col - (p - look))) { - match = false; - } - } - if (match) { - return true; - } - } - look = p; - - // Ok, it's a boring generic character. - } else { - if (try_match && (uint8_t)(*look) == keytyped) { - return true; - } - if (*look != NUL) { - look++; - } - } - - // Skip over ", ". - look = skip_to_option_part(look); - } - return false; -} - static void ins_reg(void) { bool need_redraw = false; @@ -4630,80 +4205,6 @@ static int ins_ctrl_ey(int tc) return c; } -// Try to do some very smart auto-indenting. -// Used when inserting a "normal" character. -static void ins_try_si(int c) -{ - pos_T *pos; - - // do some very smart indenting when entering '{' or '}' - if (((did_si || can_si_back) && c == '{') || (can_si && c == '}' && inindent(0))) { - pos_T old_pos; - char *ptr; - int i; - bool temp; - // for '}' set indent equal to indent of line containing matching '{' - if (c == '}' && (pos = findmatch(NULL, '{')) != NULL) { - old_pos = curwin->w_cursor; - // If the matching '{' has a ')' immediately before it (ignoring - // white-space), then line up with the start of the line - // containing the matching '(' if there is one. This handles the - // case where an "if (..\n..) {" statement continues over multiple - // lines -- webb - ptr = ml_get(pos->lnum); - i = pos->col; - if (i > 0) { // skip blanks before '{' - while (--i > 0 && ascii_iswhite(ptr[i])) {} - } - curwin->w_cursor.lnum = pos->lnum; - curwin->w_cursor.col = i; - if (ptr[i] == ')' && (pos = findmatch(NULL, '(')) != NULL) { - curwin->w_cursor = *pos; - } - i = get_indent(); - curwin->w_cursor = old_pos; - if (State & VREPLACE_FLAG) { - change_indent(INDENT_SET, i, false, true); - } else { - set_indent(i, SIN_CHANGED); - } - } else if (curwin->w_cursor.col > 0) { - // when inserting '{' after "O" reduce indent, but not - // more than indent of previous line - temp = true; - if (c == '{' && can_si_back && curwin->w_cursor.lnum > 1) { - old_pos = curwin->w_cursor; - i = get_indent(); - while (curwin->w_cursor.lnum > 1) { - ptr = skipwhite(ml_get(--(curwin->w_cursor.lnum))); - - // ignore empty lines and lines starting with '#'. - if (*ptr != '#' && *ptr != NUL) { - break; - } - } - if (get_indent() >= i) { - temp = false; - } - curwin->w_cursor = old_pos; - } - if (temp) { - shift_line(true, false, 1, true); - } - } - } - - // set indent of '#' always to 0 - if (curwin->w_cursor.col > 0 && can_si && c == '#' && inindent(0)) { - // remember current indent for next line - old_indent = get_indent(); - set_indent(0, SIN_CHANGED); - } - - // Adjust ai_col, the char at this position can be deleted. - ai_col = MIN(ai_col, curwin->w_cursor.col); -} - // Get the value that w_virtcol would have when 'list' is off. // Unless 'cpo' contains the 'L' flag. colnr_T get_nolist_virtcol(void) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 59421c24f7..4ed902b89a 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -266,6 +266,7 @@ static struct vimvar { VV(VV_TYPE_BOOL, "t_bool", VAR_NUMBER, VV_RO), VV(VV_TYPE_BLOB, "t_blob", VAR_NUMBER, VV_RO), VV(VV_EVENT, "event", VAR_DICT, VV_RO), + VV(VV_VERSIONLONG, "versionlong", VAR_NUMBER, VV_RO), VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO), VV(VV_ARGV, "argv", VAR_LIST, VV_RO), VV(VV_COLLATE, "collate", VAR_STRING, VV_RO), @@ -433,6 +434,7 @@ void eval_init(void) } } vimvars[VV_VERSION].vv_nr = VIM_VERSION_100; + vimvars[VV_VERSIONLONG].vv_nr = VIM_VERSION_100 * 10000 + highest_patch(); dict_T *const msgpack_types_dict = tv_dict_alloc(); for (size_t i = 0; i < ARRAY_SIZE(msgpack_type_names); i++) { diff --git a/src/nvim/eval.h b/src/nvim/eval.h index 8b4aa8101a..bd018b034f 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -162,6 +162,7 @@ typedef enum { VV_TYPE_BOOL, VV_TYPE_BLOB, VV_EVENT, + VV_VERSIONLONG, VV_ECHOSPACE, VV_ARGV, VV_COLLATE, diff --git a/src/nvim/eval/buffer.c b/src/nvim/eval/buffer.c index a03c19b523..f713c70ef5 100644 --- a/src/nvim/eval/buffer.c +++ b/src/nvim/eval/buffer.c @@ -706,27 +706,3 @@ void restore_buffer(bufref_T *save_curbuf) curbuf->b_nwindows++; } } - -/// Find a window for buffer "buf". -/// If found true is returned and "wp" and "tp" are set to -/// the window and tabpage. -/// If not found, false is returned. -/// -/// @param buf buffer to find a window for -/// @param[out] wp stores the found window -/// @param[out] tp stores the found tabpage -/// -/// @return true if a window was found for the buffer. -bool find_win_for_buf(buf_T *buf, win_T **wp, tabpage_T **tp) -{ - *wp = NULL; - *tp = NULL; - FOR_ALL_TAB_WINDOWS(tp2, wp2) { - if (wp2->w_buffer == buf) { - *tp = tp2; - *wp = wp2; - return true; - } - } - return false; -} diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 6f98d0eb5f..f72148d0ba 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -777,20 +777,6 @@ static void f_charcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) get_col(argvars, rettv, true); } -/// "cindent(lnum)" function -static void f_cindent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - pos_T pos = curwin->w_cursor; - linenr_T lnum = tv_get_lnum(argvars); - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { - curwin->w_cursor.lnum = lnum; - rettv->vval.v_number = get_c_indent(); - curwin->w_cursor = pos; - } else { - rettv->vval.v_number = -1; - } -} - win_T *get_optional_window(typval_T *argvars, int idx) { if (argvars[idx].v_type == VAR_UNKNOWN) { @@ -3246,17 +3232,6 @@ static void f_hostname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_string = xstrdup(hostname); } -/// "indent()" function -static void f_indent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - const linenr_T lnum = tv_get_lnum(argvars); - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { - rettv->vval.v_number = get_indent_lnum(lnum); - } else { - rettv->vval.v_number = -1; - } -} - /// "index()" function static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -4415,20 +4390,6 @@ static void f_line2byte(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } -/// "lispindent(lnum)" function -static void f_lispindent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - const pos_T pos = curwin->w_cursor; - const linenr_T lnum = tv_get_lnum(argvars); - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { - curwin->w_cursor.lnum = lnum; - rettv->vval.v_number = get_lisp_indent(); - curwin->w_cursor = pos; - } else { - rettv->vval.v_number = -1; - } -} - /// "localtime()" function static void f_localtime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -5280,52 +5241,6 @@ static void f_printf(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } -/// "prompt_setcallback({buffer}, {callback})" function -static void f_prompt_setcallback(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - Callback prompt_callback = { .type = kCallbackNone }; - - if (check_secure()) { - return; - } - buf_T *buf = tv_get_buf(&argvars[0], false); - if (buf == NULL) { - return; - } - - if (argvars[1].v_type != VAR_STRING || *argvars[1].vval.v_string != NUL) { - if (!callback_from_typval(&prompt_callback, &argvars[1])) { - return; - } - } - - callback_free(&buf->b_prompt_callback); - buf->b_prompt_callback = prompt_callback; -} - -/// "prompt_setinterrupt({buffer}, {callback})" function -static void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - Callback interrupt_callback = { .type = kCallbackNone }; - - if (check_secure()) { - return; - } - buf_T *buf = tv_get_buf(&argvars[0], false); - if (buf == NULL) { - return; - } - - if (argvars[1].v_type != VAR_STRING || *argvars[1].vval.v_string != NUL) { - if (!callback_from_typval(&interrupt_callback, &argvars[1])) { - return; - } - } - - callback_free(&buf->b_prompt_interrupt); - buf->b_prompt_interrupt = interrupt_callback; -} - /// "prompt_getprompt({buffer})" function static void f_prompt_getprompt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) FUNC_ATTR_NONNULL_ALL @@ -5346,22 +5261,6 @@ static void f_prompt_getprompt(typval_T *argvars, typval_T *rettv, EvalFuncData rettv->vval.v_string = xstrdup(buf_prompt_text(buf)); } -/// "prompt_setprompt({buffer}, {text})" function -static void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - if (check_secure()) { - return; - } - buf_T *buf = tv_get_buf(&argvars[0], false); - if (buf == NULL) { - return; - } - - const char *text = tv_get_string(&argvars[1]); - xfree(buf->b_prompt_text); - buf->b_prompt_text = xstrdup(text); -} - /// "prompt_getinput({buffer})" function static void f_prompt_getinput(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) FUNC_ATTR_NONNULL_ALL diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index f9cf245e50..8a44b180da 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -190,7 +190,7 @@ void tv_list_watch_remove(list_T *const l, listwatch_T *const lwrem) /// /// @param[out] l List from which item is removed. /// @param[in] item List item being removed. -void tv_list_watch_fix(list_T *const l, const listitem_T *const item) +static void tv_list_watch_fix(list_T *const l, const listitem_T *const item) FUNC_ATTR_NONNULL_ALL { for (listwatch_T *lw = l->lv_watch; lw != NULL; lw = lw->lw_next) { diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 36cca6cf22..fcd5fcdd9e 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -2144,7 +2144,7 @@ void do_wqall(exarg_T *eap) /// Check the 'write' option. /// /// @return true and give a message when it's not st. -bool not_writing(void) +static bool not_writing(void) { if (p_write) { return false; diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index 18c691d076..424ee07779 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -799,7 +799,7 @@ void report_make_pending(int pending, void *value) /// If something pending in a finally clause is resumed at the ":endtry", report /// it if required by the 'verbose' option or when debugging. -void report_resume_pending(int pending, void *value) +static void report_resume_pending(int pending, void *value) { if (p_verbose >= 14 || debug_break_level > 0) { if (debug_break_level <= 0) { @@ -814,7 +814,7 @@ void report_resume_pending(int pending, void *value) /// If something pending in a finally clause is discarded, report it if required /// by the 'verbose' option or when debugging. -void report_discard_pending(int pending, void *value) +static void report_discard_pending(int pending, void *value) { if (p_verbose >= 14 || debug_break_level > 0) { if (debug_break_level <= 0) { diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index c882777f1c..3c9290c308 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -1031,7 +1031,7 @@ fail: /// Free the list of lists of visited files and directories /// Can handle it if the passed search_context is NULL; -void vim_findfile_free_visited(void *search_ctx_arg) +static void vim_findfile_free_visited(void *search_ctx_arg) { if (search_ctx_arg == NULL) { return; diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index c40371d4cf..61320945d4 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -2049,20 +2049,6 @@ static char *readfile_charconvert(char *fname, char *fenc, int *fdp) return tmpname; } -/// Read marks for the current buffer from the ShaDa file, when we support -/// buffer marks and the buffer has a name. -static void check_marks_read(void) -{ - if (!curbuf->b_marks_read && get_shada_parameter('\'') > 0 - && curbuf->b_ffname != NULL) { - shada_read_marks(); - } - - // Always set b_marks_read; needed when 'shada' is changed to include - // the ' parameter after opening a buffer. - curbuf->b_marks_read = true; -} - /// Set the name of the current buffer. Use when the buffer doesn't have a /// name and a ":r" or ":w" command with a file name is used. int set_rw_fname(char *fname, char *sfname) diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index c945d878fc..28338e026e 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1282,7 +1282,7 @@ void may_sync_undo(void) } /// Make "typebuf" empty and allocate new buffers. -void alloc_typebuf(void) +static void alloc_typebuf(void) { typebuf.tb_buf = xmalloc(TYPELEN_INIT); typebuf.tb_noremap = xmalloc(TYPELEN_INIT); @@ -1298,7 +1298,7 @@ void alloc_typebuf(void) } /// Free the buffers of "typebuf". -void free_typebuf(void) +static void free_typebuf(void) { if (typebuf.tb_buf == typebuf_init) { internal_error("Free typebuf 1"); diff --git a/src/nvim/indent.c b/src/nvim/indent.c index 399e5afbb8..70d83cea2e 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -15,6 +15,7 @@ #include "nvim/edit.h" #include "nvim/errors.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" @@ -31,6 +32,7 @@ #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/move.h" +#include "nvim/ops.h" #include "nvim/option.h" #include "nvim/option_defs.h" #include "nvim/option_vars.h" @@ -276,7 +278,7 @@ void tabstop_fromto(colnr_T start_col, colnr_T end_col, int ts_arg, const colnr_ } /// See if two tabstop arrays contain the same values. -bool tabstop_eq(const colnr_T *ts1, const colnr_T *ts2) +static bool tabstop_eq(const colnr_T *ts1, const colnr_T *ts2) { if ((ts1 == 0 && ts2) || (ts1 && ts2 == 0)) { return false; @@ -333,7 +335,7 @@ int get_sw_value(buf_T *buf) } /// Idem, using "pos". -int get_sw_value_pos(buf_T *buf, pos_T *pos, bool left) +static int get_sw_value_pos(buf_T *buf, pos_T *pos, bool left) { pos_T save_cursor = curwin->w_cursor; @@ -977,12 +979,482 @@ bool inindent(int extra) return false; } +/// Handle reindenting a block of lines. +void op_reindent(oparg_T *oap, Indenter how) +{ + int i = 0; + linenr_T first_changed = 0; + linenr_T last_changed = 0; + linenr_T start_lnum = curwin->w_cursor.lnum; + + // Don't even try when 'modifiable' is off. + if (!MODIFIABLE(curbuf)) { + emsg(_(e_modifiable)); + return; + } + + // Save for undo. Do this once for all lines, much faster than doing this + // for each line separately, especially when undoing. + if (u_savecommon(curbuf, start_lnum - 1, start_lnum + oap->line_count, + start_lnum + oap->line_count, false) == OK) { + int amount; + for (i = oap->line_count - 1; i >= 0 && !got_int; i--) { + // it's a slow thing to do, so give feedback so there's no worry + // that the computer's just hung. + + if (i > 1 + && (i % 50 == 0 || i == oap->line_count - 1) + && oap->line_count > p_report) { + smsg(0, _("%" PRId64 " lines to indent... "), (int64_t)i); + } + + // Be vi-compatible: For lisp indenting the first line is not + // indented, unless there is only one line. + if (i != oap->line_count - 1 || oap->line_count == 1 + || how != get_lisp_indent) { + char *l = skipwhite(get_cursor_line_ptr()); + if (*l == NUL) { // empty or blank line + amount = 0; + } else { + amount = how(); // get the indent for this line + } + if (amount >= 0 && set_indent(amount, 0)) { + // did change the indent, call changed_lines() later + if (first_changed == 0) { + first_changed = curwin->w_cursor.lnum; + } + last_changed = curwin->w_cursor.lnum; + } + } + curwin->w_cursor.lnum++; + curwin->w_cursor.col = 0; // make sure it's valid + } + } + + // put cursor on first non-blank of indented line + curwin->w_cursor.lnum = start_lnum; + beginline(BL_SOL | BL_FIX); + + // Mark changed lines so that they will be redrawn. When Visual + // highlighting was present, need to continue until the last line. When + // there is no change still need to remove the Visual highlighting. + if (last_changed != 0) { + changed_lines(curbuf, first_changed, 0, + oap->is_VIsual ? start_lnum + oap->line_count + : last_changed + 1, 0, true); + } else if (oap->is_VIsual) { + redraw_curbuf_later(UPD_INVERTED); + } + + if (oap->line_count > p_report) { + i = oap->line_count - (i + 1); + smsg(0, NGETTEXT("%" PRId64 " line indented ", "%" PRId64 " lines indented ", i), (int64_t)i); + } + if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { + // set '[ and '] marks + curbuf->b_op_start = oap->start; + curbuf->b_op_end = oap->end; + } +} + +/// @return true if lines starting with '#' should be left aligned. +bool preprocs_left(void) +{ + return ((curbuf->b_p_si && !curbuf->b_p_cin) + || (curbuf->b_p_cin && in_cinkeys('#', ' ', true) + && curbuf->b_ind_hash_comment == 0)); +} + /// @return true if the conditions are OK for smart indenting. bool may_do_si(void) { return curbuf->b_p_si && !curbuf->b_p_cin && *curbuf->b_p_inde == NUL && !p_paste; } +// Try to do some very smart auto-indenting. +// Used when inserting a "normal" character. +void ins_try_si(int c) +{ + pos_T *pos; + + // do some very smart indenting when entering '{' or '}' + if (((did_si || can_si_back) && c == '{') || (can_si && c == '}' && inindent(0))) { + pos_T old_pos; + char *ptr; + int i; + bool temp; + // for '}' set indent equal to indent of line containing matching '{' + if (c == '}' && (pos = findmatch(NULL, '{')) != NULL) { + old_pos = curwin->w_cursor; + // If the matching '{' has a ')' immediately before it (ignoring + // white-space), then line up with the start of the line + // containing the matching '(' if there is one. This handles the + // case where an "if (..\n..) {" statement continues over multiple + // lines -- webb + ptr = ml_get(pos->lnum); + i = pos->col; + if (i > 0) { // skip blanks before '{' + while (--i > 0 && ascii_iswhite(ptr[i])) {} + } + curwin->w_cursor.lnum = pos->lnum; + curwin->w_cursor.col = i; + if (ptr[i] == ')' && (pos = findmatch(NULL, '(')) != NULL) { + curwin->w_cursor = *pos; + } + i = get_indent(); + curwin->w_cursor = old_pos; + if (State & VREPLACE_FLAG) { + change_indent(INDENT_SET, i, false, true); + } else { + set_indent(i, SIN_CHANGED); + } + } else if (curwin->w_cursor.col > 0) { + // when inserting '{' after "O" reduce indent, but not + // more than indent of previous line + temp = true; + if (c == '{' && can_si_back && curwin->w_cursor.lnum > 1) { + old_pos = curwin->w_cursor; + i = get_indent(); + while (curwin->w_cursor.lnum > 1) { + ptr = skipwhite(ml_get(--(curwin->w_cursor.lnum))); + + // ignore empty lines and lines starting with '#'. + if (*ptr != '#' && *ptr != NUL) { + break; + } + } + if (get_indent() >= i) { + temp = false; + } + curwin->w_cursor = old_pos; + } + if (temp) { + shift_line(true, false, 1, true); + } + } + } + + // set indent of '#' always to 0 + if (curwin->w_cursor.col > 0 && can_si && c == '#' && inindent(0)) { + // remember current indent for next line + old_indent = get_indent(); + set_indent(0, SIN_CHANGED); + } + + // Adjust ai_col, the char at this position can be deleted. + ai_col = MIN(ai_col, curwin->w_cursor.col); +} + +/// Insert an indent (for or CTRL-T) or delete an indent (for CTRL-D). +/// Keep the cursor on the same character. +/// type == INDENT_INC increase indent (for CTRL-T or ) +/// type == INDENT_DEC decrease indent (for CTRL-D) +/// type == INDENT_SET set indent to "amount" +/// +/// @param round if true, round the indent to 'shiftwidth' (only with _INC and _Dec). +/// @param call_changed_bytes call changed_bytes() +void change_indent(int type, int amount, int round, bool call_changed_bytes) +{ + int insstart_less; // reduction for Insstart.col + colnr_T orig_col = 0; // init for GCC + char *orig_line = NULL; // init for GCC + + // MODE_VREPLACE state needs to know what the line was like before changing + if (State & VREPLACE_FLAG) { + orig_line = xstrnsave(get_cursor_line_ptr(), (size_t)get_cursor_line_len()); + orig_col = curwin->w_cursor.col; + } + + // for the following tricks we don't want list mode + int save_p_list = curwin->w_p_list; + curwin->w_p_list = false; + colnr_T vc = getvcol_nolist(&curwin->w_cursor); + int vcol = vc; + + // For Replace mode we need to fix the replace stack later, which is only + // possible when the cursor is in the indent. Remember the number of + // characters before the cursor if it's possible. + int start_col = curwin->w_cursor.col; + + // determine offset from first non-blank + int new_cursor_col = curwin->w_cursor.col; + beginline(BL_WHITE); + new_cursor_col -= curwin->w_cursor.col; + + insstart_less = curwin->w_cursor.col; + + // If the cursor is in the indent, compute how many screen columns the + // cursor is to the left of the first non-blank. + if (new_cursor_col < 0) { + vcol = get_indent() - vcol; + } + + if (new_cursor_col > 0) { // can't fix replace stack + start_col = -1; + } + + // Set the new indent. The cursor will be put on the first non-blank. + if (type == INDENT_SET) { + set_indent(amount, call_changed_bytes ? SIN_CHANGED : 0); + } else { + int save_State = State; + + // Avoid being called recursively. + if (State & VREPLACE_FLAG) { + State = MODE_INSERT; + } + shift_line(type == INDENT_DEC, round, 1, call_changed_bytes); + State = save_State; + } + insstart_less -= curwin->w_cursor.col; + + // Try to put cursor on same character. + // If the cursor is at or after the first non-blank in the line, + // compute the cursor column relative to the column of the first + // non-blank character. + // If we are not in insert mode, leave the cursor on the first non-blank. + // If the cursor is before the first non-blank, position it relative + // to the first non-blank, counted in screen columns. + if (new_cursor_col >= 0) { + // When changing the indent while the cursor is touching it, reset + // Insstart_col to 0. + if (new_cursor_col == 0) { + insstart_less = MAXCOL; + } + new_cursor_col += curwin->w_cursor.col; + } else if (!(State & MODE_INSERT)) { + new_cursor_col = curwin->w_cursor.col; + } else { + // Compute the screen column where the cursor should be. + vcol = get_indent() - vcol; + int const end_vcol = (colnr_T)((vcol < 0) ? 0 : vcol); + curwin->w_virtcol = end_vcol; + + // Advance the cursor until we reach the right screen column. + new_cursor_col = 0; + char *const line = get_cursor_line_ptr(); + vcol = 0; + if (*line != NUL) { + CharsizeArg csarg; + CSType cstype = init_charsize_arg(&csarg, curwin, 0, line); + StrCharInfo ci = utf_ptr2StrCharInfo(line); + while (true) { + int next_vcol = vcol + win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &csarg).width; + if (next_vcol > end_vcol) { + break; + } + vcol = next_vcol; + ci = utfc_next(ci); + if (*ci.ptr == NUL) { + break; + } + } + new_cursor_col = (int)(ci.ptr - line); + } + + // May need to insert spaces to be able to position the cursor on + // the right screen column. + if (vcol != (int)curwin->w_virtcol) { + curwin->w_cursor.col = (colnr_T)new_cursor_col; + const size_t ptrlen = (size_t)(curwin->w_virtcol - vcol); + char *ptr = xmallocz(ptrlen); + memset(ptr, ' ', ptrlen); + new_cursor_col += (int)ptrlen; + ins_str(ptr, ptrlen); + xfree(ptr); + } + + // When changing the indent while the cursor is in it, reset + // Insstart_col to 0. + insstart_less = MAXCOL; + } + + curwin->w_p_list = save_p_list; + curwin->w_cursor.col = MAX(0, (colnr_T)new_cursor_col); + curwin->w_set_curswant = true; + changed_cline_bef_curs(curwin); + + // May have to adjust the start of the insert. + if (State & MODE_INSERT) { + if (curwin->w_cursor.lnum == Insstart.lnum && Insstart.col != 0) { + if ((int)Insstart.col <= insstart_less) { + Insstart.col = 0; + } else { + Insstart.col -= insstart_less; + } + } + if ((int)ai_col <= insstart_less) { + ai_col = 0; + } else { + ai_col -= insstart_less; + } + } + + // For MODE_REPLACE state, may have to fix the replace stack, if it's + // possible. If the number of characters before the cursor decreased, need + // to pop a few characters from the replace stack. + // If the number of characters before the cursor increased, need to push a + // few NULs onto the replace stack. + if (REPLACE_NORMAL(State) && start_col >= 0) { + while (start_col > (int)curwin->w_cursor.col) { + replace_join(0); // remove a NUL from the replace stack + start_col--; + } + while (start_col < (int)curwin->w_cursor.col) { + replace_push_nul(); + start_col++; + } + } + + // For MODE_VREPLACE state, we also have to fix the replace stack. In this + // case it is always possible because we backspace over the whole line and + // then put it back again the way we wanted it. + if (State & VREPLACE_FLAG) { + // Save new line + char *new_line = xstrnsave(get_cursor_line_ptr(), (size_t)get_cursor_line_len()); + + // We only put back the new line up to the cursor + new_line[curwin->w_cursor.col] = NUL; + int new_col = curwin->w_cursor.col; + + // Put back original line + ml_replace(curwin->w_cursor.lnum, orig_line, false); + curwin->w_cursor.col = orig_col; + + curbuf_splice_pending++; + + // Backspace from cursor to start of line + backspace_until_column(0); + + // Insert new stuff into line again + ins_bytes(new_line); + + xfree(new_line); + + curbuf_splice_pending--; + + // TODO(bfredl): test for crazy edge cases, like we stand on a TAB or + // something? does this even do the right text change then? + int delta = orig_col - new_col; + extmark_splice_cols(curbuf, (int)curwin->w_cursor.lnum - 1, new_col, + delta < 0 ? -delta : 0, + delta > 0 ? delta : 0, + kExtmarkUndo); + } +} + +/// Copy the indent from ptr to the current line (and fill to size). +/// Leaves the cursor on the first non-blank in the line. +/// +/// @return true if the line was changed. +bool copy_indent(int size, char *src) +{ + char *p = NULL; + char *line = NULL; + int ind_len; + int line_len = 0; + int tab_pad; + + // Round 1: compute the number of characters needed for the indent + // Round 2: copy the characters. + for (int round = 1; round <= 2; round++) { + int todo = size; + ind_len = 0; + int ind_done = 0; + int ind_col = 0; + char *s = src; + + // Count/copy the usable portion of the source line. + while (todo > 0 && ascii_iswhite(*s)) { + if (*s == TAB) { + tab_pad = tabstop_padding(ind_done, + curbuf->b_p_ts, + curbuf->b_p_vts_array); + + // Stop if this tab will overshoot the target. + if (todo < tab_pad) { + break; + } + todo -= tab_pad; + ind_done += tab_pad; + ind_col += tab_pad; + } else { + todo--; + ind_done++; + ind_col++; + } + ind_len++; + + if (p != NULL) { + *p++ = *s; + } + s++; + } + + // Fill to next tabstop with a tab, if possible. + tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts, curbuf->b_p_vts_array); + + if ((todo >= tab_pad) && !curbuf->b_p_et) { + todo -= tab_pad; + ind_len++; + ind_col += tab_pad; + + if (p != NULL) { + *p++ = TAB; + } + } + + // Add tabs required for indent. + if (!curbuf->b_p_et) { + while (true) { + tab_pad = tabstop_padding(ind_col, + curbuf->b_p_ts, + curbuf->b_p_vts_array); + if (todo < tab_pad) { + break; + } + todo -= tab_pad; + ind_len++; + ind_col += tab_pad; + if (p != NULL) { + *p++ = TAB; + } + } + } + + // Count/add spaces required for indent. + while (todo > 0) { + todo--; + ind_len++; + + if (p != NULL) { + *p++ = ' '; + } + } + + if (p == NULL) { + // Allocate memory for the result: the copied indent, new indent + // and the rest of the line. + line_len = get_cursor_line_len() + 1; + assert(ind_len + line_len >= 0); + size_t line_size; + STRICT_ADD(ind_len, line_len, &line_size, size_t); + line = xmalloc(line_size); + p = line; + } + } + + // Append the original line + memmove(p, get_cursor_line_ptr(), (size_t)line_len); + + // Replace the line + ml_replace(curwin->w_cursor.lnum, line, false); + + // Put the cursor after the indent. + curwin->w_cursor.col = ind_len; + return true; +} + /// Give a "resulting text too long" error and maybe set got_int. static void emsg_text_too_long(void) { @@ -1474,3 +1946,28 @@ void fix_indent(void) do_c_expr_indent(); } } + +/// "indent()" function +void f_indent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const linenr_T lnum = tv_get_lnum(argvars); + if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { + rettv->vval.v_number = get_indent_lnum(lnum); + } else { + rettv->vval.v_number = -1; + } +} + +/// "lispindent(lnum)" function +void f_lispindent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const pos_T pos = curwin->w_cursor; + const linenr_T lnum = tv_get_lnum(argvars); + if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { + curwin->w_cursor.lnum = lnum; + rettv->vval.v_number = get_lisp_indent(); + curwin->w_cursor = pos; + } else { + rettv->vval.v_number = -1; + } +} diff --git a/src/nvim/indent.h b/src/nvim/indent.h index 54d27e6243..c7b5279721 100644 --- a/src/nvim/indent.h +++ b/src/nvim/indent.h @@ -1,6 +1,7 @@ #pragma once #include "nvim/ex_cmds_defs.h" // IWYU pragma: keep +#include "nvim/normal_defs.h" #include "nvim/pos_defs.h" // IWYU pragma: keep #include "nvim/types_defs.h" // IWYU pragma: keep @@ -14,6 +15,8 @@ enum { SIN_NOMARK = 8, ///< don't adjust extmarks }; +typedef int (*Indenter)(void); + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "indent.h.generated.h" #endif diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index 307e800be7..345d1a7720 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -9,9 +9,11 @@ #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/edit.h" +#include "nvim/eval/typval.h" #include "nvim/globals.h" #include "nvim/indent.h" #include "nvim/indent_c.h" +#include "nvim/keycodes.h" #include "nvim/macros_defs.h" #include "nvim/mark_defs.h" #include "nvim/math.h" @@ -232,6 +234,13 @@ bool cin_is_cinword(const char *line) return retval; } +/// Check that C-indenting is on. +bool cindent_on(void) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return !p_paste && (curbuf->b_p_cin || *curbuf->b_p_inde != NUL); +} + // Skip over white space and C comments within the line. // Also skip over Perl/shell comments if desired. static const char *cin_skipcomment(const char *s) @@ -346,7 +355,7 @@ static bool cin_islabel_skip(const char **s) // Recognize a label: "label:". // Note: curwin->w_cursor must be where we are looking for the label. -bool cin_islabel(void) // XXX +static bool cin_islabel(void) // XXX { const char *s = cin_skipcomment(get_cursor_line_ptr()); @@ -445,7 +454,7 @@ static int cin_isinit(void) /// Recognize a switch label: "case .*:" or "default:". /// /// @param strict Allow relaxed check of case statement for JS -bool cin_iscase(const char *s, bool strict) +static bool cin_iscase(const char *s, bool strict) { s = cin_skipcomment(s); if (cin_starts_with(s, "case")) { @@ -491,7 +500,7 @@ static int cin_isdefault(const char *s) } /// Recognize a scope declaration label set in 'cinscopedecls'. -bool cin_isscopedecl(const char *p) +static bool cin_isscopedecl(const char *p) { const char *s = cin_skipcomment(p); @@ -3695,6 +3704,226 @@ static int find_match(int lookfor, linenr_T ourscope) return FAIL; } +/// Check that "cinkeys" contains the key "keytyped", +/// when == '*': Only if key is preceded with '*' (indent before insert) +/// when == '!': Only if key is preceded with '!' (don't insert) +/// when == ' ': Only if key is not preceded with '*' or '!' (indent afterwards) +/// +/// "keytyped" can have a few special values: +/// KEY_OPEN_FORW : +/// KEY_OPEN_BACK : +/// KEY_COMPLETE : Just finished completion. +/// +/// @param keytyped key that was typed +/// @param when condition on when to perform the check +/// @param line_is_empty when true, accept keys with '0' before them. +bool in_cinkeys(int keytyped, int when, bool line_is_empty) +{ + char *look; + bool try_match; + bool try_match_word; + char *p; + bool icase; + + if (keytyped == NUL) { + // Can happen with CTRL-Y and CTRL-E on a short line. + return false; + } + + if (*curbuf->b_p_inde != NUL) { + look = curbuf->b_p_indk; // 'indentexpr' set: use 'indentkeys' + } else { + look = curbuf->b_p_cink; // 'indentexpr' empty: use 'cinkeys' + } + while (*look) { + // Find out if we want to try a match with this key, depending on + // 'when' and a '*' or '!' before the key. + switch (when) { + case '*': + try_match = (*look == '*'); break; + case '!': + try_match = (*look == '!'); break; + default: + try_match = (*look != '*') && (*look != '!'); break; + } + if (*look == '*' || *look == '!') { + look++; + } + + // If there is a '0', only accept a match if the line is empty. + // But may still match when typing last char of a word. + if (*look == '0') { + try_match_word = try_match; + if (!line_is_empty) { + try_match = false; + } + look++; + } else { + try_match_word = false; + } + + // Does it look like a control character? + if (*look == '^' && look[1] >= '?' && look[1] <= '_') { + if (try_match && keytyped == CTRL_CHR(look[1])) { + return true; + } + look += 2; + + // 'o' means "o" command, open forward. + // 'O' means "O" command, open backward. + } else if (*look == 'o') { + if (try_match && keytyped == KEY_OPEN_FORW) { + return true; + } + look++; + } else if (*look == 'O') { + if (try_match && keytyped == KEY_OPEN_BACK) { + return true; + } + look++; + + // 'e' means to check for "else" at start of line and just before the + // cursor. + } else if (*look == 'e') { + if (try_match && keytyped == 'e' && curwin->w_cursor.col >= 4) { + p = get_cursor_line_ptr(); + if (skipwhite(p) == p + curwin->w_cursor.col - 4 + && strncmp(p + curwin->w_cursor.col - 4, "else", 4) == 0) { + return true; + } + } + look++; + + // ':' only causes an indent if it is at the end of a label or case + // statement, or when it was before typing the ':' (to fix + // class::method for C++). + } else if (*look == ':') { + if (try_match && keytyped == ':') { + p = get_cursor_line_ptr(); + if (cin_iscase(p, false) || cin_isscopedecl(p) || cin_islabel()) { + return true; + } + // Need to get the line again after cin_islabel(). + p = get_cursor_line_ptr(); + if (curwin->w_cursor.col > 2 + && p[curwin->w_cursor.col - 1] == ':' + && p[curwin->w_cursor.col - 2] == ':') { + p[curwin->w_cursor.col - 1] = ' '; + const bool i = cin_iscase(p, false) + || cin_isscopedecl(p) + || cin_islabel(); + p = get_cursor_line_ptr(); + p[curwin->w_cursor.col - 1] = ':'; + if (i) { + return true; + } + } + } + look++; + + // Is it a key in <>, maybe? + } else if (*look == '<') { + if (try_match) { + // make up some named keys , , , <0>, <>>, <<>, <*>, + // <:> and so that people can re-indent on o, O, e, 0, <, + // >, *, : and ! keys if they really really want to. + if (vim_strchr("<>!*oOe0:", (uint8_t)look[1]) != NULL + && keytyped == look[1]) { + return true; + } + + if (keytyped == get_special_key_code(look + 1)) { + return true; + } + } + while (*look && *look != '>') { + look++; + } + while (*look == '>') { + look++; + } + // Is it a word: "=word"? + } else if (*look == '=' && look[1] != ',' && look[1] != NUL) { + look++; + if (*look == '~') { + icase = true; + look++; + } else { + icase = false; + } + p = vim_strchr(look, ','); + if (p == NULL) { + p = look + strlen(look); + } + if ((try_match || try_match_word) + && curwin->w_cursor.col >= (colnr_T)(p - look)) { + bool match = false; + + if (keytyped == KEY_COMPLETE) { + char *n, *s; + + // Just completed a word, check if it starts with "look". + // search back for the start of a word. + char *line = get_cursor_line_ptr(); + for (s = line + curwin->w_cursor.col; s > line; s = n) { + n = mb_prevptr(line, s); + if (!vim_iswordp(n)) { + break; + } + } + assert(p >= look && (uintmax_t)(p - look) <= SIZE_MAX); + if (s + (p - look) <= line + curwin->w_cursor.col + && (icase + ? mb_strnicmp(s, look, (size_t)(p - look)) + : strncmp(s, look, (size_t)(p - look))) == 0) { + match = true; + } + } else { + // TODO(@brammool): multi-byte + if (keytyped == (int)(uint8_t)p[-1] + || (icase && keytyped < 256 && keytyped >= 0 + && TOLOWER_LOC(keytyped) == TOLOWER_LOC((uint8_t)p[-1]))) { + char *line = get_cursor_pos_ptr(); + assert(p >= look && (uintmax_t)(p - look) <= SIZE_MAX); + if ((curwin->w_cursor.col == (colnr_T)(p - look) + || !vim_iswordc((uint8_t)line[-(p - look) - 1])) + && (icase + ? mb_strnicmp(line - (p - look), look, (size_t)(p - look)) + : strncmp(line - (p - look), look, (size_t)(p - look))) == 0) { + match = true; + } + } + } + if (match && try_match_word && !try_match) { + // "0=word": Check if there are only blanks before the + // word. + if (getwhitecols_curline() != + (int)(curwin->w_cursor.col - (p - look))) { + match = false; + } + } + if (match) { + return true; + } + } + look = p; + + // Ok, it's a boring generic character. + } else { + if (try_match && (uint8_t)(*look) == keytyped) { + return true; + } + if (*look != NUL) { + look++; + } + } + + // Skip over ", ". + look = skip_to_option_part(look); + } + return false; +} + // Do C or expression indenting on the current line. void do_c_expr_indent(void) { @@ -3704,3 +3933,17 @@ void do_c_expr_indent(void) fixthisline(get_c_indent); } } + +/// "cindent(lnum)" function +void f_cindent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + pos_T pos = curwin->w_cursor; + linenr_T lnum = tv_get_lnum(argvars); + if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { + curwin->w_cursor.lnum = lnum; + rettv->vval.v_number = get_c_indent(); + curwin->w_cursor = pos; + } else { + rettv->vval.v_number = -1; + } +} diff --git a/src/nvim/indent_c.h b/src/nvim/indent_c.h index 65a402f02c..aa91f00489 100644 --- a/src/nvim/indent_c.h +++ b/src/nvim/indent_c.h @@ -1,5 +1,6 @@ #pragma once +#include "nvim/eval/typval_defs.h" #include "nvim/pos_defs.h" // IWYU pragma: keep #include "nvim/types_defs.h" // IWYU pragma: keep diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 5762e43e94..d465b3e70f 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -1090,7 +1090,7 @@ char *get_menu_names(expand_T *xp, int idx) /// /// @param name may be modified. /// @return start of the next element -char *menu_name_skip(char *const name) +static char *menu_name_skip(char *const name) { char *p; diff --git a/src/nvim/message.c b/src/nvim/message.c index 5c96fb9f73..94faab771b 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -662,7 +662,7 @@ void msg_source(int hl_id) /// If "emsg_off" is set: no error messages at the moment. /// If "msg" is in 'debug': do error message but without side effects. /// If "emsg_skip" is set: never do error messages. -int emsg_not_now(void) +static int emsg_not_now(void) { if ((emsg_off > 0 && vim_strchr(p_debug, 'm') == NULL && vim_strchr(p_debug, 't') == NULL) @@ -3045,7 +3045,7 @@ static bool do_more_prompt(int typed_char) return retval; } -void msg_moremsg(bool full) +static void msg_moremsg(bool full) { int attr = hl_combine_attr(HL_ATTR(HLF_MSG), HL_ATTR(HLF_M)); grid_line_start(&msg_grid_adj, Rows - 1); @@ -3739,7 +3739,7 @@ static void copy_confirm_hotkeys(const char *buttons, int default_button_idx, } /// Display the ":confirm" message. Also called when screen resized. -void display_confirm_msg(void) +static void display_confirm_msg(void) { // Avoid that 'q' at the more prompt truncates the message here. confirm_msg_used++; diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 1489033d15..a118ff1a4e 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -134,16 +134,6 @@ static char opchars[][3] = { { Ctrl_X, NUL, OPF_CHANGE }, // OP_NR_SUB }; -yankreg_T *get_y_previous(void) -{ - return y_previous; -} - -void set_y_previous(yankreg_T *yreg) -{ - y_previous = yreg; -} - /// Translate a command name into an operator type. /// Must only be called with a valid operator name! int get_op_type(int char1, int char2) @@ -723,84 +713,6 @@ static void block_insert(oparg_T *oap, const char *s, size_t slen, bool b_insert changed_lines(curbuf, oap->start.lnum + 1, 0, oap->end.lnum + 1, 0, true); } -/// Handle reindenting a block of lines. -void op_reindent(oparg_T *oap, Indenter how) -{ - int i = 0; - linenr_T first_changed = 0; - linenr_T last_changed = 0; - linenr_T start_lnum = curwin->w_cursor.lnum; - - // Don't even try when 'modifiable' is off. - if (!MODIFIABLE(curbuf)) { - emsg(_(e_modifiable)); - return; - } - - // Save for undo. Do this once for all lines, much faster than doing this - // for each line separately, especially when undoing. - if (u_savecommon(curbuf, start_lnum - 1, start_lnum + oap->line_count, - start_lnum + oap->line_count, false) == OK) { - int amount; - for (i = oap->line_count - 1; i >= 0 && !got_int; i--) { - // it's a slow thing to do, so give feedback so there's no worry - // that the computer's just hung. - - if (i > 1 - && (i % 50 == 0 || i == oap->line_count - 1) - && oap->line_count > p_report) { - smsg(0, _("%" PRId64 " lines to indent... "), (int64_t)i); - } - - // Be vi-compatible: For lisp indenting the first line is not - // indented, unless there is only one line. - if (i != oap->line_count - 1 || oap->line_count == 1 - || how != get_lisp_indent) { - char *l = skipwhite(get_cursor_line_ptr()); - if (*l == NUL) { // empty or blank line - amount = 0; - } else { - amount = how(); // get the indent for this line - } - if (amount >= 0 && set_indent(amount, 0)) { - // did change the indent, call changed_lines() later - if (first_changed == 0) { - first_changed = curwin->w_cursor.lnum; - } - last_changed = curwin->w_cursor.lnum; - } - } - curwin->w_cursor.lnum++; - curwin->w_cursor.col = 0; // make sure it's valid - } - } - - // put cursor on first non-blank of indented line - curwin->w_cursor.lnum = start_lnum; - beginline(BL_SOL | BL_FIX); - - // Mark changed lines so that they will be redrawn. When Visual - // highlighting was present, need to continue until the last line. When - // there is no change still need to remove the Visual highlighting. - if (last_changed != 0) { - changed_lines(curbuf, first_changed, 0, - oap->is_VIsual ? start_lnum + oap->line_count - : last_changed + 1, 0, true); - } else if (oap->is_VIsual) { - redraw_curbuf_later(UPD_INVERTED); - } - - if (oap->line_count > p_report) { - i = oap->line_count - (i + 1); - smsg(0, NGETTEXT("%" PRId64 " line indented ", "%" PRId64 " lines indented ", i), (int64_t)i); - } - if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { - // set '[ and '] marks - curbuf->b_op_start = oap->start; - curbuf->b_op_end = oap->end; - } -} - // Keep the last expression line here, for repeating. static char *expr_line = NULL; @@ -3738,14 +3650,6 @@ void adjust_cursor_eol(void) } } -/// @return true if lines starting with '#' should be left aligned. -bool preprocs_left(void) -{ - return ((curbuf->b_p_si && !curbuf->b_p_cin) - || (curbuf->b_p_cin && in_cinkeys('#', ' ', true) - && curbuf->b_ind_hash_comment == 0)); -} - /// @return the character name of the register with the given number int get_register_name(int num) { diff --git a/src/nvim/ops.h b/src/nvim/ops.h index 99b9b6182d..b49618856d 100644 --- a/src/nvim/ops.h +++ b/src/nvim/ops.h @@ -33,8 +33,6 @@ struct block_def { colnr_T start_char_vcols; ///< number of vcols of pre-block char }; -typedef int (*Indenter)(void); - /// flags for do_put() enum { PUT_FIXINDENT = 1, ///< make indent look nice diff --git a/src/nvim/option.c b/src/nvim/option.c index cbcb18fc4a..0d62e705a2 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -1537,40 +1537,6 @@ void set_options_bin(int oldval, int newval, int opt_flags) didset_options_sctx(opt_flags, p_bin_dep_opts); } -/// Find the parameter represented by the given character (eg ', :, ", or /), -/// and return its associated value in the 'shada' string. -/// Only works for number parameters, not for 'r' or 'n'. -/// If the parameter is not specified in the string or there is no following -/// number, return -1. -int get_shada_parameter(int type) -{ - char *p = find_shada_parameter(type); - if (p != NULL && ascii_isdigit(*p)) { - return atoi(p); - } - return -1; -} - -/// Find the parameter represented by the given character (eg ''', ':', '"', or -/// '/') in the 'shada' option and return a pointer to the string after it. -/// Return NULL if the parameter is not specified in the string. -char *find_shada_parameter(int type) -{ - for (char *p = p_shada; *p; p++) { - if (*p == type) { - return p + 1; - } - if (*p == 'n') { // 'n' is always the last one - break; - } - p = vim_strchr(p, ','); // skip until next ',' - if (p == NULL) { // hit the end without finding parameter - break; - } - } - return NULL; -} - /// Expand environment variables for some string options. /// These string options cannot be indirect! /// If "val" is NULL expand the current value of the option. @@ -4950,7 +4916,7 @@ void copy_winopt(winopt_T *from, winopt_T *to) } /// Check string options in a window for a NULL value. -void check_win_options(win_T *win) +static void check_win_options(win_T *win) { check_winopt(&win->w_onebuf_opt); check_winopt(&win->w_allbuf_opt); diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 9fb54ba03f..0144a227e1 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -43,6 +43,7 @@ #include "nvim/pos_defs.h" #include "nvim/regexp.h" #include "nvim/regexp_defs.h" +#include "nvim/shada.h" #include "nvim/spell.h" #include "nvim/spellfile.h" #include "nvim/spellsuggest.h" diff --git a/src/nvim/profile.c b/src/nvim/profile.c index 627f06c9ce..9462f5b285 100644 --- a/src/nvim/profile.c +++ b/src/nvim/profile.c @@ -171,7 +171,7 @@ proftime_T profile_self(proftime_T self, proftime_T total, proftime_T children) /// Gets the current waittime. /// /// @return the current waittime -proftime_T profile_get_wait(void) FUNC_ATTR_PURE +static proftime_T profile_get_wait(void) FUNC_ATTR_PURE { return prof_wait_time; } @@ -194,7 +194,7 @@ proftime_T profile_sub_wait(proftime_T tm, proftime_T tma) FUNC_ATTR_PURE /// Checks if time `tm1` is equal to `tm2`. /// /// @return true if `tm1` == `tm2` -bool profile_equal(proftime_T tm1, proftime_T tm2) FUNC_ATTR_CONST +static bool profile_equal(proftime_T tm1, proftime_T tm2) FUNC_ATTR_CONST { return tm1 == tm2; } diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 55a482d4f9..a9bda0100a 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -3772,3 +3772,51 @@ void shada_read_string(String string, const int flags) shada_read(&sd_reader, flags); close_file(&sd_reader); } + +/// Find the parameter represented by the given character (eg ', :, ", or /), +/// and return its associated value in the 'shada' string. +/// Only works for number parameters, not for 'r' or 'n'. +/// If the parameter is not specified in the string or there is no following +/// number, return -1. +int get_shada_parameter(int type) +{ + char *p = find_shada_parameter(type); + if (p != NULL && ascii_isdigit(*p)) { + return atoi(p); + } + return -1; +} + +/// Find the parameter represented by the given character (eg ''', ':', '"', or +/// '/') in the 'shada' option and return a pointer to the string after it. +/// Return NULL if the parameter is not specified in the string. +char *find_shada_parameter(int type) +{ + for (char *p = p_shada; *p; p++) { + if (*p == type) { + return p + 1; + } + if (*p == 'n') { // 'n' is always the last one + break; + } + p = vim_strchr(p, ','); // skip until next ',' + if (p == NULL) { // hit the end without finding parameter + break; + } + } + return NULL; +} + +/// Read marks for the current buffer from the ShaDa file, when we support +/// buffer marks and the buffer has a name. +void check_marks_read(void) +{ + if (!curbuf->b_marks_read && get_shada_parameter('\'') > 0 + && curbuf->b_ffname != NULL) { + shada_read_marks(); + } + + // Always set b_marks_read; needed when 'shada' is changed to include + // the ' parameter after opening a buffer. + curbuf->b_marks_read = true; +} diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 8d791a4c38..e878fe2709 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -2991,7 +2991,7 @@ void u_clearallandblockfree(buf_T *buf) } /// Save the line "lnum" for the "U" command. -void u_saveline(buf_T *buf, linenr_T lnum) +static void u_saveline(buf_T *buf, linenr_T lnum) { if (lnum == buf->b_u_line_lnum) { // line is already saved return; diff --git a/src/nvim/version.c b/src/nvim/version.c index 721259ba1f..ceedb1c2e2 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -2528,6 +2528,12 @@ bool has_nvim_version(const char *const version_str) && patch <= NVIM_VERSION_PATCH)))); } +int highest_patch(void) +{ + // this relies on the highest patch number to be the first entry + return included_patches[0]; +} + /// Checks whether a Vim patch has been included. /// /// @param n Patch number. diff --git a/src/nvim/vvars.lua b/src/nvim/vvars.lua index 5de617b5cb..67fa5bd87e 100644 --- a/src/nvim/vvars.lua +++ b/src/nvim/vvars.lua @@ -878,6 +878,21 @@ M.vars = { < ]=], }, + versionlong = { + type = 'integer', + desc = [=[ + Like v:version, but also including the patchlevel in the last + four digits. Version 8.1 with patch 123 has value 8010123. + This can be used like this: > + if v:versionlong >= 8010123 + < + However, if there are gaps in the list of patches included + this will not work well. This can happen if a recent patch + was included into an older version, e.g. for a security fix. + Use the has() function to make sure the patch is actually + included. + ]=], + }, vim_did_enter = { type = 'integer', desc = [=[ diff --git a/src/nvim/window.c b/src/nvim/window.c index 92b8eab184..b38799514a 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -4262,7 +4262,7 @@ int win_new_tabpage(int after, char *filename) // Open a new tab page if ":tab cmd" was used. It will edit the same buffer, // like with ":split". // Returns OK if a new tab page was created, FAIL otherwise. -int may_open_tabpage(void) +static int may_open_tabpage(void) { int n = (cmdmod.cmod_tab == 0) ? postponed_split_tab : cmdmod.cmod_tab; diff --git a/test/old/testdir/test_eval_stuff.vim b/test/old/testdir/test_eval_stuff.vim index b1c1136809..f4139c647b 100644 --- a/test/old/testdir/test_eval_stuff.vim +++ b/test/old/testdir/test_eval_stuff.vim @@ -272,6 +272,17 @@ func Test_string_concat_scriptversion1() endif endfunc +" scriptversion 2 +func Test_vvar_scriptversion2() + call assert_true(exists('version')) + echo version + call assert_fails('let version = 1', 'E46:') + call assert_equal(v:version, version) + + call assert_equal(v:version, v:versionlong / 10000) + call assert_true(v:versionlong > 8011525) +endfunc + " scriptversion 1 func Test_vvar_scriptversion1() call assert_equal(15, 017) diff --git a/test/old/testdir/test_ruby.vim b/test/old/testdir/test_ruby.vim index 94941da873..5df11b20cf 100644 --- a/test/old/testdir/test_ruby.vim +++ b/test/old/testdir/test_ruby.vim @@ -319,6 +319,9 @@ func Test_ruby_Vim_evaluate() call assert_equal('foo', rubyeval('Vim::evaluate("\"foo\"")')) call assert_equal('String', rubyeval('Vim::evaluate("\"foo\"").class')) + call assert_equal(["\x01\xAB"], rubyeval('Vim::evaluate("0z01ab").unpack("M")')) + call assert_equal('String', rubyeval('Vim::evaluate("0z01ab").class')) + call assert_equal([1, 2], rubyeval('Vim::evaluate("[1, 2]")')) call assert_equal('Array', rubyeval('Vim::evaluate("[1, 2]").class')) diff --git a/test/unit/profile_spec.lua b/test/unit/profile_spec.lua index 27b817cf42..ed760c37d9 100644 --- a/test/unit/profile_spec.lua +++ b/test/unit/profile_spec.lua @@ -96,9 +96,6 @@ describe('profiling related functions', function() local function profile_cmp(t1, t2) return prof.profile_cmp(t1, t2) end - local function profile_equal(t1, t2) - return prof.profile_equal(t1, t2) - end local function profile_msg(q) return ffi.string(prof.profile_msg(q)) end @@ -110,20 +107,6 @@ describe('profiling related functions', function() return tonumber(s) + tonumber(us) / 1000000 end - describe('profile_equal', function() - itp('times are equal to themselves', function() - local start = profile_start() - assert.is_true(profile_equal(start, start)) - - local e = profile_end(start) - assert.is_true(profile_equal(e, e)) - end) - - itp('times are unequal to others', function() - assert.is_false(profile_equal(profile_start(), profile_start())) - end) - end) - -- this is quite difficult to test, as it would rely on other functions in -- the profiling package. Those functions in turn will probably be tested -- using profile_cmp... circular reasoning. @@ -169,7 +152,6 @@ describe('profiling related functions', function() describe('profile_zero', function() itp('returns the same value on each call', function() eq(0, profile_zero()) - assert.is_true(profile_equal(profile_zero(), profile_zero())) end) end) @@ -203,7 +185,7 @@ describe('profiling related functions', function() describe('profile_setlimit', function() itp('sets no limit when 0 is passed', function() - eq(true, profile_equal(profile_setlimit(0), profile_zero())) + eq(profile_setlimit(0), profile_zero()) end) itp('sets a limit in the future otherwise', function()