aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/lua.txt37
-rw-r--r--runtime/doc/map.txt19
-rw-r--r--runtime/doc/options.txt20
-rw-r--r--runtime/lua/vim/filetype.lua10
-rw-r--r--src/nvim/api/vim.c1
-rw-r--r--src/nvim/buffer.c1194
-rw-r--r--src/nvim/buffer.h3
-rw-r--r--src/nvim/drawscreen.c205
-rw-r--r--src/nvim/edit.c2
-rw-r--r--src/nvim/ex_cmds.c2
-rw-r--r--src/nvim/ex_getln.c258
-rw-r--r--src/nvim/hardcopy.c1
-rw-r--r--src/nvim/message.c13
-rw-r--r--src/nvim/normal.c2
-rw-r--r--src/nvim/ops.c22
-rw-r--r--src/nvim/option.c48
-rw-r--r--src/nvim/quickfix.c35
-rw-r--r--src/nvim/screen.c460
-rw-r--r--src/nvim/search.c2
-rw-r--r--src/nvim/statusline.c1807
-rw-r--r--src/nvim/statusline.h10
-rw-r--r--src/nvim/testdir/runtest.vim1
-rw-r--r--src/nvim/testdir/test_autocmd.vim53
-rw-r--r--src/nvim/testdir/test_edit.vim129
-rw-r--r--src/nvim/testdir/test_filechanged.vim9
-rw-r--r--src/nvim/testdir/test_global.vim5
-rw-r--r--src/nvim/testdir/test_normal.vim89
-rw-r--r--src/nvim/testdir/test_selectmode.vim150
-rw-r--r--src/nvim/testdir/test_visual.vim118
-rw-r--r--src/nvim/testdir/test_writefile.vim44
-rw-r--r--test/functional/legacy/filechanged_spec.lua9
-rw-r--r--test/functional/ui/inccommand_user_spec.lua347
-rw-r--r--test/unit/buffer_spec.lua3
33 files changed, 2885 insertions, 2223 deletions
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index 9cc17ffa34..42f3a5e432 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -1238,41 +1238,54 @@ vim.go :setglobal set -
vim.o *vim.o*
Get or set editor options, like |:set|. Invalid key is an error.
+
Example: >
vim.o.cmdheight = 4
print(vim.o.columns)
+ print(vim.o.foo) -- error: invalid key
<
vim.go *vim.go*
Get or set an |option|. Invalid key is an error.
- This is a wrapper around |nvim_set_option()| and |nvim_get_option()|.
+ This is a wrapper around |nvim_set_option_value()| and
+ |nvim_get_option_value()|.
- NOTE: This is different than |vim.o| because this ONLY sets the global
+ NOTE: This is different from |vim.o| because this ONLY sets the global
option, which generally produces confusing behavior for options with
|global-local| values.
Example: >
vim.go.cmdheight = 4
+ print(vim.go.columns)
+ print(vim.go.bar) -- error: invalid key
<
-vim.bo *vim.bo*
- Get or set buffer-scoped |local-options|. Invalid key is an error.
+vim.bo[{bufnr}] *vim.bo*
+ Get or set buffer-scoped |local-options| for the buffer with number {bufnr}.
+ If [{bufnr}] is omitted, use the current buffer. Invalid {bufnr} or key is
+ an error.
- This is a wrapper around |nvim_buf_set_option()| and
- |nvim_buf_get_option()|.
+ This is a wrapper around |nvim_set_option_value()| and
+ |nvim_get_option_value()| with `opts = {scope = local, buf = bufnr}` .
Example: >
- vim.bo.buflisted = true
+ local bufnr = vim.api.nvim_get_current_buf()
+ vim.bo[bufnr].buflisted = true -- same as vim.bo.buflisted = true
print(vim.bo.comments)
+ print(vim.bo.baz) -- error: invalid key
<
-vim.wo *vim.wo*
- Get or set window-scoped |local-options|. Invalid key is an error.
+vim.wo[{winid}] *vim.wo*
+ Get or set window-scoped |local-options| for the window with handle {winid}.
+ If [{winid}] is omitted, use the current window. Invalid {winid} or key
+ is an error.
- This is a wrapper around |nvim_win_set_option()| and
- |nvim_win_get_option()|.
+ This is a wrapper around |nvim_set_option_value()| and
+ |nvim_get_option_value()| with `opts = {scope = local, win = winid}` .
Example: >
- vim.wo.cursorcolumn = true
+ local winid = vim.api.nvim_get_current_win()
+ vim.wo[winid].number = true -- same as vim.wo.number = true
print(vim.wo.foldmarker)
+ print(vim.wo.quux) -- error: invalid key
<
==============================================================================
Lua module: vim *lua-vim*
diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt
index 00e5cc6e27..ca1ddaabd4 100644
--- a/runtime/doc/map.txt
+++ b/runtime/doc/map.txt
@@ -905,6 +905,17 @@ or `unnamedplus`.
The `mode()` function will return the state as it will be after applying the
operator.
+Here is an example for using a lambda function to create a normal-mode
+operator to add quotes around text in the current line: >
+
+ nnoremap <F4> <Cmd>let &opfunc='{t ->
+ \ getline(".")
+ \ ->split("\\zs")
+ \ ->insert("\"", col("'']"))
+ \ ->insert("\"", col("''[") - 1)
+ \ ->join("")
+ \ ->setline(".")}'<CR>g@
+
==============================================================================
2. Abbreviations *abbreviations* *Abbreviations*
@@ -1466,10 +1477,10 @@ results (for "inccommand=split", or nil for "inccommand=nosplit").
Your command preview routine must implement this protocol:
-1. Modify the current buffer as required for the preview (see
+1. Modify the target buffers as required for the preview (see
|nvim_buf_set_text()| and |nvim_buf_set_lines()|).
2. If preview buffer is provided, add necessary text to the preview buffer.
-3. Add required highlights to the current buffer. If preview buffer is
+3. Add required highlights to the target buffers. If preview buffer is
provided, add required highlights to the preview buffer as well. All
highlights must be added to the preview namespace which is provided as an
argument to the preview callback (see |nvim_buf_add_highlight()| and
@@ -1480,8 +1491,8 @@ Your command preview routine must implement this protocol:
2: Preview is shown and preview window is opened (if "inccommand=split").
For "inccommand=nosplit" this is the same as 1.
-After preview ends, Nvim discards all changes to the buffer and all highlights
-in the preview namespace.
+After preview ends, Nvim discards all changes to all buffers made during the
+preview and clears all highlights in the preview namespace.
Here's an example of a command to trim trailing whitespace from lines that
supports incremental command preview:
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index a1f2eac5ed..27aa06e18b 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -312,6 +312,17 @@ Note: In the future more global options can be made |global-local|. Using
":setlocal" on a global option might work differently then.
+ *option-value-function*
+Some options ('completefunc', 'imactivatefunc', 'imstatusfunc', 'omnifunc',
+'operatorfunc', 'quickfixtextfunc' and 'tagfunc') are set to a function name
+or a function reference or a lambda function. Examples:
+>
+ set opfunc=MyOpFunc
+ set opfunc=function("MyOpFunc")
+ set opfunc=funcref("MyOpFunc")
+ set opfunc={t\ ->\ MyOpFunc(t)}
+<
+
Setting the filetype
:setf[iletype] [FALLBACK] {filetype} *:setf* *:setfiletype*
@@ -4402,7 +4413,9 @@ A jump table for the options with a short description can be found at |Q_op|.
'operatorfunc' 'opfunc' string (default: empty)
global
This option specifies a function to be called by the |g@| operator.
- See |:map-operator| for more info and an example.
+ See |:map-operator| for more info and an example. The value can be
+ the name of a function, a |lambda| or a |Funcref|. See
+ |option-value-function| for more information.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
@@ -4696,8 +4709,9 @@ A jump table for the options with a short description can be found at |Q_op|.
customize the information displayed in the quickfix or location window
for each entry in the corresponding quickfix or location list. See
|quickfix-window-function| for an explanation of how to write the
- function and an example. The value can be the name of a function or a
- lambda.
+ function and an example. The value can be the name of a function, a
+ |lambda| or a |Funcref|. See |option-value-function| for more
+ information.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index 872106b20d..99c98764dd 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -2489,7 +2489,15 @@ function M.match(args)
-- Finally, check file contents
if contents or bufnr then
- contents = contents or M.getlines(bufnr)
+ if contents == nil then
+ if api.nvim_buf_line_count(bufnr) > 101 then
+ -- only need first 100 and last line for current checks
+ contents = M.getlines(bufnr, 1, 100)
+ contents[#contents + 1] = M.getlines(bufnr, -1)
+ else
+ contents = M.getlines(bufnr)
+ end
+ end
-- If name is nil, catch any errors from the contents filetype detection function.
-- If the function tries to use the filename that is nil then it will fail,
-- but this enables checks which do not need a filename to still work.
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index e4dc219e9a..a9787787d1 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -57,6 +57,7 @@
#include "nvim/popupmenu.h"
#include "nvim/runtime.h"
#include "nvim/state.h"
+#include "nvim/statusline.h"
#include "nvim/types.h"
#include "nvim/ui.h"
#include "nvim/vim.h"
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index f9bce2476f..88b7cfc8ee 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -76,6 +76,7 @@
#include "nvim/runtime.h"
#include "nvim/sign.h"
#include "nvim/spell.h"
+#include "nvim/statusline.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/ui.h"
@@ -89,11 +90,6 @@
# include "buffer.c.generated.h"
#endif
-// Determines how deeply nested %{} blocks will be evaluated in statusline.
-#define MAX_STL_EVAL_DEPTH 100
-
-static char *msg_loclist = N_("[Location List]");
-static char *msg_qflist = N_("[Quickfix List]");
static char *e_auabort = N_("E855: Autocommands caused command to abort");
static char *e_buflocked = N_("E937: Attempt to delete a buffer that is in use");
@@ -3359,1192 +3355,6 @@ void free_titles(void)
#endif
-/// Enumeration specifying the valid numeric bases that can
-/// be used when printing numbers in the status line.
-typedef enum {
- kNumBaseDecimal = 10,
- kNumBaseHexadecimal = 16,
-} NumberBase;
-
-/// Build a string from the status line items in "fmt".
-/// Return length of string in screen cells.
-///
-/// Normally works for window "wp", except when working for 'tabline' then it
-/// is "curwin".
-///
-/// Items are drawn interspersed with the text that surrounds it
-/// Specials: %-<wid>(xxx%) => group, %= => separation marker, %< => truncation
-/// Item: %-<minwid>.<maxwid><itemch> All but <itemch> are optional
-///
-/// If maxwidth is not zero, the string will be filled at any middle marker
-/// or truncated if too long, fillchar is used for all whitespace.
-///
-/// @param wp The window to build a statusline for
-/// @param out The output buffer to write the statusline to
-/// Note: This should not be NameBuff
-/// @param outlen The length of the output buffer
-/// @param fmt The statusline format string
-/// @param use_sandbox Use a sandboxed environment when evaluating fmt
-/// @param fillchar Character to use when filling empty space in the statusline
-/// @param maxwidth The maximum width to make the statusline
-/// @param hltab HL attributes (can be NULL)
-/// @param tabtab Tab clicks definition (can be NULL).
-///
-/// @return The final width of the statusline
-int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_sandbox, int fillchar,
- int maxwidth, stl_hlrec_t **hltab, StlClickRecord **tabtab)
-{
- static size_t stl_items_len = 20; // Initial value, grows as needed.
- static stl_item_t *stl_items = NULL;
- static int *stl_groupitems = NULL;
- static stl_hlrec_t *stl_hltab = NULL;
- static StlClickRecord *stl_tabtab = NULL;
- static int *stl_separator_locations = NULL;
-
-#define TMPLEN 70
- char buf_tmp[TMPLEN];
- char win_tmp[TMPLEN];
- char *usefmt = fmt;
- const int save_must_redraw = must_redraw;
- const int save_redr_type = curwin->w_redr_type;
-
- if (stl_items == NULL) {
- stl_items = xmalloc(sizeof(stl_item_t) * stl_items_len);
- stl_groupitems = xmalloc(sizeof(int) * stl_items_len);
-
- // Allocate one more, because the last element is used to indicate the
- // end of the list.
- stl_hltab = xmalloc(sizeof(stl_hlrec_t) * (stl_items_len + 1));
- stl_tabtab = xmalloc(sizeof(StlClickRecord) * (stl_items_len + 1));
-
- stl_separator_locations = xmalloc(sizeof(int) * stl_items_len);
- }
-
- // When the format starts with "%!" then evaluate it as an expression and
- // use the result as the actual format string.
- if (fmt[0] == '%' && fmt[1] == '!') {
- typval_T tv = {
- .v_type = VAR_NUMBER,
- .vval.v_number = wp->handle,
- };
- set_var(S_LEN("g:statusline_winid"), &tv, false);
-
- usefmt = eval_to_string_safe(fmt + 2, NULL, use_sandbox);
- if (usefmt == NULL) {
- usefmt = fmt;
- }
-
- do_unlet(S_LEN("g:statusline_winid"), true);
- }
-
- if (fillchar == 0) {
- fillchar = ' ';
- }
-
- // The cursor in windows other than the current one isn't always
- // up-to-date, esp. because of autocommands and timers.
- linenr_T lnum = wp->w_cursor.lnum;
- if (lnum > wp->w_buffer->b_ml.ml_line_count) {
- lnum = wp->w_buffer->b_ml.ml_line_count;
- wp->w_cursor.lnum = lnum;
- }
-
- // Get line & check if empty (cursorpos will show "0-1").
- const char *line_ptr = (char *)ml_get_buf(wp->w_buffer, lnum, false);
- bool empty_line = (*line_ptr == NUL);
-
- // Get the byte value now, in case we need it below. This is more
- // efficient than making a copy of the line.
- int byteval;
- const size_t len = STRLEN(line_ptr);
- if (wp->w_cursor.col > (colnr_T)len) {
- // Line may have changed since checking the cursor column, or the lnum
- // was adjusted above.
- wp->w_cursor.col = (colnr_T)len;
- wp->w_cursor.coladd = 0;
- byteval = 0;
- } else {
- byteval = utf_ptr2char(line_ptr + wp->w_cursor.col);
- }
-
- int groupdepth = 0;
- int evaldepth = 0;
-
- int curitem = 0;
- bool prevchar_isflag = true;
- bool prevchar_isitem = false;
-
- // out_p is the current position in the output buffer
- char *out_p = out;
-
- // out_end_p is the last valid character in the output buffer
- // Note: The null termination character must occur here or earlier,
- // so any user-visible characters must occur before here.
- char *out_end_p = (out + outlen) - 1;
-
- // Proceed character by character through the statusline format string
- // fmt_p is the current position in the input buffer
- for (char *fmt_p = usefmt; *fmt_p != NUL;) {
- if (curitem == (int)stl_items_len) {
- size_t new_len = stl_items_len * 3 / 2;
-
- stl_items = xrealloc(stl_items, sizeof(stl_item_t) * new_len);
- stl_groupitems = xrealloc(stl_groupitems, sizeof(int) * new_len);
- stl_hltab = xrealloc(stl_hltab, sizeof(stl_hlrec_t) * (new_len + 1));
- stl_tabtab = xrealloc(stl_tabtab, sizeof(StlClickRecord) * (new_len + 1));
- stl_separator_locations =
- xrealloc(stl_separator_locations, sizeof(int) * new_len);
-
- stl_items_len = new_len;
- }
-
- if (*fmt_p != '%') {
- prevchar_isflag = prevchar_isitem = false;
- }
-
- // Copy the formatting verbatim until we reach the end of the string
- // or find a formatting item (denoted by `%`)
- // or run out of room in our output buffer.
- while (*fmt_p != NUL && *fmt_p != '%' && out_p < out_end_p) {
- *out_p++ = *fmt_p++;
- }
-
- // If we have processed the entire format string or run out of
- // room in our output buffer, exit the loop.
- if (*fmt_p == NUL || out_p >= out_end_p) {
- break;
- }
-
- // The rest of this loop will handle a single `%` item.
- // Note: We increment here to skip over the `%` character we are currently
- // on so we can process the item's contents.
- fmt_p++;
-
- // Ignore `%` at the end of the format string
- if (*fmt_p == NUL) {
- break;
- }
-
- // Two `%` in a row is the escape sequence to print a
- // single `%` in the output buffer.
- if (*fmt_p == '%') {
- *out_p++ = *fmt_p++;
- prevchar_isflag = prevchar_isitem = false;
- continue;
- }
-
- // STL_SEPARATE: Separation place between left and right aligned items.
- if (*fmt_p == STL_SEPARATE) {
- fmt_p++;
- // Ignored when we are inside of a grouping
- if (groupdepth > 0) {
- continue;
- }
- stl_items[curitem].type = Separate;
- stl_items[curitem++].start = out_p;
- continue;
- }
-
- // STL_TRUNCMARK: Where to begin truncating if the statusline is too long.
- if (*fmt_p == STL_TRUNCMARK) {
- fmt_p++;
- stl_items[curitem].type = Trunc;
- stl_items[curitem++].start = out_p;
- continue;
- }
-
- // The end of a grouping
- if (*fmt_p == ')') {
- fmt_p++;
- // Ignore if we are not actually inside a group currently
- if (groupdepth < 1) {
- continue;
- }
- groupdepth--;
-
- // Determine how long the group is.
- // Note: We set the current output position to null
- // so `vim_strsize` will work.
- char *t = stl_items[stl_groupitems[groupdepth]].start;
- *out_p = NUL;
- long group_len = vim_strsize(t);
-
- // If the group contained internal items
- // and the group did not have a minimum width,
- // and if there were no normal items in the group,
- // move the output pointer back to where the group started.
- // Note: This erases any non-item characters that were in the group.
- // Otherwise there would be no reason to do this step.
- if (curitem > stl_groupitems[groupdepth] + 1
- && stl_items[stl_groupitems[groupdepth]].minwid == 0) {
- // remove group if all items are empty and highlight group
- // doesn't change
- int group_start_userhl = 0;
- int group_end_userhl = 0;
- int n;
- for (n = stl_groupitems[groupdepth] - 1; n >= 0; n--) {
- if (stl_items[n].type == Highlight) {
- group_start_userhl = group_end_userhl = stl_items[n].minwid;
- break;
- }
- }
- for (n = stl_groupitems[groupdepth] + 1; n < curitem; n++) {
- if (stl_items[n].type == Normal) {
- break;
- }
- if (stl_items[n].type == Highlight) {
- group_end_userhl = stl_items[n].minwid;
- }
- }
- if (n == curitem && group_start_userhl == group_end_userhl) {
- // empty group
- out_p = t;
- group_len = 0;
- for (n = stl_groupitems[groupdepth] + 1; n < curitem; n++) {
- // do not use the highlighting from the removed group
- if (stl_items[n].type == Highlight) {
- stl_items[n].type = Empty;
- }
- // adjust the start position of TabPage to the next
- // item position
- if (stl_items[n].type == TabPage) {
- stl_items[n].start = out_p;
- }
- }
- }
- }
-
- // If the group is longer than it is allowed to be
- // truncate by removing bytes from the start of the group text.
- if (group_len > stl_items[stl_groupitems[groupdepth]].maxwid) {
- // { Determine the number of bytes to remove
-
- // Find the first character that should be included.
- long n = 0;
- while (group_len >= stl_items[stl_groupitems[groupdepth]].maxwid) {
- group_len -= ptr2cells(t + n);
- n += utfc_ptr2len(t + n);
- }
- // }
-
- // Prepend the `<` to indicate that the output was truncated.
- *t = '<';
-
- // { Move the truncated output
- memmove(t + 1, t + n, (size_t)(out_p - (t + n)));
- out_p = out_p - n + 1;
- // Fill up space left over by half a double-wide char.
- while (++group_len < stl_items[stl_groupitems[groupdepth]].minwid) {
- MB_CHAR2BYTES(fillchar, out_p);
- }
- // }
-
- // correct the start of the items for the truncation
- for (int idx = stl_groupitems[groupdepth] + 1; idx < curitem; idx++) {
- // Shift everything back by the number of removed bytes
- // Minus one for the leading '<' added above.
- stl_items[idx].start -= n - 1;
-
- // If the item was partially or completely truncated, set its
- // start to the start of the group
- if (stl_items[idx].start < t) {
- stl_items[idx].start = t;
- }
- }
- // If the group is shorter than the minimum width, add padding characters.
- } else if (abs(stl_items[stl_groupitems[groupdepth]].minwid) > group_len) {
- long min_group_width = stl_items[stl_groupitems[groupdepth]].minwid;
- // If the group is left-aligned, add characters to the right.
- if (min_group_width < 0) {
- min_group_width = 0 - min_group_width;
- while (group_len++ < min_group_width && out_p < out_end_p) {
- MB_CHAR2BYTES(fillchar, out_p);
- }
- // If the group is right-aligned, shift everything to the right and
- // prepend with filler characters.
- } else {
- // { Move the group to the right
- group_len = (min_group_width - group_len) * utf_char2len(fillchar);
- memmove(t + group_len, t, (size_t)(out_p - t));
- if (out_p + group_len >= (out_end_p + 1)) {
- group_len = (long)(out_end_p - out_p);
- }
- out_p += group_len;
- // }
-
- // Adjust item start positions
- for (int n = stl_groupitems[groupdepth] + 1; n < curitem; n++) {
- stl_items[n].start += group_len;
- }
-
- // Prepend the fill characters
- for (; group_len > 0; group_len--) {
- MB_CHAR2BYTES(fillchar, t);
- }
- }
- }
- continue;
- }
- int minwid = 0;
- int maxwid = 9999;
- bool left_align = false;
-
- // Denotes that numbers should be left-padded with zeros
- bool zeropad = (*fmt_p == '0');
- if (zeropad) {
- fmt_p++;
- }
-
- // Denotes that the item should be left-aligned.
- // This is tracked by using a negative length.
- if (*fmt_p == '-') {
- fmt_p++;
- left_align = true;
- }
-
- // The first digit group is the item's min width
- if (ascii_isdigit(*fmt_p)) {
- minwid = getdigits_int(&fmt_p, false, 0);
- }
-
- // User highlight groups override the min width field
- // to denote the styling to use.
- if (*fmt_p == STL_USER_HL) {
- stl_items[curitem].type = Highlight;
- stl_items[curitem].start = out_p;
- stl_items[curitem].minwid = minwid > 9 ? 1 : minwid;
- fmt_p++;
- curitem++;
- continue;
- }
-
- // TABPAGE pairs are used to denote a region that when clicked will
- // either switch to or close a tab.
- //
- // Ex: tabline=%0Ttab\ zero%X
- // This tabline has a TABPAGENR item with minwid `0`,
- // which is then closed with a TABCLOSENR item.
- // Clicking on this region with mouse enabled will switch to tab 0.
- // Setting the minwid to a different value will switch
- // to that tab, if it exists
- //
- // Ex: tabline=%1Xtab\ one%X
- // This tabline has a TABCLOSENR item with minwid `1`,
- // which is then closed with a TABCLOSENR item.
- // Clicking on this region with mouse enabled will close tab 0.
- // This is determined by the following formula:
- // tab to close = (1 - minwid)
- // This is because for TABPAGENR we use `minwid` = `tab number`.
- // For TABCLOSENR we store the tab number as a negative value.
- // Because 0 is a valid TABPAGENR value, we have to
- // start our numbering at `-1`.
- // So, `-1` corresponds to us wanting to close tab `0`
- //
- // Note: These options are only valid when creating a tabline.
- if (*fmt_p == STL_TABPAGENR || *fmt_p == STL_TABCLOSENR) {
- if (*fmt_p == STL_TABCLOSENR) {
- if (minwid == 0) {
- // %X ends the close label, go back to the previous tab label nr.
- for (long n = curitem - 1; n >= 0; n--) {
- if (stl_items[n].type == TabPage && stl_items[n].minwid >= 0) {
- minwid = stl_items[n].minwid;
- break;
- }
- }
- } else {
- // close nrs are stored as negative values
- minwid = -minwid;
- }
- }
- stl_items[curitem].type = TabPage;
- stl_items[curitem].start = out_p;
- stl_items[curitem].minwid = minwid;
- fmt_p++;
- curitem++;
- continue;
- }
-
- if (*fmt_p == STL_CLICK_FUNC) {
- fmt_p++;
- char *t = fmt_p;
- while (*fmt_p != STL_CLICK_FUNC && *fmt_p) {
- fmt_p++;
- }
- if (*fmt_p != STL_CLICK_FUNC) {
- break;
- }
- stl_items[curitem].type = ClickFunc;
- stl_items[curitem].start = out_p;
- stl_items[curitem].cmd = xmemdupz(t, (size_t)(fmt_p - t));
- stl_items[curitem].minwid = minwid;
- fmt_p++;
- curitem++;
- continue;
- }
-
- // Denotes the end of the minwid
- // the maxwid may follow immediately after
- if (*fmt_p == '.') {
- fmt_p++;
- if (ascii_isdigit(*fmt_p)) {
- maxwid = getdigits_int(&fmt_p, false, 50);
- }
- }
-
- // Bound the minimum width at 50.
- // Make the number negative to denote left alignment of the item
- minwid = (minwid > 50 ? 50 : minwid) * (left_align ? -1 : 1);
-
- // Denotes the start of a new group
- if (*fmt_p == '(') {
- stl_groupitems[groupdepth++] = curitem;
- stl_items[curitem].type = Group;
- stl_items[curitem].start = out_p;
- stl_items[curitem].minwid = minwid;
- stl_items[curitem].maxwid = maxwid;
- fmt_p++;
- curitem++;
- continue;
- }
-
- // Denotes end of expanded %{} block
- if (*fmt_p == '}' && evaldepth > 0) {
- fmt_p++;
- evaldepth--;
- continue;
- }
-
- // An invalid item was specified.
- // Continue processing on the next character of the format string.
- if (vim_strchr(STL_ALL, *fmt_p) == NULL) {
- fmt_p++;
- continue;
- }
-
- // The status line item type
- char opt = *fmt_p++;
-
- // OK - now for the real work
- NumberBase base = kNumBaseDecimal;
- bool itemisflag = false;
- bool fillable = true;
- long num = -1;
- char *str = NULL;
- switch (opt) {
- case STL_FILEPATH:
- case STL_FULLPATH:
- case STL_FILENAME:
- // Set fillable to false so that ' ' in the filename will not
- // get replaced with the fillchar
- fillable = false;
- if (buf_spname(wp->w_buffer) != NULL) {
- STRLCPY(NameBuff, buf_spname(wp->w_buffer), MAXPATHL);
- } else {
- char *t = (opt == STL_FULLPATH) ? wp->w_buffer->b_ffname
- : wp->w_buffer->b_fname;
- home_replace(wp->w_buffer, t, (char *)NameBuff, MAXPATHL, true);
- }
- trans_characters((char *)NameBuff, MAXPATHL);
- if (opt != STL_FILENAME) {
- str = (char *)NameBuff;
- } else {
- str = path_tail((char *)NameBuff);
- }
- break;
- case STL_VIM_EXPR: // '{'
- {
- char *block_start = fmt_p - 1;
- int reevaluate = (*fmt_p == '%');
- itemisflag = true;
-
- if (reevaluate) {
- fmt_p++;
- }
-
- // Attempt to copy the expression to evaluate into
- // the output buffer as a null-terminated string.
- char *t = out_p;
- while ((*fmt_p != '}' || (reevaluate && fmt_p[-1] != '%'))
- && *fmt_p != NUL && out_p < out_end_p) {
- *out_p++ = *fmt_p++;
- }
- if (*fmt_p != '}') { // missing '}' or out of space
- break;
- }
- fmt_p++;
- if (reevaluate) {
- out_p[-1] = 0; // remove the % at the end of %{% expr %}
- } else {
- *out_p = 0;
- }
-
- // Move our position in the output buffer
- // to the beginning of the expression
- out_p = t;
-
- // { Evaluate the expression
-
- // Store the current buffer number as a string variable
- vim_snprintf(buf_tmp, sizeof(buf_tmp), "%d", curbuf->b_fnum);
- set_internal_string_var("g:actual_curbuf", buf_tmp);
- vim_snprintf((char *)win_tmp, sizeof(win_tmp), "%d", curwin->handle);
- set_internal_string_var("g:actual_curwin", (char *)win_tmp);
-
- buf_T *const save_curbuf = curbuf;
- win_T *const save_curwin = curwin;
- const int save_VIsual_active = VIsual_active;
- curwin = wp;
- curbuf = wp->w_buffer;
- // Visual mode is only valid in the current window.
- if (curwin != save_curwin) {
- VIsual_active = false;
- }
-
- // Note: The result stored in `t` is unused.
- str = eval_to_string_safe(out_p, &t, use_sandbox);
-
- curwin = save_curwin;
- curbuf = save_curbuf;
- VIsual_active = save_VIsual_active;
-
- // Remove the variable we just stored
- do_unlet(S_LEN("g:actual_curbuf"), true);
- do_unlet(S_LEN("g:actual_curwin"), true);
-
- // }
-
- // Check if the evaluated result is a number.
- // If so, convert the number to an int and free the string.
- if (str != NULL && *str != 0) {
- if (*skipdigits(str) == NUL) {
- num = atoi(str);
- XFREE_CLEAR(str);
- itemisflag = false;
- }
- }
-
- // If the output of the expression needs to be evaluated
- // replace the %{} block with the result of evaluation
- if (reevaluate && str != NULL && *str != 0
- && strchr((const char *)str, '%') != NULL
- && evaldepth < MAX_STL_EVAL_DEPTH) {
- size_t parsed_usefmt = (size_t)(block_start - usefmt);
- size_t str_length = STRLEN(str);
- size_t fmt_length = STRLEN(fmt_p);
- size_t new_fmt_len = parsed_usefmt + str_length + fmt_length + 3;
- char *new_fmt = xmalloc(new_fmt_len * sizeof(char));
- char *new_fmt_p = new_fmt;
-
- new_fmt_p = (char *)memcpy(new_fmt_p, usefmt, parsed_usefmt) + parsed_usefmt;
- new_fmt_p = (char *)memcpy(new_fmt_p, str, str_length) + str_length;
- new_fmt_p = (char *)memcpy(new_fmt_p, "%}", 2) + 2;
- new_fmt_p = (char *)memcpy(new_fmt_p, fmt_p, fmt_length) + fmt_length;
- *new_fmt_p = 0;
- new_fmt_p = NULL;
-
- if (usefmt != fmt) {
- xfree(usefmt);
- }
- XFREE_CLEAR(str);
- usefmt = new_fmt;
- fmt_p = usefmt + parsed_usefmt;
- evaldepth++;
- continue;
- }
- break;
- }
-
- case STL_LINE:
- num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY)
- ? 0L : (long)(wp->w_cursor.lnum);
- break;
-
- case STL_NUMLINES:
- num = wp->w_buffer->b_ml.ml_line_count;
- break;
-
- case STL_COLUMN:
- num = (State & MODE_INSERT) == 0 && empty_line ? 0 : (int)wp->w_cursor.col + 1;
- break;
-
- case STL_VIRTCOL:
- case STL_VIRTCOL_ALT: {
- colnr_T virtcol = wp->w_virtcol + 1;
- // Don't display %V if it's the same as %c.
- if (opt == STL_VIRTCOL_ALT
- && (virtcol == (colnr_T)((State & MODE_INSERT) == 0 && empty_line
- ? 0 : (int)wp->w_cursor.col + 1))) {
- break;
- }
- num = (long)virtcol;
- break;
- }
-
- case STL_PERCENTAGE:
- num = (int)(((long)wp->w_cursor.lnum * 100L) /
- (long)wp->w_buffer->b_ml.ml_line_count);
- break;
-
- case STL_ALTPERCENT:
- // Store the position percentage in our temporary buffer.
- // Note: We cannot store the value in `num` because
- // `get_rel_pos` can return a named position. Ex: "Top"
- get_rel_pos(wp, buf_tmp, TMPLEN);
- str = buf_tmp;
- break;
-
- case STL_ARGLISTSTAT:
- fillable = false;
-
- // Note: This is important because `append_arg_number` starts appending
- // at the end of the null-terminated string.
- // Setting the first byte to null means it will place the argument
- // number string at the beginning of the buffer.
- buf_tmp[0] = 0;
-
- // Note: The call will only return true if it actually
- // appended data to the `buf_tmp` buffer.
- if (append_arg_number(wp, buf_tmp, (int)sizeof(buf_tmp), false)) {
- str = buf_tmp;
- }
- break;
-
- case STL_KEYMAP:
- fillable = false;
- if (get_keymap_str(wp, "<%s>", buf_tmp, TMPLEN)) {
- str = buf_tmp;
- }
- break;
- case STL_PAGENUM:
- num = printer_page_num;
- break;
-
- case STL_BUFNO:
- num = wp->w_buffer->b_fnum;
- break;
-
- case STL_OFFSET_X:
- base = kNumBaseHexadecimal;
- FALLTHROUGH;
- case STL_OFFSET: {
- long l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL,
- false);
- num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) || l < 0 ?
- 0L : l + 1 + ((State & MODE_INSERT) == 0 && empty_line ?
- 0 : (int)wp->w_cursor.col);
- break;
- }
- case STL_BYTEVAL_X:
- base = kNumBaseHexadecimal;
- FALLTHROUGH;
- case STL_BYTEVAL:
- num = byteval;
- if (num == NL) {
- num = 0;
- } else if (num == CAR && get_fileformat(wp->w_buffer) == EOL_MAC) {
- num = NL;
- }
- break;
-
- case STL_ROFLAG:
- case STL_ROFLAG_ALT:
- itemisflag = true;
- if (wp->w_buffer->b_p_ro) {
- str = (opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]");
- }
- break;
-
- case STL_HELPFLAG:
- case STL_HELPFLAG_ALT:
- itemisflag = true;
- if (wp->w_buffer->b_help) {
- str = (opt == STL_HELPFLAG_ALT) ? ",HLP" : _("[Help]");
- }
- break;
-
- case STL_FILETYPE:
- // Copy the filetype if it is not null and the formatted string will fit
- // in the temporary buffer
- // (including the brackets and null terminating character)
- if (*wp->w_buffer->b_p_ft != NUL
- && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 3) {
- vim_snprintf(buf_tmp, sizeof(buf_tmp), "[%s]",
- wp->w_buffer->b_p_ft);
- str = buf_tmp;
- }
- break;
-
- case STL_FILETYPE_ALT:
- itemisflag = true;
- // Copy the filetype if it is not null and the formatted string will fit
- // in the temporary buffer
- // (including the comma and null terminating character)
- if (*wp->w_buffer->b_p_ft != NUL
- && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 2) {
- vim_snprintf(buf_tmp, sizeof(buf_tmp), ",%s", wp->w_buffer->b_p_ft);
- // Uppercase the file extension
- for (char *t = buf_tmp; *t != 0; t++) {
- *t = (char)TOUPPER_LOC(*t);
- }
- str = buf_tmp;
- }
- break;
- case STL_PREVIEWFLAG:
- case STL_PREVIEWFLAG_ALT:
- itemisflag = true;
- if (wp->w_p_pvw) {
- str = (opt == STL_PREVIEWFLAG_ALT) ? ",PRV" : _("[Preview]");
- }
- break;
-
- case STL_QUICKFIX:
- if (bt_quickfix(wp->w_buffer)) {
- str = wp->w_llist_ref ? _(msg_loclist) : _(msg_qflist);
- }
- break;
-
- case STL_MODIFIED:
- case STL_MODIFIED_ALT:
- itemisflag = true;
- switch ((opt == STL_MODIFIED_ALT)
- + bufIsChanged(wp->w_buffer) * 2
- + (!MODIFIABLE(wp->w_buffer)) * 4) {
- case 2:
- str = "[+]"; break;
- case 3:
- str = ",+"; break;
- case 4:
- str = "[-]"; break;
- case 5:
- str = ",-"; break;
- case 6:
- str = "[+-]"; break;
- case 7:
- str = ",+-"; break;
- }
- break;
-
- case STL_HIGHLIGHT: {
- // { The name of the highlight is surrounded by `#`
- char *t = fmt_p;
- while (*fmt_p != '#' && *fmt_p != NUL) {
- fmt_p++;
- }
- // }
-
- // Create a highlight item based on the name
- if (*fmt_p == '#') {
- stl_items[curitem].type = Highlight;
- stl_items[curitem].start = out_p;
- stl_items[curitem].minwid = -syn_name2id_len(t, (size_t)(fmt_p - t));
- curitem++;
- fmt_p++;
- }
- continue;
- }
- }
-
- // If we made it this far, the item is normal and starts at
- // our current position in the output buffer.
- // Non-normal items would have `continued`.
- stl_items[curitem].start = out_p;
- stl_items[curitem].type = Normal;
-
- // Copy the item string into the output buffer
- if (str != NULL && *str) {
- // { Skip the leading `,` or ` ` if the item is a flag
- // and the proper conditions are met
- char *t = str;
- if (itemisflag) {
- if ((t[0] && t[1])
- && ((!prevchar_isitem && *t == ',')
- || (prevchar_isflag && *t == ' '))) {
- t++;
- }
- prevchar_isflag = true;
- }
- // }
-
- long l = vim_strsize(t);
-
- // If this item is non-empty, record that the last thing
- // we put in the output buffer was an item
- if (l > 0) {
- prevchar_isitem = true;
- }
-
- // If the item is too wide, truncate it from the beginning
- if (l > maxwid) {
- while (l >= maxwid) {
- l -= ptr2cells(t);
- t += utfc_ptr2len(t);
- }
-
- // Early out if there isn't enough room for the truncation marker
- if (out_p >= out_end_p) {
- break;
- }
-
- // Add the truncation marker
- *out_p++ = '<';
- }
-
- // If the item is right aligned and not wide enough,
- // pad with fill characters.
- if (minwid > 0) {
- for (; l < minwid && out_p < out_end_p; l++) {
- // Don't put a "-" in front of a digit.
- if (l + 1 == minwid && fillchar == '-' && ascii_isdigit(*t)) {
- *out_p++ = ' ';
- } else {
- MB_CHAR2BYTES(fillchar, out_p);
- }
- }
- minwid = 0;
- } else {
- // Note: The negative value denotes a left aligned item.
- // Here we switch the minimum width back to a positive value.
- minwid *= -1;
- }
-
- // { Copy the string text into the output buffer
- for (; *t && out_p < out_end_p; t++) {
- // Change a space by fillchar, unless fillchar is '-' and a
- // digit follows.
- if (fillable && *t == ' '
- && (!ascii_isdigit(*(t + 1)) || fillchar != '-')) {
- MB_CHAR2BYTES(fillchar, out_p);
- } else {
- *out_p++ = *t;
- }
- }
- // }
-
- // For left-aligned items, fill any remaining space with the fillchar
- for (; l < minwid && out_p < out_end_p; l++) {
- MB_CHAR2BYTES(fillchar, out_p);
- }
-
- // Otherwise if the item is a number, copy that to the output buffer.
- } else if (num >= 0) {
- if (out_p + 20 > out_end_p) {
- break; // not sufficient space
- }
- prevchar_isitem = true;
-
- // { Build the formatting string
- char nstr[20];
- char *t = nstr;
- if (opt == STL_VIRTCOL_ALT) {
- *t++ = '-';
- minwid--;
- }
- *t++ = '%';
- if (zeropad) {
- *t++ = '0';
- }
-
- // Note: The `*` means we take the width as one of the arguments
- *t++ = '*';
- *t++ = base == kNumBaseHexadecimal ? 'X' : 'd';
- *t = 0;
- // }
-
- // { Determine how many characters the number will take up when printed
- // Note: We have to cast the base because the compiler uses
- // unsigned ints for the enum values.
- long num_chars = 1;
- for (long n = num; n >= (int)base; n /= (int)base) {
- num_chars++;
- }
-
- // VIRTCOL_ALT takes up an extra character because
- // of the `-` we added above.
- if (opt == STL_VIRTCOL_ALT) {
- num_chars++;
- }
- // }
-
- assert(out_end_p >= out_p);
- size_t remaining_buf_len = (size_t)(out_end_p - out_p) + 1;
-
- // If the number is going to take up too much room
- // Figure out the approximate number in "scientific" type notation.
- // Ex: 14532 with maxwid of 4 -> '14>3'
- if (num_chars > maxwid) {
- // Add two to the width because the power piece will take
- // two extra characters
- num_chars += 2;
-
- // How many extra characters there are
- long n = num_chars - maxwid;
-
- // { Reduce the number by base^n
- while (num_chars-- > maxwid) {
- num /= (long)base;
- }
- // }
-
- // { Add the format string for the exponent bit
- *t++ = '>';
- *t++ = '%';
- // Use the same base as the first number
- *t = t[-3];
- *++t = 0;
- // }
-
- vim_snprintf(out_p, remaining_buf_len, nstr, 0, num, n);
- } else {
- vim_snprintf(out_p, remaining_buf_len, nstr, minwid, num);
- }
-
- // Advance the output buffer position to the end of the
- // number we just printed
- out_p += STRLEN(out_p);
-
- // Otherwise, there was nothing to print so mark the item as empty
- } else {
- stl_items[curitem].type = Empty;
- }
-
- // Only free the string buffer if we allocated it.
- // Note: This is not needed if `str` is pointing at `tmp`
- if (opt == STL_VIM_EXPR) {
- XFREE_CLEAR(str);
- }
-
- if (num >= 0 || (!itemisflag && str && *str)) {
- prevchar_isflag = false; // Item not NULL, but not a flag
- }
-
- // Item processed, move to the next
- curitem++;
- }
-
- *out_p = NUL;
- int itemcnt = curitem;
-
- // Free the format buffer if we allocated it internally
- if (usefmt != fmt) {
- xfree(usefmt);
- }
-
- // We have now processed the entire statusline format string.
- // What follows is post-processing to handle alignment and highlighting.
-
- int width = vim_strsize(out);
- if (maxwidth > 0 && width > maxwidth) {
- // Result is too long, must truncate somewhere.
- int item_idx = 0;
- char *trunc_p;
-
- // If there are no items, truncate from beginning
- if (itemcnt == 0) {
- trunc_p = out;
-
- // Otherwise, look for the truncation item
- } else {
- // Default to truncating at the first item
- trunc_p = stl_items[0].start;
- item_idx = 0;
-
- for (int i = 0; i < itemcnt; i++) {
- if (stl_items[i].type == Trunc) {
- // Truncate at %< stl_items.
- trunc_p = stl_items[i].start;
- item_idx = i;
- break;
- }
- }
- }
-
- // If the truncation point we found is beyond the maximum
- // length of the string, truncate the end of the string.
- if (width - vim_strsize(trunc_p) >= maxwidth) {
- // Walk from the beginning of the
- // string to find the last character that will fit.
- trunc_p = out;
- width = 0;
- for (;;) {
- width += ptr2cells(trunc_p);
- if (width >= maxwidth) {
- break;
- }
-
- // Note: Only advance the pointer if the next
- // character will fit in the available output space
- trunc_p += utfc_ptr2len(trunc_p);
- }
-
- // Ignore any items in the statusline that occur after
- // the truncation point
- for (int i = 0; i < itemcnt; i++) {
- if (stl_items[i].start > trunc_p) {
- itemcnt = i;
- break;
- }
- }
-
- // Truncate the output
- *trunc_p++ = '>';
- *trunc_p = 0;
-
- // Truncate at the truncation point we found
- } else {
- // { Determine how many bytes to remove
- long trunc_len = 0;
- while (width >= maxwidth) {
- width -= ptr2cells(trunc_p + trunc_len);
- trunc_len += utfc_ptr2len(trunc_p + trunc_len);
- }
- // }
-
- // { Truncate the string
- char *trunc_end_p = trunc_p + trunc_len;
- STRMOVE(trunc_p + 1, trunc_end_p);
-
- // Put a `<` to mark where we truncated at
- *trunc_p = '<';
-
- if (width + 1 < maxwidth) {
- // Advance the pointer to the end of the string
- trunc_p = trunc_p + STRLEN(trunc_p);
- }
-
- // Fill up for half a double-wide character.
- while (++width < maxwidth) {
- MB_CHAR2BYTES(fillchar, trunc_p);
- *trunc_p = NUL;
- }
- // }
-
- // { Change the start point for items based on
- // their position relative to our truncation point
-
- // Note: The offset is one less than the truncation length because
- // the truncation marker `<` is not counted.
- long item_offset = trunc_len - 1;
-
- for (int i = item_idx; i < itemcnt; i++) {
- // Items starting at or after the end of the truncated section need
- // to be moved backwards.
- if (stl_items[i].start >= trunc_end_p) {
- stl_items[i].start -= item_offset;
- // Anything inside the truncated area is set to start
- // at the `<` truncation character.
- } else {
- stl_items[i].start = trunc_p;
- }
- }
- // }
- }
- width = maxwidth;
-
- // If there is room left in our statusline, and room left in our buffer,
- // add characters at the separate marker (if there is one) to
- // fill up the available space.
- } else if (width < maxwidth
- && STRLEN(out) + (size_t)(maxwidth - width) + 1 < outlen) {
- // Find how many separators there are, which we will use when
- // figuring out how many groups there are.
- int num_separators = 0;
- for (int i = 0; i < itemcnt; i++) {
- if (stl_items[i].type == Separate) {
- // Create an array of the start location for each
- // separator mark.
- stl_separator_locations[num_separators] = i;
- num_separators++;
- }
- }
-
- // If we have separated groups, then we deal with it now
- if (num_separators) {
- int standard_spaces = (maxwidth - width) / num_separators;
- int final_spaces = (maxwidth - width) -
- standard_spaces * (num_separators - 1);
-
- for (int i = 0; i < num_separators; i++) {
- int dislocation = (i == (num_separators - 1)) ? final_spaces : standard_spaces;
- dislocation *= utf_char2len(fillchar);
- char *start = stl_items[stl_separator_locations[i]].start;
- char *seploc = start + dislocation;
- STRMOVE(seploc, start);
- for (char *s = start; s < seploc;) {
- MB_CHAR2BYTES(fillchar, s);
- }
-
- for (int item_idx = stl_separator_locations[i] + 1;
- item_idx < itemcnt;
- item_idx++) {
- stl_items[item_idx].start += dislocation;
- }
- }
-
- width = maxwidth;
- }
- }
-
- // Store the info about highlighting.
- if (hltab != NULL) {
- *hltab = stl_hltab;
- stl_hlrec_t *sp = stl_hltab;
- for (long l = 0; l < itemcnt; l++) {
- if (stl_items[l].type == Highlight) {
- sp->start = stl_items[l].start;
- sp->userhl = stl_items[l].minwid;
- sp++;
- }
- }
- sp->start = NULL;
- sp->userhl = 0;
- }
-
- // Store the info about tab pages labels.
- if (tabtab != NULL) {
- *tabtab = stl_tabtab;
- StlClickRecord *cur_tab_rec = stl_tabtab;
- for (long l = 0; l < itemcnt; l++) {
- if (stl_items[l].type == TabPage) {
- cur_tab_rec->start = stl_items[l].start;
- if (stl_items[l].minwid == 0) {
- cur_tab_rec->def.type = kStlClickDisabled;
- cur_tab_rec->def.tabnr = 0;
- } else {
- int tabnr = stl_items[l].minwid;
- if (stl_items[l].minwid > 0) {
- cur_tab_rec->def.type = kStlClickTabSwitch;
- } else {
- cur_tab_rec->def.type = kStlClickTabClose;
- tabnr = -tabnr;
- }
- cur_tab_rec->def.tabnr = tabnr;
- }
- cur_tab_rec->def.func = NULL;
- cur_tab_rec++;
- } else if (stl_items[l].type == ClickFunc) {
- cur_tab_rec->start = stl_items[l].start;
- cur_tab_rec->def.type = kStlClickFuncRun;
- cur_tab_rec->def.tabnr = stl_items[l].minwid;
- cur_tab_rec->def.func = stl_items[l].cmd;
- cur_tab_rec++;
- }
- }
- cur_tab_rec->start = NULL;
- cur_tab_rec->def.type = kStlClickDisabled;
- cur_tab_rec->def.tabnr = 0;
- cur_tab_rec->def.func = NULL;
- }
-
- // When inside update_screen we do not want redrawing a statusline, ruler,
- // title, etc. to trigger another redraw, it may cause an endless loop.
- if (updating_screen) {
- must_redraw = save_must_redraw;
- curwin->w_redr_type = save_redr_type;
- }
-
- return width;
-}
-
/// Get relative cursor position in window into "buf[buflen]", in the form 99%,
/// using "Top", "Bot" or "All" when appropriate.
void get_rel_pos(win_T *wp, char *buf, int buflen)
@@ -4584,7 +3394,7 @@ void get_rel_pos(win_T *wp, char *buf, int buflen)
/// @param add_file if true, add "file" before the arg number
///
/// @return true if it was appended.
-static bool append_arg_number(win_T *wp, char *buf, int buflen, bool add_file)
+bool append_arg_number(win_T *wp, char *buf, int buflen, bool add_file)
FUNC_ATTR_NONNULL_ALL
{
// Nothing to do
diff --git a/src/nvim/buffer.h b/src/nvim/buffer.h
index 7627b6a596..d56a70dc7e 100644
--- a/src/nvim/buffer.h
+++ b/src/nvim/buffer.h
@@ -62,6 +62,9 @@ enum bfa_values {
BFA_IGNORE_ABORT = 8, // do not abort for aborting()
};
+EXTERN char *msg_loclist INIT(= N_("[Location List]"));
+EXTERN char *msg_qflist INIT(= N_("[Quickfix List]"));
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "buffer.h.generated.h"
#endif
diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c
index 8cd1bdddd8..41d7d22934 100644
--- a/src/nvim/drawscreen.c
+++ b/src/nvim/drawscreen.c
@@ -75,6 +75,7 @@
#include "nvim/popupmenu.h"
#include "nvim/profile.h"
#include "nvim/regexp.h"
+#include "nvim/statusline.h"
#include "nvim/syntax.h"
#include "nvim/ui_compositor.h"
#include "nvim/undo.h"
@@ -736,190 +737,10 @@ static void win_redr_border(win_T *wp)
}
}
-/// Redraw the status line of window `wp`.
-///
-/// If inversion is possible we use it. Else '=' characters are used.
-static void win_redr_status(win_T *wp)
-{
- int row;
- int col;
- char_u *p;
- int len;
- int fillchar;
- int attr;
- int width;
- int this_ru_col;
- bool is_stl_global = global_stl_height() > 0;
- static bool busy = false;
-
- // May get here recursively when 'statusline' (indirectly)
- // invokes ":redrawstatus". Simply ignore the call then.
- if (busy
- // Also ignore if wildmenu is showing.
- || (wild_menu_showing != 0 && !ui_has(kUIWildmenu))) {
- return;
- }
- busy = true;
-
- wp->w_redr_status = false;
- if (wp->w_status_height == 0 && !(is_stl_global && wp == curwin)) {
- // no status line, either global statusline is enabled or the window is a last window
- redraw_cmdline = true;
- } else if (!redrawing()) {
- // Don't redraw right now, do it later. Don't update status line when
- // popup menu is visible and may be drawn over it
- wp->w_redr_status = true;
- } else if (*p_stl != NUL || *wp->w_p_stl != NUL) {
- // redraw custom status line
- redraw_custom_statusline(wp);
- } else {
- fillchar = fillchar_status(&attr, wp);
- width = is_stl_global ? Columns : wp->w_width;
-
- get_trans_bufname(wp->w_buffer);
- p = NameBuff;
- len = (int)STRLEN(p);
-
- if (bt_help(wp->w_buffer)
- || wp->w_p_pvw
- || bufIsChanged(wp->w_buffer)
- || wp->w_buffer->b_p_ro) {
- *(p + len++) = ' ';
- }
- if (bt_help(wp->w_buffer)) {
- snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[Help]"));
- len += (int)STRLEN(p + len);
- }
- if (wp->w_p_pvw) {
- snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[Preview]"));
- len += (int)STRLEN(p + len);
- }
- if (bufIsChanged(wp->w_buffer)) {
- snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", "[+]");
- len += (int)STRLEN(p + len);
- }
- if (wp->w_buffer->b_p_ro) {
- snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[RO]"));
- // len += (int)STRLEN(p + len); // dead assignment
- }
-
- this_ru_col = ru_col - (Columns - width);
- if (this_ru_col < (width + 1) / 2) {
- this_ru_col = (width + 1) / 2;
- }
- if (this_ru_col <= 1) {
- p = (char_u *)"<"; // No room for file name!
- len = 1;
- } else {
- int clen = 0, i;
-
- // Count total number of display cells.
- clen = (int)mb_string2cells((char *)p);
-
- // Find first character that will fit.
- // Going from start to end is much faster for DBCS.
- for (i = 0; p[i] != NUL && clen >= this_ru_col - 1;
- i += utfc_ptr2len((char *)p + i)) {
- clen -= utf_ptr2cells((char *)p + i);
- }
- len = clen;
- if (i > 0) {
- p = p + i - 1;
- *p = '<';
- len++;
- }
- }
-
- row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp);
- col = is_stl_global ? 0 : wp->w_wincol;
- grid_puts(&default_grid, p, row, col, attr);
- grid_fill(&default_grid, row, row + 1, len + col,
- this_ru_col + col, fillchar, fillchar, attr);
-
- if (get_keymap_str(wp, "<%s>", (char *)NameBuff, MAXPATHL)
- && this_ru_col - len > (int)(STRLEN(NameBuff) + 1)) {
- grid_puts(&default_grid, NameBuff, row,
- (int)((size_t)this_ru_col - STRLEN(NameBuff) - 1), attr);
- }
-
- win_redr_ruler(wp, true);
- }
-
- // May need to draw the character below the vertical separator.
- if (wp->w_vsep_width != 0 && wp->w_status_height != 0 && redrawing()) {
- if (stl_connected(wp)) {
- fillchar = fillchar_status(&attr, wp);
- } else {
- fillchar = fillchar_vsep(wp, &attr);
- }
- grid_putchar(&default_grid, fillchar, W_ENDROW(wp), W_ENDCOL(wp), attr);
- }
- busy = false;
-}
-
-/// Redraw the status line according to 'statusline' and take care of any
-/// errors encountered.
-static void redraw_custom_statusline(win_T *wp)
-{
- static bool entered = false;
- int saved_did_emsg = did_emsg;
-
- // When called recursively return. This can happen when the statusline
- // contains an expression that triggers a redraw.
- if (entered) {
- return;
- }
- entered = true;
-
- did_emsg = false;
- win_redr_custom(wp, false, false);
- if (did_emsg) {
- // When there is an error disable the statusline, otherwise the
- // display is messed up with errors and a redraw triggers the problem
- // again and again.
- set_string_option_direct("statusline", -1, "",
- OPT_FREE | (*wp->w_p_stl != NUL
- ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR);
- }
- did_emsg |= saved_did_emsg;
- entered = false;
-}
-
-static void win_redr_winbar(win_T *wp)
-{
- static bool entered = false;
-
- // Return when called recursively. This can happen when the winbar contains an expression
- // that triggers a redraw.
- if (entered) {
- return;
- }
- entered = true;
-
- if (wp->w_winbar_height == 0 || !redrawing()) {
- // Do nothing.
- } else if (*p_wbr != NUL || *wp->w_p_wbr != NUL) {
- int saved_did_emsg = did_emsg;
-
- did_emsg = false;
- win_redr_custom(wp, true, false);
- if (did_emsg) {
- // When there is an error disable the winbar, otherwise the
- // display is messed up with errors and a redraw triggers the problem
- // again and again.
- set_string_option_direct("winbar", -1, "",
- OPT_FREE | (*wp->w_p_stl != NUL
- ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR);
- }
- did_emsg |= saved_did_emsg;
- }
- entered = false;
-}
-
-/// Show current status info in ruler and various other places
+/// Show current cursor info in ruler and various other places
///
/// @param always if false, only show ruler if position has changed.
-void showruler(bool always)
+void show_cursor_info(bool always)
{
if (!always && !redrawing()) {
return;
@@ -2303,6 +2124,26 @@ void redraw_statuslines(void)
}
}
+/// Redraw all status lines at the bottom of frame "frp".
+void win_redraw_last_status(const frame_T *frp)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ if (frp->fr_layout == FR_LEAF) {
+ frp->fr_win->w_redr_status = true;
+ } else if (frp->fr_layout == FR_ROW) {
+ FOR_ALL_FRAMES(frp, frp->fr_child) {
+ win_redraw_last_status(frp);
+ }
+ } else {
+ assert(frp->fr_layout == FR_COL);
+ frp = frp->fr_child;
+ while (frp->fr_next != NULL) {
+ frp = frp->fr_next;
+ }
+ win_redraw_last_status(frp);
+ }
+}
+
/// Changed something in the current window, at buffer line "lnum", that
/// requires that line and possibly other lines to be redrawn.
/// Used when entering/leaving Insert mode with the cursor on a folded line.
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 05abeee550..6583ac8584 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -1349,7 +1349,7 @@ void ins_redraw(bool ready)
} else if (clear_cmdline || redraw_cmdline) {
showmode(); // clear cmdline and show mode
}
- showruler(false);
+ show_cursor_info(false);
setcursor();
emsg_on_display = false; // may remove error message now
}
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index bc8e823797..66176620a5 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -3976,7 +3976,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
_("replace with %s (y/n/a/q/l/^E/^Y)?"), sub);
msg_no_more = false;
msg_scroll = (int)i;
- showruler(true);
+ show_cursor_info(true);
ui_cursor_goto(msg_row, msg_col);
RedrawingDisabled = temp;
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 9f8d0fd0b1..776bdd935b 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -148,7 +148,7 @@ struct cmdline_info {
/// Last value of prompt_id, incremented when doing new prompt
static unsigned last_prompt_id = 0;
-// Struct to store the viewstate during 'incsearch' highlighting.
+// Struct to store the viewstate during 'incsearch' highlighting and 'inccommand' preview.
typedef struct {
colnr_T vs_curswant;
colnr_T vs_leftcol;
@@ -200,6 +200,32 @@ typedef struct command_line_state {
long *b_im_ptr;
} CommandLineState;
+typedef struct cmdpreview_win_info {
+ win_T *win;
+ pos_T save_w_cursor;
+ viewstate_T save_viewstate;
+ int save_w_p_cul;
+ int save_w_p_cuc;
+} CpWinInfo;
+
+typedef struct cmdpreview_buf_info {
+ buf_T *buf;
+ time_t save_b_u_time_cur;
+ long save_b_u_seq_cur;
+ u_header_T *save_b_u_newhead;
+ long save_b_p_ul;
+ int save_b_changed;
+ varnumber_T save_changedtick;
+} CpBufInfo;
+
+typedef struct cmdpreview_info {
+ kvec_t(CpWinInfo) win_info;
+ kvec_t(CpBufInfo) buf_info;
+ bool save_hls;
+ cmdmod_T save_cmdmod;
+ garray_T save_view;
+} CpInfo;
+
typedef struct cmdline_info CmdlineInfo;
/// The current cmdline_info. It is initialized in getcmdline() and after that
@@ -242,26 +268,26 @@ static long cmdpreview_ns = 0;
static int cmd_hkmap = 0; // Hebrew mapping during command line
-static void save_viewstate(viewstate_T *vs)
+static void save_viewstate(win_T *wp, viewstate_T *vs)
FUNC_ATTR_NONNULL_ALL
{
- vs->vs_curswant = curwin->w_curswant;
- vs->vs_leftcol = curwin->w_leftcol;
- vs->vs_topline = curwin->w_topline;
- vs->vs_topfill = curwin->w_topfill;
- vs->vs_botline = curwin->w_botline;
- vs->vs_empty_rows = curwin->w_empty_rows;
+ vs->vs_curswant = wp->w_curswant;
+ vs->vs_leftcol = wp->w_leftcol;
+ vs->vs_topline = wp->w_topline;
+ vs->vs_topfill = wp->w_topfill;
+ vs->vs_botline = wp->w_botline;
+ vs->vs_empty_rows = wp->w_empty_rows;
}
-static void restore_viewstate(viewstate_T *vs)
+static void restore_viewstate(win_T *wp, viewstate_T *vs)
FUNC_ATTR_NONNULL_ALL
{
- curwin->w_curswant = vs->vs_curswant;
- curwin->w_leftcol = vs->vs_leftcol;
- curwin->w_topline = vs->vs_topline;
- curwin->w_topfill = vs->vs_topfill;
- curwin->w_botline = vs->vs_botline;
- curwin->w_empty_rows = vs->vs_empty_rows;
+ wp->w_curswant = vs->vs_curswant;
+ wp->w_leftcol = vs->vs_leftcol;
+ wp->w_topline = vs->vs_topline;
+ wp->w_topfill = vs->vs_topfill;
+ wp->w_botline = vs->vs_botline;
+ wp->w_empty_rows = vs->vs_empty_rows;
}
static void init_incsearch_state(incsearch_state_T *s)
@@ -273,8 +299,8 @@ static void init_incsearch_state(incsearch_state_T *s)
clearpos(&s->match_end);
s->save_cursor = curwin->w_cursor; // may be restored later
s->search_start = curwin->w_cursor;
- save_viewstate(&s->init_viewstate);
- save_viewstate(&s->old_viewstate);
+ save_viewstate(curwin, &s->init_viewstate);
+ save_viewstate(curwin, &s->old_viewstate);
}
/// Completion for |:checkhealth| command.
@@ -554,7 +580,7 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat
// first restore the old curwin values, so the screen is
// positioned in the same way as the actual search command
- restore_viewstate(&s->old_viewstate);
+ restore_viewstate(curwin, &s->old_viewstate);
changed_cline_bef_curs();
update_topline(curwin);
@@ -664,7 +690,7 @@ static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s, bool
}
curwin->w_cursor = s->search_start; // -V519
}
- restore_viewstate(&s->old_viewstate);
+ restore_viewstate(curwin, &s->old_viewstate);
highlight_match = false;
// by default search all lines
@@ -1663,7 +1689,7 @@ static int may_do_command_line_next_incsearch(int firstc, long count, incsearch_
update_topline(curwin);
validate_cursor();
highlight_match = true;
- save_viewstate(&s->old_viewstate);
+ save_viewstate(curwin, &s->old_viewstate);
update_screen(NOT_VALID);
highlight_match = false;
redrawcmdline();
@@ -2395,6 +2421,126 @@ static void cmdpreview_close_win(void)
}
}
+/// Save current state and prepare windows and buffers for command preview.
+static void cmdpreview_prepare(CpInfo *cpinfo)
+{
+ kv_init(cpinfo->buf_info);
+ kv_init(cpinfo->win_info);
+
+ FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
+ buf_T *buf = win->w_buffer;
+
+ // Don't save state of command preview buffer or preview window.
+ if (buf->handle == cmdpreview_bufnr) {
+ continue;
+ }
+
+ CpBufInfo cp_bufinfo;
+ cp_bufinfo.buf = buf;
+
+ cp_bufinfo.save_b_u_time_cur = buf->b_u_time_cur;
+ cp_bufinfo.save_b_u_seq_cur = buf->b_u_seq_cur;
+ cp_bufinfo.save_b_u_newhead = buf->b_u_newhead;
+ cp_bufinfo.save_b_p_ul = buf->b_p_ul;
+ cp_bufinfo.save_b_changed = buf->b_changed;
+ cp_bufinfo.save_changedtick = buf_get_changedtick(buf);
+
+ kv_push(cpinfo->buf_info, cp_bufinfo);
+
+ buf->b_p_ul = LONG_MAX; // Make sure we can undo all changes
+
+ CpWinInfo cp_wininfo;
+ cp_wininfo.win = win;
+
+ // Save window cursor position and viewstate
+ cp_wininfo.save_w_cursor = win->w_cursor;
+ save_viewstate(win, &cp_wininfo.save_viewstate);
+
+ // Save 'cursorline' and 'cursorcolumn'
+ cp_wininfo.save_w_p_cul = win->w_p_cul;
+ cp_wininfo.save_w_p_cuc = win->w_p_cuc;
+
+ kv_push(cpinfo->win_info, cp_wininfo);
+
+ win->w_p_cul = false; // Disable 'cursorline' so it doesn't mess up the highlights
+ win->w_p_cuc = false; // Disable 'cursorcolumn' so it doesn't mess up the highlights
+ }
+
+ cpinfo->save_hls = p_hls;
+ cpinfo->save_cmdmod = cmdmod;
+ win_size_save(&cpinfo->save_view);
+ save_search_patterns();
+
+ p_hls = false; // Don't show search highlighting during live substitution
+ cmdmod.cmod_split = 0; // Disable :leftabove/botright modifiers
+ cmdmod.cmod_tab = 0; // Disable :tab modifier
+ cmdmod.cmod_flags |= CMOD_NOSWAPFILE; // Disable swap for preview buffer
+}
+
+// Restore the state of buffers and windows before command preview.
+static void cmdpreview_restore_state(CpInfo *cpinfo)
+{
+ for (size_t i = 0; i < cpinfo->buf_info.size; i++) {
+ CpBufInfo cp_bufinfo = cpinfo->buf_info.items[i];
+ buf_T *buf = cp_bufinfo.buf;
+
+ buf->b_changed = cp_bufinfo.save_b_changed;
+
+ if (buf->b_u_seq_cur != cp_bufinfo.save_b_u_seq_cur) {
+ int count = 0;
+
+ // Calculate how many undo steps are necessary to restore earlier state.
+ for (u_header_T *uhp = buf->b_u_curhead ? buf->b_u_curhead : buf->b_u_newhead;
+ uhp != NULL && uhp->uh_seq > cp_bufinfo.save_b_u_seq_cur;
+ uhp = uhp->uh_next.ptr, ++count) {}
+
+ aco_save_T aco;
+ aucmd_prepbuf(&aco, buf);
+ // Undo invisibly. This also moves the cursor!
+ if (!u_undo_and_forget(count)) {
+ abort();
+ }
+ aucmd_restbuf(&aco);
+
+ // Restore newhead. It is meaningless when curhead is valid, but we must
+ // restore it so that undotree() is identical before/after the preview.
+ buf->b_u_newhead = cp_bufinfo.save_b_u_newhead;
+ buf->b_u_time_cur = cp_bufinfo.save_b_u_time_cur;
+ }
+ if (cp_bufinfo.save_changedtick != buf_get_changedtick(buf)) {
+ buf_set_changedtick(buf, cp_bufinfo.save_changedtick);
+ }
+
+ buf->b_p_ul = cp_bufinfo.save_b_p_ul; // Restore 'undolevels'
+
+ // Clear preview highlights.
+ extmark_clear(buf, (uint32_t)cmdpreview_ns, 0, 0, MAXLNUM, MAXCOL);
+ }
+ for (size_t i = 0; i < cpinfo->win_info.size; i++) {
+ CpWinInfo cp_wininfo = cpinfo->win_info.items[i];
+ win_T *win = cp_wininfo.win;
+
+ // Restore window cursor position and viewstate
+ win->w_cursor = cp_wininfo.save_w_cursor;
+ restore_viewstate(win, &cp_wininfo.save_viewstate);
+
+ // Restore 'cursorline' and 'cursorcolumn'
+ win->w_p_cul = cp_wininfo.save_w_p_cul;
+ win->w_p_cuc = cp_wininfo.save_w_p_cuc;
+
+ update_topline(win);
+ }
+
+ cmdmod = cpinfo->save_cmdmod; // Restore cmdmod
+ p_hls = cpinfo->save_hls; // Restore 'hlsearch'
+ restore_search_patterns(); // Restore search patterns
+ win_size_restore(&cpinfo->save_view); // Restore window sizes
+
+ ga_clear(&cpinfo->save_view);
+ kv_destroy(cpinfo->win_info);
+ kv_destroy(cpinfo->buf_info);
+}
+
/// Show 'inccommand' preview if command is previewable. It works like this:
/// 1. Store current undo information so we can revert to current state later.
/// 2. Execute the preview callback with the parsed command, preview buffer number and preview
@@ -2439,35 +2585,18 @@ static bool cmdpreview_may_show(CommandLineState *s)
ea.line2 = lnum;
}
- time_t save_b_u_time_cur = curbuf->b_u_time_cur;
- long save_b_u_seq_cur = curbuf->b_u_seq_cur;
- u_header_T *save_b_u_newhead = curbuf->b_u_newhead;
- long save_b_p_ul = curbuf->b_p_ul;
- int save_b_changed = curbuf->b_changed;
- int save_w_p_cul = curwin->w_p_cul;
- int save_w_p_cuc = curwin->w_p_cuc;
- bool save_hls = p_hls;
- varnumber_T save_changedtick = buf_get_changedtick(curbuf);
+ CpInfo cpinfo;
bool icm_split = *p_icm == 's'; // inccommand=split
buf_T *cmdpreview_buf;
win_T *cmdpreview_win;
- cmdmod_T save_cmdmod = cmdmod;
- cmdpreview = true;
emsg_silent++; // Block error reporting as the command may be incomplete,
// but still update v:errmsg
msg_silent++; // Block messages, namely ones that prompt
block_autocmds(); // Block events
- garray_T save_view;
- win_size_save(&save_view); // Save current window sizes
- save_search_patterns(); // Save search patterns
- curbuf->b_p_ul = LONG_MAX; // Make sure we can undo all changes
- curwin->w_p_cul = false; // Disable 'cursorline' so it doesn't mess up the highlights
- curwin->w_p_cuc = false; // Disable 'cursorcolumn' so it doesn't mess up the highlights
- p_hls = false; // Don't show search highlighting during live substitution
- cmdmod.cmod_split = 0; // Disable :leftabove/botright modifiers
- cmdmod.cmod_tab = 0; // Disable :tab modifier
- cmdmod.cmod_flags |= CMOD_NOSWAPFILE; // Disable swap for preview buffer
+
+ // Save current state and prepare for command preview.
+ cmdpreview_prepare(&cpinfo);
// Open preview buffer if inccommand=split.
if (!icm_split) {
@@ -2475,12 +2604,14 @@ static bool cmdpreview_may_show(CommandLineState *s)
} else if ((cmdpreview_buf = cmdpreview_open_buf()) == NULL) {
abort();
}
-
// Setup preview namespace if it's not already set.
if (!cmdpreview_ns) {
cmdpreview_ns = (int)nvim_create_namespace((String)STRING_INIT);
}
+ // Set cmdpreview state.
+ cmdpreview = true;
+
// Execute the preview callback and use its return value to determine whether to show preview or
// open the preview window. The preview callback also handles doing the changes and highlights for
// the preview.
@@ -2499,7 +2630,7 @@ static bool cmdpreview_may_show(CommandLineState *s)
cmdpreview_type = 1;
}
- // If preview callback is nonzero, update screen now.
+ // If preview callback return value is nonzero, update screen now.
if (cmdpreview_type != 0) {
int save_rd = RedrawingDisabled;
RedrawingDisabled = 0;
@@ -2511,44 +2642,13 @@ static bool cmdpreview_may_show(CommandLineState *s)
if (icm_split && cmdpreview_type == 2 && cmdpreview_win != NULL) {
cmdpreview_close_win();
}
- // Clear preview highlights.
- extmark_clear(curbuf, (uint32_t)cmdpreview_ns, 0, 0, MAXLNUM, MAXCOL);
- curbuf->b_changed = save_b_changed; // Preserve 'modified' during preview
-
- if (curbuf->b_u_seq_cur != save_b_u_seq_cur) {
- // Undo invisibly. This also moves the cursor!
- while (curbuf->b_u_seq_cur != save_b_u_seq_cur) {
- if (!u_undo_and_forget(1)) {
- abort();
- }
- }
- // Restore newhead. It is meaningless when curhead is valid, but we must
- // restore it so that undotree() is identical before/after the preview.
- curbuf->b_u_newhead = save_b_u_newhead;
- curbuf->b_u_time_cur = save_b_u_time_cur;
- }
- if (save_changedtick != buf_get_changedtick(curbuf)) {
- buf_set_changedtick(curbuf, save_changedtick);
- }
+ // Restore state.
+ cmdpreview_restore_state(&cpinfo);
- cmdmod = save_cmdmod; // Restore cmdmod
- p_hls = save_hls; // Restore 'hlsearch'
- curwin->w_p_cul = save_w_p_cul; // Restore 'cursorline'
- curwin->w_p_cuc = save_w_p_cuc; // Restore 'cursorcolumn'
- curbuf->b_p_ul = save_b_p_ul; // Restore 'undolevels'
- restore_search_patterns(); // Restore search patterns
- win_size_restore(&save_view); // Restore window sizes
- ga_clear(&save_view);
unblock_autocmds(); // Unblock events
msg_silent--; // Unblock messages
emsg_silent--; // Unblock error reporting
-
- // Restore the window "view".
- curwin->w_cursor = s->is_state.save_cursor;
- restore_viewstate(&s->is_state.old_viewstate);
- update_topline(curwin);
-
redrawcmdline();
end:
xfree(cmdline);
@@ -4164,7 +4264,7 @@ char_u *ExpandOne(expand_T *xp, char_u *str, char_u *orig, int options, int mode
compl_selected = findex;
cmdline_pum_display(false);
} else if (p_wmnu) {
- win_redr_status_matches(xp, xp->xp_numfiles, xp->xp_files, findex, cmd_showtail);
+ redraw_wildmenu(xp, xp->xp_numfiles, xp->xp_files, findex, cmd_showtail);
}
if (findex == -1) {
return vim_strsave(orig_save);
@@ -4579,7 +4679,7 @@ static int showmatches(expand_T *xp, int wildmenu)
if (got_int) {
got_int = false; // only int. the completion, not the cmd line
} else if (wildmenu) {
- win_redr_status_matches(xp, num_files, files_found, -1, showtail);
+ redraw_wildmenu(xp, num_files, files_found, -1, showtail);
} else {
// find the length of the longest file name
maxlen = 0;
@@ -4691,7 +4791,7 @@ static int showmatches(expand_T *xp, int wildmenu)
return EXPAND_OK;
}
-/// Private path_tail for showmatches() (and win_redr_status_matches()):
+/// Private path_tail for showmatches() (and redraw_wildmenu()):
/// Find tail of file name path, but ignore trailing "/".
char *sm_gettail(char *s, bool eager)
{
diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c
index 6a1bbcb089..e8410d1ee7 100644
--- a/src/nvim/hardcopy.c
+++ b/src/nvim/hardcopy.c
@@ -33,6 +33,7 @@
#include "nvim/os/os.h"
#include "nvim/path.h"
#include "nvim/runtime.h"
+#include "nvim/statusline.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/ui.h"
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 6cc68edb82..684cf7207c 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -830,8 +830,15 @@ static bool semsgv(const char *fmt, va_list ap)
/// detected when fuzzing vim.
void iemsg(const char *s)
{
+ if (emsg_not_now()) {
+ return;
+ }
+
emsg(s);
#ifdef ABORT_ON_INTERNAL_ERROR
+ set_vim_var_string(VV_ERRMSG, s, -1);
+ msg_putchar('\n'); // avoid overwriting the error message
+ ui_flush();
abort();
#endif
}
@@ -841,11 +848,17 @@ void iemsg(const char *s)
/// detected when fuzzing vim.
void siemsg(const char *s, ...)
{
+ if (emsg_not_now()) {
+ return;
+ }
+
va_list ap;
va_start(ap, s);
(void)semsgv(s, ap);
va_end(ap);
#ifdef ABORT_ON_INTERNAL_ERROR
+ msg_putchar('\n'); // avoid overwriting the error message
+ ui_flush();
abort();
#endif
}
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index c87c0cbb6e..1d8a786c9f 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -1322,7 +1322,7 @@ static void normal_redraw(NormalState *s)
did_emsg = false;
msg_didany = false; // reset lines_left in msg_start()
may_clear_sb_text(); // clear scroll-back text on next msg
- showruler(false);
+ show_cursor_info(false);
setcursor();
}
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index c3edc5b315..b4fc7534bc 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -6136,6 +6136,23 @@ static void op_colon(oparg_T *oap)
// do_cmdline() does the rest
}
+/// callback function for 'operatorfunc'
+static Callback opfunc_cb;
+
+/// Process the 'operatorfunc' option value.
+/// @return OK or FAIL
+int set_operatorfunc_option(void)
+{
+ return option_set_callback_func(p_opfunc, &opfunc_cb);
+}
+
+#if defined(EXITFREE)
+void free_operatorfunc_option(void)
+{
+ callback_free(&opfunc_cb);
+}
+#endif
+
/// Handle the "g@" operator: call 'operatorfunc'.
static void op_function(const oparg_T *oap)
FUNC_ATTR_NONNULL_ALL
@@ -6173,7 +6190,10 @@ static void op_function(const oparg_T *oap)
// Reset finish_op so that mode() returns the right value.
finish_op = false;
- (void)call_func_retnr((char *)p_opfunc, 1, argv);
+ typval_T rettv;
+ if (callback_call(&opfunc_cb, 1, argv, &rettv) != FAIL) {
+ tv_clear(&rettv);
+ }
virtual_op = save_virtual_op;
finish_op = save_finish_op;
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 09793cbdcf..e85a0d9ee4 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -66,6 +66,7 @@
#include "nvim/mouse.h"
#include "nvim/move.h"
#include "nvim/normal.h"
+#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/os/os.h"
#include "nvim/os_unix.h"
@@ -782,6 +783,7 @@ void free_all_options(void)
clear_string_option((char_u **)options[i].var);
}
}
+ free_operatorfunc_option();
}
#endif
@@ -3264,8 +3266,12 @@ static char *did_set_string_option(int opt_idx, char_u **varp, char_u *oldval, c
}
}
}
- } else if (varp == &p_qftf) {
- if (!qf_process_qftf_option()) {
+ } else if (varp == &p_opfunc) { // 'operatorfunc'
+ if (set_operatorfunc_option() == FAIL) {
+ errmsg = e_invarg;
+ }
+ } else if (varp == &p_qftf) { // 'quickfixtextfunc'
+ if (qf_process_qftf_option() == FAIL) {
errmsg = e_invarg;
}
} else {
@@ -6917,6 +6923,44 @@ static int fill_culopt_flags(char_u *val, win_T *wp)
return OK;
}
+/// Set the callback function value for an option that accepts a function name,
+/// lambda, et al. (e.g. 'operatorfunc', 'tagfunc', etc.)
+/// @return OK if the option is successfully set to a function, otherwise FAIL
+int option_set_callback_func(char_u *optval, Callback *optcb)
+{
+ if (optval == NULL || *optval == NUL) {
+ callback_free(optcb);
+ return OK;
+ }
+
+ typval_T *tv;
+ if (*optval == '{'
+ || (STRNCMP(optval, "function(", 9) == 0)
+ || (STRNCMP(optval, "funcref(", 8) == 0)) {
+ // Lambda expression or a funcref
+ tv = eval_expr((char *)optval);
+ if (tv == NULL) {
+ return FAIL;
+ }
+ } else {
+ // treat everything else as a function name string
+ tv = xcalloc(1, sizeof(*tv));
+ tv->v_type = VAR_STRING;
+ tv->vval.v_string = (char *)vim_strsave(optval);
+ }
+
+ Callback cb;
+ if (!callback_from_typval(&cb, tv)) {
+ tv_free(tv);
+ return FAIL;
+ }
+
+ callback_free(optcb);
+ *optcb = cb;
+ tv_free(tv);
+ return OK;
+}
+
/// Check an option that can be a range of string values.
///
/// @param list when true: accept a list of values
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 1c416a872b..d04c2bc470 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -3829,38 +3829,11 @@ static buf_T *qf_find_buf(qf_info_T *qi)
return NULL;
}
-// Process the 'quickfixtextfunc' option value.
-bool qf_process_qftf_option(void)
+/// Process the 'quickfixtextfunc' option value.
+/// @return OK or FAIL
+int qf_process_qftf_option(void)
{
- if (p_qftf == NULL || *p_qftf == NUL) {
- callback_free(&qftf_cb);
- return true;
- }
-
- typval_T *tv;
- if (*p_qftf == '{') {
- // Lambda expression
- tv = eval_expr((char *)p_qftf);
- if (tv == NULL) {
- return false;
- }
- } else {
- // treat everything else as a function name string
- tv = xcalloc(1, sizeof(*tv));
- tv->v_type = VAR_STRING;
- tv->vval.v_string = (char *)vim_strsave(p_qftf);
- }
-
- Callback cb;
- if (!callback_from_typval(&cb, tv)) {
- tv_free(tv);
- return false;
- }
-
- callback_free(&qftf_cb);
- qftf_cb = cb;
- tv_free(tv);
- return true;
+ return option_set_callback_func(p_qftf, &qftf_cb);
}
/// Update the w:quickfix_title variable in the quickfix/location list window in
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 2419a42a2a..c9a8faa8c6 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -16,6 +16,7 @@
#include "nvim/buffer.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
+#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/ex_getln.h"
#include "nvim/extmark.h"
@@ -34,6 +35,7 @@
#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/state.h"
+#include "nvim/statusline.h"
#include "nvim/ui_compositor.h"
#include "nvim/undo.h"
#include "nvim/window.h"
@@ -249,7 +251,7 @@ void rl_mirror(char_u *str)
}
/// Get the length of an item as it will be shown in the status line.
-static int status_match_len(expand_T *xp, char_u *s)
+static int wildmenu_match_len(expand_T *xp, char_u *s)
{
int len = 0;
@@ -262,7 +264,7 @@ static int status_match_len(expand_T *xp, char_u *s)
}
while (*s != NUL) {
- s += skip_status_match_char(xp, s);
+ s += skip_wildmenu_char(xp, s);
len += ptr2cells((char *)s);
MB_PTR_ADV(s);
}
@@ -270,29 +272,9 @@ static int status_match_len(expand_T *xp, char_u *s)
return len;
}
-/// Redraw all status lines at the bottom of frame "frp".
-void win_redraw_last_status(const frame_T *frp)
- FUNC_ATTR_NONNULL_ARG(1)
-{
- if (frp->fr_layout == FR_LEAF) {
- frp->fr_win->w_redr_status = true;
- } else if (frp->fr_layout == FR_ROW) {
- FOR_ALL_FRAMES(frp, frp->fr_child) {
- win_redraw_last_status(frp);
- }
- } else {
- assert(frp->fr_layout == FR_COL);
- frp = frp->fr_child;
- while (frp->fr_next != NULL) {
- frp = frp->fr_next;
- }
- win_redraw_last_status(frp);
- }
-}
-
-/// Return the number of characters that should be skipped in a status match.
+/// Return the number of characters that should be skipped in the wildmenu
/// These are backslashes used for escaping. Do show backslashes in help tags.
-static int skip_status_match_char(expand_T *xp, char_u *s)
+static int skip_wildmenu_char(expand_T *xp, char_u *s)
{
if ((rem_backslash(s) && xp->xp_context != EXPAND_HELP)
|| ((xp->xp_context == EXPAND_MENUS
@@ -300,6 +282,10 @@ static int skip_status_match_char(expand_T *xp, char_u *s)
&& (s[0] == '\t'
|| (s[0] == '\\' && s[1] != NUL)))) {
#ifndef BACKSLASH_IN_FILENAME
+ // TODO(bfredl): Why in the actual fuck are we special casing the
+ // shell variety deep in the redraw logic? Shell special snowflakiness
+ // should already be eliminated multiple layers before reaching the
+ // screen infracstructure.
if (xp->xp_shell && csh_like_shell() && s[1] == '\\' && s[2] == '!') {
return 2;
}
@@ -316,7 +302,7 @@ static int skip_status_match_char(expand_T *xp, char_u *s)
/// If inversion is possible we use it. Else '=' characters are used.
///
/// @param matches list of matches
-void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int match, int showtail)
+void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int match, int showtail)
{
#define L_MATCH(m) (showtail ? sm_gettail(matches[m], false) : matches[m])
int row;
@@ -347,7 +333,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int
highlight = false;
}
// count 1 for the ending ">"
- clen = status_match_len(xp, (char_u *)L_MATCH(match)) + 3;
+ clen = wildmenu_match_len(xp, (char_u *)L_MATCH(match)) + 3;
if (match == 0) {
first_match = 0;
} else if (match < first_match) {
@@ -357,7 +343,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int
} else {
// check if match fits on the screen
for (i = first_match; i < match; i++) {
- clen += status_match_len(xp, (char_u *)L_MATCH(i)) + 2;
+ clen += wildmenu_match_len(xp, (char_u *)L_MATCH(i)) + 2;
}
if (first_match > 0) {
clen += 2;
@@ -368,7 +354,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int
// if showing the last match, we can add some on the left
clen = 2;
for (i = match; i < num_matches; i++) {
- clen += status_match_len(xp, (char_u *)L_MATCH(i)) + 2;
+ clen += wildmenu_match_len(xp, (char_u *)L_MATCH(i)) + 2;
if ((long)clen >= Columns) {
break;
}
@@ -380,7 +366,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int
}
if (add_left) {
while (first_match > 0) {
- clen += status_match_len(xp, (char_u *)L_MATCH(first_match - 1)) + 2;
+ clen += wildmenu_match_len(xp, (char_u *)L_MATCH(first_match - 1)) + 2;
if ((long)clen >= Columns) {
break;
}
@@ -400,7 +386,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int
clen = len;
i = first_match;
- while (clen + status_match_len(xp, (char_u *)L_MATCH(i)) + 2 < Columns) {
+ while (clen + wildmenu_match_len(xp, (char_u *)L_MATCH(i)) + 2 < Columns) {
if (i == match) {
selstart = buf + len;
selstart_col = clen;
@@ -417,7 +403,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int
clen += l;
} else {
for (; *s != NUL; s++) {
- s += skip_status_match_char(xp, s);
+ s += skip_wildmenu_char(xp, s);
clen += ptr2cells((char *)s);
if ((l = utfc_ptr2len((char *)s)) > 1) {
STRNCPY(buf + len, s, l); // NOLINT(runtime/printf)
@@ -561,223 +547,6 @@ bool get_keymap_str(win_T *wp, char *fmt, char *buf, int len)
return buf[0] != NUL;
}
-/// Redraw the status line, window bar or ruler of window "wp".
-/// When "wp" is NULL redraw the tab pages line from 'tabline'.
-void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
-{
- static bool entered = false;
- int attr;
- int curattr;
- int row;
- int col = 0;
- int maxwidth;
- int width;
- int n;
- int len;
- int fillchar;
- char buf[MAXPATHL];
- char_u *stl;
- char *p;
- stl_hlrec_t *hltab;
- StlClickRecord *tabtab;
- int use_sandbox = false;
- win_T *ewp;
- int p_crb_save;
- bool is_stl_global = global_stl_height() > 0;
-
- ScreenGrid *grid = &default_grid;
-
- // There is a tiny chance that this gets called recursively: When
- // redrawing a status line triggers redrawing the ruler or tabline.
- // Avoid trouble by not allowing recursion.
- if (entered) {
- return;
- }
- entered = true;
-
- // setup environment for the task at hand
- if (wp == NULL) {
- // Use 'tabline'. Always at the first line of the screen.
- stl = p_tal;
- row = 0;
- fillchar = ' ';
- attr = HL_ATTR(HLF_TPF);
- maxwidth = Columns;
- use_sandbox = was_set_insecurely(wp, "tabline", 0);
- } else if (draw_winbar) {
- stl = (char_u *)((*wp->w_p_wbr != NUL) ? wp->w_p_wbr : p_wbr);
- row = -1; // row zero is first row of text
- col = 0;
- grid = &wp->w_grid;
- grid_adjust(&grid, &row, &col);
-
- if (row < 0) {
- return;
- }
-
- fillchar = wp->w_p_fcs_chars.wbr;
- attr = (wp == curwin) ? win_hl_attr(wp, HLF_WBR) : win_hl_attr(wp, HLF_WBRNC);
- maxwidth = wp->w_width_inner;
- use_sandbox = was_set_insecurely(wp, "winbar", 0);
-
- stl_clear_click_defs(wp->w_winbar_click_defs, (long)wp->w_winbar_click_defs_size);
- // Allocate / resize the click definitions array for winbar if needed.
- if (wp->w_winbar_height && wp->w_winbar_click_defs_size < (size_t)maxwidth) {
- xfree(wp->w_winbar_click_defs);
- wp->w_winbar_click_defs_size = (size_t)maxwidth;
- wp->w_winbar_click_defs = xcalloc(wp->w_winbar_click_defs_size, sizeof(StlClickRecord));
- }
- } else {
- row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp);
- fillchar = fillchar_status(&attr, wp);
- maxwidth = is_stl_global ? Columns : wp->w_width;
-
- stl_clear_click_defs(wp->w_status_click_defs, (long)wp->w_status_click_defs_size);
- // Allocate / resize the click definitions array for statusline if needed.
- if (wp->w_status_click_defs_size < (size_t)maxwidth) {
- xfree(wp->w_status_click_defs);
- wp->w_status_click_defs_size = (size_t)maxwidth;
- wp->w_status_click_defs = xcalloc(wp->w_status_click_defs_size, sizeof(StlClickRecord));
- }
-
- if (draw_ruler) {
- stl = p_ruf;
- // advance past any leading group spec - implicit in ru_col
- if (*stl == '%') {
- if (*++stl == '-') {
- stl++;
- }
- if (atoi((char *)stl)) {
- while (ascii_isdigit(*stl)) {
- stl++;
- }
- }
- if (*stl++ != '(') {
- stl = p_ruf;
- }
- }
- col = ru_col - (Columns - maxwidth);
- if (col < (maxwidth + 1) / 2) {
- col = (maxwidth + 1) / 2;
- }
- maxwidth = maxwidth - col;
- if (!wp->w_status_height && !is_stl_global) {
- grid = &msg_grid_adj;
- row = Rows - 1;
- maxwidth--; // writing in last column may cause scrolling
- fillchar = ' ';
- attr = HL_ATTR(HLF_MSG);
- }
-
- use_sandbox = was_set_insecurely(wp, "rulerformat", 0);
- } else {
- if (*wp->w_p_stl != NUL) {
- stl = wp->w_p_stl;
- } else {
- stl = p_stl;
- }
- use_sandbox = was_set_insecurely(wp, "statusline", *wp->w_p_stl == NUL ? 0 : OPT_LOCAL);
- }
-
- col += is_stl_global ? 0 : wp->w_wincol;
- }
-
- if (maxwidth <= 0) {
- goto theend;
- }
-
- // Temporarily reset 'cursorbind', we don't want a side effect from moving
- // the cursor away and back.
- ewp = wp == NULL ? curwin : wp;
- p_crb_save = ewp->w_p_crb;
- ewp->w_p_crb = false;
-
- // Make a copy, because the statusline may include a function call that
- // might change the option value and free the memory.
- stl = vim_strsave(stl);
- width =
- build_stl_str_hl(ewp, buf, sizeof(buf), (char *)stl, use_sandbox,
- fillchar, maxwidth, &hltab, &tabtab);
- xfree(stl);
- ewp->w_p_crb = p_crb_save;
-
- // Make all characters printable.
- p = transstr(buf, true);
- len = (int)STRLCPY(buf, p, sizeof(buf));
- len = (size_t)len < sizeof(buf) ? len : (int)sizeof(buf) - 1;
- xfree(p);
-
- // fill up with "fillchar"
- while (width < maxwidth && len < (int)sizeof(buf) - 1) {
- len += utf_char2bytes(fillchar, buf + len);
- width++;
- }
- buf[len] = NUL;
-
- // Draw each snippet with the specified highlighting.
- grid_puts_line_start(grid, row);
-
- curattr = attr;
- p = buf;
- for (n = 0; hltab[n].start != NULL; n++) {
- int textlen = (int)(hltab[n].start - p);
- grid_puts_len(grid, (char_u *)p, textlen, row, col, curattr);
- col += vim_strnsize((char_u *)p, textlen);
- p = hltab[n].start;
-
- if (hltab[n].userhl == 0) {
- curattr = attr;
- } else if (hltab[n].userhl < 0) {
- curattr = syn_id2attr(-hltab[n].userhl);
- } else if (wp != NULL && wp != curwin && wp->w_status_height != 0) {
- curattr = highlight_stlnc[hltab[n].userhl - 1];
- } else {
- curattr = highlight_user[hltab[n].userhl - 1];
- }
- }
- // Make sure to use an empty string instead of p, if p is beyond buf + len.
- grid_puts(grid, p >= buf + len ? (char_u *)"" : (char_u *)p, row, col,
- curattr);
-
- grid_puts_line_flush(false);
-
- // Fill the tab_page_click_defs, w_status_click_defs or w_winbar_click_defs array for clicking
- // in the tab page line, status line or window bar
- StlClickDefinition *click_defs = (wp == NULL) ? tab_page_click_defs
- : draw_winbar ? wp->w_winbar_click_defs
- : wp->w_status_click_defs;
-
- if (click_defs == NULL) {
- goto theend;
- }
-
- col = 0;
- len = 0;
- p = buf;
- StlClickDefinition cur_click_def = {
- .type = kStlClickDisabled,
- };
- for (n = 0; tabtab[n].start != NULL; n++) {
- len += vim_strnsize((char_u *)p, (int)(tabtab[n].start - p));
- while (col < len) {
- click_defs[col++] = cur_click_def;
- }
- p = (char *)tabtab[n].start;
- cur_click_def = tabtab[n].def;
- if ((wp != NULL) && !(cur_click_def.type == kStlClickDisabled
- || cur_click_def.type == kStlClickFuncRun)) {
- // window bar and status line only support click functions
- cur_click_def.type = kStlClickDisabled;
- }
- }
- while (col < maxwidth) {
- click_defs[col++] = cur_click_def;
- }
-
-theend:
- entered = false;
-}
-
/// Prepare for 'hlsearch' highlighting.
void start_search_hl(void)
{
@@ -1378,32 +1147,6 @@ void get_trans_bufname(buf_T *buf)
trans_characters((char *)NameBuff, MAXPATHL);
}
-/// Get the character to use in a status line. Get its attributes in "*attr".
-int fillchar_status(int *attr, win_T *wp)
-{
- int fill;
- bool is_curwin = (wp == curwin);
- if (is_curwin) {
- *attr = win_hl_attr(wp, HLF_S);
- fill = wp->w_p_fcs_chars.stl;
- } else {
- *attr = win_hl_attr(wp, HLF_SNC);
- fill = wp->w_p_fcs_chars.stlnc;
- }
- // Use fill when there is highlighting, and highlighting of current
- // window differs, or the fillchars differ, or this is not the
- // current window
- if (*attr != 0 && ((win_hl_attr(wp, HLF_S) != win_hl_attr(wp, HLF_SNC)
- || !is_curwin || ONE_WINDOW)
- || (wp->w_p_fcs_chars.stl != wp->w_p_fcs_chars.stlnc))) {
- return fill;
- }
- if (is_curwin) {
- return '^';
- }
- return '=';
-}
-
/// Get the character to use in a separator between vertically split windows.
/// Get its attributes in "*attr".
int fillchar_vsep(win_T *wp, int *attr)
@@ -1433,175 +1176,6 @@ bool messaging(void)
return !(p_lz && char_avail() && !KeyTyped) && ui_has_messages();
}
-void win_redr_ruler(win_T *wp, bool always)
-{
- bool is_stl_global = global_stl_height() > 0;
- static bool did_show_ext_ruler = false;
-
- // If 'ruler' off, don't do anything
- if (!p_ru) {
- return;
- }
-
- // Check if cursor.lnum is valid, since win_redr_ruler() may be called
- // after deleting lines, before cursor.lnum is corrected.
- if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) {
- return;
- }
-
- // Don't draw the ruler while doing insert-completion, it might overwrite
- // the (long) mode message.
- if (wp == lastwin && lastwin->w_status_height == 0 && !is_stl_global) {
- if (edit_submode != NULL) {
- return;
- }
- }
-
- if (*p_ruf && p_ch > 0 && !ui_has(kUIMessages)) {
- const int called_emsg_before = called_emsg;
- win_redr_custom(wp, false, true);
- if (called_emsg > called_emsg_before) {
- set_string_option_direct("rulerformat", -1, "", OPT_FREE, SID_ERROR);
- }
- return;
- }
-
- // Check if not in Insert mode and the line is empty (will show "0-1").
- int empty_line = false;
- if ((State & MODE_INSERT) == 0 && *ml_get_buf(wp->w_buffer, wp->w_cursor.lnum, false) == NUL) {
- empty_line = true;
- }
-
- // Only draw the ruler when something changed.
- validate_virtcol_win(wp);
- if (redraw_cmdline
- || always
- || wp->w_cursor.lnum != wp->w_ru_cursor.lnum
- || wp->w_cursor.col != wp->w_ru_cursor.col
- || wp->w_virtcol != wp->w_ru_virtcol
- || wp->w_cursor.coladd != wp->w_ru_cursor.coladd
- || wp->w_topline != wp->w_ru_topline
- || wp->w_buffer->b_ml.ml_line_count != wp->w_ru_line_count
- || wp->w_topfill != wp->w_ru_topfill
- || empty_line != wp->w_ru_empty) {
- int width;
- int row;
- int fillchar;
- int attr;
- int off;
- bool part_of_status = false;
-
- if (wp->w_status_height) {
- row = W_ENDROW(wp);
- fillchar = fillchar_status(&attr, wp);
- off = wp->w_wincol;
- width = wp->w_width;
- part_of_status = true;
- } else if (is_stl_global) {
- row = Rows - (int)p_ch - 1;
- fillchar = fillchar_status(&attr, wp);
- off = 0;
- width = Columns;
- part_of_status = true;
- } else {
- row = Rows - 1;
- fillchar = ' ';
- attr = HL_ATTR(HLF_MSG);
- width = Columns;
- off = 0;
- }
-
- if (!part_of_status && !ui_has_messages()) {
- return;
- }
-
- // In list mode virtcol needs to be recomputed
- colnr_T virtcol = wp->w_virtcol;
- if (wp->w_p_list && wp->w_p_lcs_chars.tab1 == NUL) {
- wp->w_p_list = false;
- getvvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL);
- wp->w_p_list = true;
- }
-
-#define RULER_BUF_LEN 70
- char buffer[RULER_BUF_LEN];
-
- // Some sprintfs return the length, some return a pointer.
- // To avoid portability problems we use strlen() here.
- vim_snprintf(buffer, RULER_BUF_LEN, "%" PRId64 ",",
- (wp->w_buffer->b_ml.ml_flags &
- ML_EMPTY) ? (int64_t)0L : (int64_t)wp->w_cursor.lnum);
- size_t len = STRLEN(buffer);
- col_print(buffer + len, RULER_BUF_LEN - len,
- empty_line ? 0 : (int)wp->w_cursor.col + 1,
- (int)virtcol + 1);
-
- // Add a "50%" if there is room for it.
- // On the last line, don't print in the last column (scrolls the
- // screen up on some terminals).
- int i = (int)STRLEN(buffer);
- get_rel_pos(wp, buffer + i + 1, RULER_BUF_LEN - i - 1);
- int o = i + vim_strsize(buffer + i + 1);
- if (wp->w_status_height == 0 && !is_stl_global) { // can't use last char of screen
- o++;
- }
- int this_ru_col = ru_col - (Columns - width);
- if (this_ru_col < 0) {
- this_ru_col = 0;
- }
- // Never use more than half the window/screen width, leave the other half
- // for the filename.
- if (this_ru_col < (width + 1) / 2) {
- this_ru_col = (width + 1) / 2;
- }
- if (this_ru_col + o < width) {
- // Need at least 3 chars left for get_rel_pos() + NUL.
- while (this_ru_col + o < width && RULER_BUF_LEN > i + 4) {
- i += utf_char2bytes(fillchar, buffer + i);
- o++;
- }
- get_rel_pos(wp, buffer + i, RULER_BUF_LEN - i);
- }
-
- if (ui_has(kUIMessages) && !part_of_status) {
- MAXSIZE_TEMP_ARRAY(content, 1);
- MAXSIZE_TEMP_ARRAY(chunk, 2);
- ADD_C(chunk, INTEGER_OBJ(attr));
- ADD_C(chunk, STRING_OBJ(cstr_as_string((char *)buffer)));
- ADD_C(content, ARRAY_OBJ(chunk));
- ui_call_msg_ruler(content);
- did_show_ext_ruler = true;
- } else {
- if (did_show_ext_ruler) {
- ui_call_msg_ruler((Array)ARRAY_DICT_INIT);
- did_show_ext_ruler = false;
- }
- // Truncate at window boundary.
- o = 0;
- for (i = 0; buffer[i] != NUL; i += utfc_ptr2len(buffer + i)) {
- o += utf_ptr2cells(buffer + i);
- if (this_ru_col + o > width) {
- buffer[i] = NUL;
- break;
- }
- }
-
- ScreenGrid *grid = part_of_status ? &default_grid : &msg_grid_adj;
- grid_puts(grid, (char_u *)buffer, row, this_ru_col + off, attr);
- grid_fill(grid, row, row + 1,
- this_ru_col + off + (int)STRLEN(buffer), off + width, fillchar,
- fillchar, attr);
- }
-
- wp->w_ru_cursor = wp->w_cursor;
- wp->w_ru_virtcol = wp->w_virtcol;
- wp->w_ru_empty = (char)empty_line;
- wp->w_ru_topline = wp->w_topline;
- wp->w_ru_line_count = wp->w_buffer->b_ml.ml_line_count;
- wp->w_ru_topfill = wp->w_topfill;
- }
-}
-
#define COL_RULER 17 // columns needed by standard ruler
/// Compute columns for ruler and shown command. 'sc_col' is also used to
diff --git a/src/nvim/search.c b/src/nvim/search.c
index c820817a71..c53d955974 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -2423,7 +2423,7 @@ void showmatch(int c)
curwin->w_cursor = mpos; // move to matching char
*so = 0; // don't use 'scrolloff' here
*siso = 0; // don't use 'sidescrolloff' here
- showruler(false);
+ show_cursor_info(false);
setcursor();
ui_flush();
// Restore dollar_vcol(), because setcursor() may call curs_rows()
diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c
new file mode 100644
index 0000000000..c3260fd496
--- /dev/null
+++ b/src/nvim/statusline.c
@@ -0,0 +1,1807 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+//
+
+#include <assert.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include "nvim/assert.h"
+#include "nvim/autocmd.h"
+#include "nvim/buffer.h"
+#include "nvim/charset.h"
+#include "nvim/drawscreen.h"
+#include "nvim/eval.h"
+#include "nvim/eval/vars.h"
+#include "nvim/grid.h"
+#include "nvim/highlight.h"
+#include "nvim/highlight_group.h"
+#include "nvim/move.h"
+#include "nvim/option.h"
+#include "nvim/statusline.h"
+#include "nvim/ui.h"
+#include "nvim/undo.h"
+#include "nvim/vim.h"
+#include "nvim/window.h"
+
+// Determines how deeply nested %{} blocks will be evaluated in statusline.
+#define MAX_STL_EVAL_DEPTH 100
+
+/// Enumeration specifying the valid numeric bases that can
+/// be used when printing numbers in the status line.
+typedef enum {
+ kNumBaseDecimal = 10,
+ kNumBaseHexadecimal = 16,
+} NumberBase;
+
+/// Redraw the status line of window `wp`.
+///
+/// If inversion is possible we use it. Else '=' characters are used.
+void win_redr_status(win_T *wp)
+{
+ int row;
+ int col;
+ char_u *p;
+ int len;
+ int fillchar;
+ int attr;
+ int width;
+ int this_ru_col;
+ bool is_stl_global = global_stl_height() > 0;
+ static bool busy = false;
+
+ // May get here recursively when 'statusline' (indirectly)
+ // invokes ":redrawstatus". Simply ignore the call then.
+ if (busy
+ // Also ignore if wildmenu is showing.
+ || (wild_menu_showing != 0 && !ui_has(kUIWildmenu))) {
+ return;
+ }
+ busy = true;
+
+ wp->w_redr_status = false;
+ if (wp->w_status_height == 0 && !(is_stl_global && wp == curwin)) {
+ // no status line, either global statusline is enabled or the window is a last window
+ redraw_cmdline = true;
+ } else if (!redrawing()) {
+ // Don't redraw right now, do it later. Don't update status line when
+ // popup menu is visible and may be drawn over it
+ wp->w_redr_status = true;
+ } else if (*p_stl != NUL || *wp->w_p_stl != NUL) {
+ // redraw custom status line
+ redraw_custom_statusline(wp);
+ } else {
+ fillchar = fillchar_status(&attr, wp);
+ width = is_stl_global ? Columns : wp->w_width;
+
+ get_trans_bufname(wp->w_buffer);
+ p = NameBuff;
+ len = (int)STRLEN(p);
+
+ if (bt_help(wp->w_buffer)
+ || wp->w_p_pvw
+ || bufIsChanged(wp->w_buffer)
+ || wp->w_buffer->b_p_ro) {
+ *(p + len++) = ' ';
+ }
+ if (bt_help(wp->w_buffer)) {
+ snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[Help]"));
+ len += (int)STRLEN(p + len);
+ }
+ if (wp->w_p_pvw) {
+ snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[Preview]"));
+ len += (int)STRLEN(p + len);
+ }
+ if (bufIsChanged(wp->w_buffer)) {
+ snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", "[+]");
+ len += (int)STRLEN(p + len);
+ }
+ if (wp->w_buffer->b_p_ro) {
+ snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[RO]"));
+ // len += (int)STRLEN(p + len); // dead assignment
+ }
+
+ this_ru_col = ru_col - (Columns - width);
+ if (this_ru_col < (width + 1) / 2) {
+ this_ru_col = (width + 1) / 2;
+ }
+ if (this_ru_col <= 1) {
+ p = (char_u *)"<"; // No room for file name!
+ len = 1;
+ } else {
+ int clen = 0, i;
+
+ // Count total number of display cells.
+ clen = (int)mb_string2cells((char *)p);
+
+ // Find first character that will fit.
+ // Going from start to end is much faster for DBCS.
+ for (i = 0; p[i] != NUL && clen >= this_ru_col - 1;
+ i += utfc_ptr2len((char *)p + i)) {
+ clen -= utf_ptr2cells((char *)p + i);
+ }
+ len = clen;
+ if (i > 0) {
+ p = p + i - 1;
+ *p = '<';
+ len++;
+ }
+ }
+
+ row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp);
+ col = is_stl_global ? 0 : wp->w_wincol;
+ grid_puts(&default_grid, p, row, col, attr);
+ grid_fill(&default_grid, row, row + 1, len + col,
+ this_ru_col + col, fillchar, fillchar, attr);
+
+ if (get_keymap_str(wp, "<%s>", (char *)NameBuff, MAXPATHL)
+ && this_ru_col - len > (int)(STRLEN(NameBuff) + 1)) {
+ grid_puts(&default_grid, NameBuff, row,
+ (int)((size_t)this_ru_col - STRLEN(NameBuff) - 1), attr);
+ }
+
+ win_redr_ruler(wp, true);
+ }
+
+ // May need to draw the character below the vertical separator.
+ if (wp->w_vsep_width != 0 && wp->w_status_height != 0 && redrawing()) {
+ if (stl_connected(wp)) {
+ fillchar = fillchar_status(&attr, wp);
+ } else {
+ fillchar = fillchar_vsep(wp, &attr);
+ }
+ grid_putchar(&default_grid, fillchar, W_ENDROW(wp), W_ENDCOL(wp), attr);
+ }
+ busy = false;
+}
+
+void win_redr_winbar(win_T *wp)
+{
+ static bool entered = false;
+
+ // Return when called recursively. This can happen when the winbar contains an expression
+ // that triggers a redraw.
+ if (entered) {
+ return;
+ }
+ entered = true;
+
+ if (wp->w_winbar_height == 0 || !redrawing()) {
+ // Do nothing.
+ } else if (*p_wbr != NUL || *wp->w_p_wbr != NUL) {
+ int saved_did_emsg = did_emsg;
+
+ did_emsg = false;
+ win_redr_custom(wp, true, false);
+ if (did_emsg) {
+ // When there is an error disable the winbar, otherwise the
+ // display is messed up with errors and a redraw triggers the problem
+ // again and again.
+ set_string_option_direct("winbar", -1, "",
+ OPT_FREE | (*wp->w_p_stl != NUL
+ ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR);
+ }
+ did_emsg |= saved_did_emsg;
+ }
+ entered = false;
+}
+
+void win_redr_ruler(win_T *wp, bool always)
+{
+ bool is_stl_global = global_stl_height() > 0;
+ static bool did_show_ext_ruler = false;
+
+ // If 'ruler' off, don't do anything
+ if (!p_ru) {
+ return;
+ }
+
+ // Check if cursor.lnum is valid, since win_redr_ruler() may be called
+ // after deleting lines, before cursor.lnum is corrected.
+ if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) {
+ return;
+ }
+
+ // Don't draw the ruler while doing insert-completion, it might overwrite
+ // the (long) mode message.
+ if (wp == lastwin && lastwin->w_status_height == 0 && !is_stl_global) {
+ if (edit_submode != NULL) {
+ return;
+ }
+ }
+
+ if (*p_ruf && p_ch > 0 && !ui_has(kUIMessages)) {
+ const int called_emsg_before = called_emsg;
+ win_redr_custom(wp, false, true);
+ if (called_emsg > called_emsg_before) {
+ set_string_option_direct("rulerformat", -1, "", OPT_FREE, SID_ERROR);
+ }
+ return;
+ }
+
+ // Check if not in Insert mode and the line is empty (will show "0-1").
+ int empty_line = false;
+ if ((State & MODE_INSERT) == 0 && *ml_get_buf(wp->w_buffer, wp->w_cursor.lnum, false) == NUL) {
+ empty_line = true;
+ }
+
+ // Only draw the ruler when something changed.
+ validate_virtcol_win(wp);
+ if (redraw_cmdline
+ || always
+ || wp->w_cursor.lnum != wp->w_ru_cursor.lnum
+ || wp->w_cursor.col != wp->w_ru_cursor.col
+ || wp->w_virtcol != wp->w_ru_virtcol
+ || wp->w_cursor.coladd != wp->w_ru_cursor.coladd
+ || wp->w_topline != wp->w_ru_topline
+ || wp->w_buffer->b_ml.ml_line_count != wp->w_ru_line_count
+ || wp->w_topfill != wp->w_ru_topfill
+ || empty_line != wp->w_ru_empty) {
+ int width;
+ int row;
+ int fillchar;
+ int attr;
+ int off;
+ bool part_of_status = false;
+
+ if (wp->w_status_height) {
+ row = W_ENDROW(wp);
+ fillchar = fillchar_status(&attr, wp);
+ off = wp->w_wincol;
+ width = wp->w_width;
+ part_of_status = true;
+ } else if (is_stl_global) {
+ row = Rows - (int)p_ch - 1;
+ fillchar = fillchar_status(&attr, wp);
+ off = 0;
+ width = Columns;
+ part_of_status = true;
+ } else {
+ row = Rows - 1;
+ fillchar = ' ';
+ attr = HL_ATTR(HLF_MSG);
+ width = Columns;
+ off = 0;
+ }
+
+ if (!part_of_status && !ui_has_messages()) {
+ return;
+ }
+
+ // In list mode virtcol needs to be recomputed
+ colnr_T virtcol = wp->w_virtcol;
+ if (wp->w_p_list && wp->w_p_lcs_chars.tab1 == NUL) {
+ wp->w_p_list = false;
+ getvvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL);
+ wp->w_p_list = true;
+ }
+
+#define RULER_BUF_LEN 70
+ char buffer[RULER_BUF_LEN];
+
+ // Some sprintfs return the length, some return a pointer.
+ // To avoid portability problems we use strlen() here.
+ vim_snprintf(buffer, RULER_BUF_LEN, "%" PRId64 ",",
+ (wp->w_buffer->b_ml.ml_flags &
+ ML_EMPTY) ? (int64_t)0L : (int64_t)wp->w_cursor.lnum);
+ size_t len = STRLEN(buffer);
+ col_print(buffer + len, RULER_BUF_LEN - len,
+ empty_line ? 0 : (int)wp->w_cursor.col + 1,
+ (int)virtcol + 1);
+
+ // Add a "50%" if there is room for it.
+ // On the last line, don't print in the last column (scrolls the
+ // screen up on some terminals).
+ int i = (int)STRLEN(buffer);
+ get_rel_pos(wp, buffer + i + 1, RULER_BUF_LEN - i - 1);
+ int o = i + vim_strsize(buffer + i + 1);
+ if (wp->w_status_height == 0 && !is_stl_global) { // can't use last char of screen
+ o++;
+ }
+ int this_ru_col = ru_col - (Columns - width);
+ if (this_ru_col < 0) {
+ this_ru_col = 0;
+ }
+ // Never use more than half the window/screen width, leave the other half
+ // for the filename.
+ if (this_ru_col < (width + 1) / 2) {
+ this_ru_col = (width + 1) / 2;
+ }
+ if (this_ru_col + o < width) {
+ // Need at least 3 chars left for get_rel_pos() + NUL.
+ while (this_ru_col + o < width && RULER_BUF_LEN > i + 4) {
+ i += utf_char2bytes(fillchar, buffer + i);
+ o++;
+ }
+ get_rel_pos(wp, buffer + i, RULER_BUF_LEN - i);
+ }
+
+ if (ui_has(kUIMessages) && !part_of_status) {
+ MAXSIZE_TEMP_ARRAY(content, 1);
+ MAXSIZE_TEMP_ARRAY(chunk, 2);
+ ADD_C(chunk, INTEGER_OBJ(attr));
+ ADD_C(chunk, STRING_OBJ(cstr_as_string((char *)buffer)));
+ ADD_C(content, ARRAY_OBJ(chunk));
+ ui_call_msg_ruler(content);
+ did_show_ext_ruler = true;
+ } else {
+ if (did_show_ext_ruler) {
+ ui_call_msg_ruler((Array)ARRAY_DICT_INIT);
+ did_show_ext_ruler = false;
+ }
+ // Truncate at window boundary.
+ o = 0;
+ for (i = 0; buffer[i] != NUL; i += utfc_ptr2len(buffer + i)) {
+ o += utf_ptr2cells(buffer + i);
+ if (this_ru_col + o > width) {
+ buffer[i] = NUL;
+ break;
+ }
+ }
+
+ ScreenGrid *grid = part_of_status ? &default_grid : &msg_grid_adj;
+ grid_puts(grid, (char_u *)buffer, row, this_ru_col + off, attr);
+ grid_fill(grid, row, row + 1,
+ this_ru_col + off + (int)STRLEN(buffer), off + width, fillchar,
+ fillchar, attr);
+ }
+
+ wp->w_ru_cursor = wp->w_cursor;
+ wp->w_ru_virtcol = wp->w_virtcol;
+ wp->w_ru_empty = (char)empty_line;
+ wp->w_ru_topline = wp->w_topline;
+ wp->w_ru_line_count = wp->w_buffer->b_ml.ml_line_count;
+ wp->w_ru_topfill = wp->w_topfill;
+ }
+}
+
+/// Get the character to use in a status line. Get its attributes in "*attr".
+int fillchar_status(int *attr, win_T *wp)
+{
+ int fill;
+ bool is_curwin = (wp == curwin);
+ if (is_curwin) {
+ *attr = win_hl_attr(wp, HLF_S);
+ fill = wp->w_p_fcs_chars.stl;
+ } else {
+ *attr = win_hl_attr(wp, HLF_SNC);
+ fill = wp->w_p_fcs_chars.stlnc;
+ }
+ // Use fill when there is highlighting, and highlighting of current
+ // window differs, or the fillchars differ, or this is not the
+ // current window
+ if (*attr != 0 && ((win_hl_attr(wp, HLF_S) != win_hl_attr(wp, HLF_SNC)
+ || !is_curwin || ONE_WINDOW)
+ || (wp->w_p_fcs_chars.stl != wp->w_p_fcs_chars.stlnc))) {
+ return fill;
+ }
+ if (is_curwin) {
+ return '^';
+ }
+ return '=';
+}
+
+/// Redraw the status line according to 'statusline' and take care of any
+/// errors encountered.
+void redraw_custom_statusline(win_T *wp)
+{
+ static bool entered = false;
+ int saved_did_emsg = did_emsg;
+
+ // When called recursively return. This can happen when the statusline
+ // contains an expression that triggers a redraw.
+ if (entered) {
+ return;
+ }
+ entered = true;
+
+ did_emsg = false;
+ win_redr_custom(wp, false, false);
+ if (did_emsg) {
+ // When there is an error disable the statusline, otherwise the
+ // display is messed up with errors and a redraw triggers the problem
+ // again and again.
+ set_string_option_direct("statusline", -1, "",
+ OPT_FREE | (*wp->w_p_stl != NUL
+ ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR);
+ }
+ did_emsg |= saved_did_emsg;
+ entered = false;
+}
+
+/// Redraw the status line, window bar or ruler of window "wp".
+/// When "wp" is NULL redraw the tab pages line from 'tabline'.
+void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
+{
+ static bool entered = false;
+ int attr;
+ int curattr;
+ int row;
+ int col = 0;
+ int maxwidth;
+ int width;
+ int n;
+ int len;
+ int fillchar;
+ char buf[MAXPATHL];
+ char_u *stl;
+ char *p;
+ stl_hlrec_t *hltab;
+ StlClickRecord *tabtab;
+ int use_sandbox = false;
+ win_T *ewp;
+ int p_crb_save;
+ bool is_stl_global = global_stl_height() > 0;
+
+ ScreenGrid *grid = &default_grid;
+
+ // There is a tiny chance that this gets called recursively: When
+ // redrawing a status line triggers redrawing the ruler or tabline.
+ // Avoid trouble by not allowing recursion.
+ if (entered) {
+ return;
+ }
+ entered = true;
+
+ // setup environment for the task at hand
+ if (wp == NULL) {
+ // Use 'tabline'. Always at the first line of the screen.
+ stl = p_tal;
+ row = 0;
+ fillchar = ' ';
+ attr = HL_ATTR(HLF_TPF);
+ maxwidth = Columns;
+ use_sandbox = was_set_insecurely(wp, "tabline", 0);
+ } else if (draw_winbar) {
+ stl = (char_u *)((*wp->w_p_wbr != NUL) ? wp->w_p_wbr : p_wbr);
+ row = -1; // row zero is first row of text
+ col = 0;
+ grid = &wp->w_grid;
+ grid_adjust(&grid, &row, &col);
+
+ if (row < 0) {
+ return;
+ }
+
+ fillchar = wp->w_p_fcs_chars.wbr;
+ attr = (wp == curwin) ? win_hl_attr(wp, HLF_WBR) : win_hl_attr(wp, HLF_WBRNC);
+ maxwidth = wp->w_width_inner;
+ use_sandbox = was_set_insecurely(wp, "winbar", 0);
+
+ stl_clear_click_defs(wp->w_winbar_click_defs, (long)wp->w_winbar_click_defs_size);
+ // Allocate / resize the click definitions array for winbar if needed.
+ if (wp->w_winbar_height && wp->w_winbar_click_defs_size < (size_t)maxwidth) {
+ xfree(wp->w_winbar_click_defs);
+ wp->w_winbar_click_defs_size = (size_t)maxwidth;
+ wp->w_winbar_click_defs = xcalloc(wp->w_winbar_click_defs_size, sizeof(StlClickRecord));
+ }
+ } else {
+ row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp);
+ fillchar = fillchar_status(&attr, wp);
+ maxwidth = is_stl_global ? Columns : wp->w_width;
+
+ stl_clear_click_defs(wp->w_status_click_defs, (long)wp->w_status_click_defs_size);
+ // Allocate / resize the click definitions array for statusline if needed.
+ if (wp->w_status_click_defs_size < (size_t)maxwidth) {
+ xfree(wp->w_status_click_defs);
+ wp->w_status_click_defs_size = (size_t)maxwidth;
+ wp->w_status_click_defs = xcalloc(wp->w_status_click_defs_size, sizeof(StlClickRecord));
+ }
+
+ if (draw_ruler) {
+ stl = p_ruf;
+ // advance past any leading group spec - implicit in ru_col
+ if (*stl == '%') {
+ if (*++stl == '-') {
+ stl++;
+ }
+ if (atoi((char *)stl)) {
+ while (ascii_isdigit(*stl)) {
+ stl++;
+ }
+ }
+ if (*stl++ != '(') {
+ stl = p_ruf;
+ }
+ }
+ col = ru_col - (Columns - maxwidth);
+ if (col < (maxwidth + 1) / 2) {
+ col = (maxwidth + 1) / 2;
+ }
+ maxwidth = maxwidth - col;
+ if (!wp->w_status_height && !is_stl_global) {
+ grid = &msg_grid_adj;
+ row = Rows - 1;
+ maxwidth--; // writing in last column may cause scrolling
+ fillchar = ' ';
+ attr = HL_ATTR(HLF_MSG);
+ }
+
+ use_sandbox = was_set_insecurely(wp, "rulerformat", 0);
+ } else {
+ if (*wp->w_p_stl != NUL) {
+ stl = wp->w_p_stl;
+ } else {
+ stl = p_stl;
+ }
+ use_sandbox = was_set_insecurely(wp, "statusline", *wp->w_p_stl == NUL ? 0 : OPT_LOCAL);
+ }
+
+ col += is_stl_global ? 0 : wp->w_wincol;
+ }
+
+ if (maxwidth <= 0) {
+ goto theend;
+ }
+
+ // Temporarily reset 'cursorbind', we don't want a side effect from moving
+ // the cursor away and back.
+ ewp = wp == NULL ? curwin : wp;
+ p_crb_save = ewp->w_p_crb;
+ ewp->w_p_crb = false;
+
+ // Make a copy, because the statusline may include a function call that
+ // might change the option value and free the memory.
+ stl = vim_strsave(stl);
+ width =
+ build_stl_str_hl(ewp, buf, sizeof(buf), (char *)stl, use_sandbox,
+ fillchar, maxwidth, &hltab, &tabtab);
+ xfree(stl);
+ ewp->w_p_crb = p_crb_save;
+
+ // Make all characters printable.
+ p = transstr(buf, true);
+ len = (int)STRLCPY(buf, p, sizeof(buf));
+ len = (size_t)len < sizeof(buf) ? len : (int)sizeof(buf) - 1;
+ xfree(p);
+
+ // fill up with "fillchar"
+ while (width < maxwidth && len < (int)sizeof(buf) - 1) {
+ len += utf_char2bytes(fillchar, buf + len);
+ width++;
+ }
+ buf[len] = NUL;
+
+ // Draw each snippet with the specified highlighting.
+ grid_puts_line_start(grid, row);
+
+ curattr = attr;
+ p = buf;
+ for (n = 0; hltab[n].start != NULL; n++) {
+ int textlen = (int)(hltab[n].start - p);
+ grid_puts_len(grid, (char_u *)p, textlen, row, col, curattr);
+ col += vim_strnsize((char_u *)p, textlen);
+ p = hltab[n].start;
+
+ if (hltab[n].userhl == 0) {
+ curattr = attr;
+ } else if (hltab[n].userhl < 0) {
+ curattr = syn_id2attr(-hltab[n].userhl);
+ } else if (wp != NULL && wp != curwin && wp->w_status_height != 0) {
+ curattr = highlight_stlnc[hltab[n].userhl - 1];
+ } else {
+ curattr = highlight_user[hltab[n].userhl - 1];
+ }
+ }
+ // Make sure to use an empty string instead of p, if p is beyond buf + len.
+ grid_puts(grid, p >= buf + len ? (char_u *)"" : (char_u *)p, row, col,
+ curattr);
+
+ grid_puts_line_flush(false);
+
+ // Fill the tab_page_click_defs, w_status_click_defs or w_winbar_click_defs array for clicking
+ // in the tab page line, status line or window bar
+ StlClickDefinition *click_defs = (wp == NULL) ? tab_page_click_defs
+ : draw_winbar ? wp->w_winbar_click_defs
+ : wp->w_status_click_defs;
+
+ if (click_defs == NULL) {
+ goto theend;
+ }
+
+ col = 0;
+ len = 0;
+ p = buf;
+ StlClickDefinition cur_click_def = {
+ .type = kStlClickDisabled,
+ };
+ for (n = 0; tabtab[n].start != NULL; n++) {
+ len += vim_strnsize((char_u *)p, (int)(tabtab[n].start - p));
+ while (col < len) {
+ click_defs[col++] = cur_click_def;
+ }
+ p = (char *)tabtab[n].start;
+ cur_click_def = tabtab[n].def;
+ if ((wp != NULL) && !(cur_click_def.type == kStlClickDisabled
+ || cur_click_def.type == kStlClickFuncRun)) {
+ // window bar and status line only support click functions
+ cur_click_def.type = kStlClickDisabled;
+ }
+ }
+ while (col < maxwidth) {
+ click_defs[col++] = cur_click_def;
+ }
+
+theend:
+ entered = false;
+}
+
+/// Build a string from the status line items in "fmt".
+/// Return length of string in screen cells.
+///
+/// Normally works for window "wp", except when working for 'tabline' then it
+/// is "curwin".
+///
+/// Items are drawn interspersed with the text that surrounds it
+/// Specials: %-<wid>(xxx%) => group, %= => separation marker, %< => truncation
+/// Item: %-<minwid>.<maxwid><itemch> All but <itemch> are optional
+///
+/// If maxwidth is not zero, the string will be filled at any middle marker
+/// or truncated if too long, fillchar is used for all whitespace.
+///
+/// @param wp The window to build a statusline for
+/// @param out The output buffer to write the statusline to
+/// Note: This should not be NameBuff
+/// @param outlen The length of the output buffer
+/// @param fmt The statusline format string
+/// @param use_sandbox Use a sandboxed environment when evaluating fmt
+/// @param fillchar Character to use when filling empty space in the statusline
+/// @param maxwidth The maximum width to make the statusline
+/// @param hltab HL attributes (can be NULL)
+/// @param tabtab Tab clicks definition (can be NULL).
+///
+/// @return The final width of the statusline
+int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_sandbox, int fillchar,
+ int maxwidth, stl_hlrec_t **hltab, StlClickRecord **tabtab)
+{
+ static size_t stl_items_len = 20; // Initial value, grows as needed.
+ static stl_item_t *stl_items = NULL;
+ static int *stl_groupitems = NULL;
+ static stl_hlrec_t *stl_hltab = NULL;
+ static StlClickRecord *stl_tabtab = NULL;
+ static int *stl_separator_locations = NULL;
+
+#define TMPLEN 70
+ char buf_tmp[TMPLEN];
+ char win_tmp[TMPLEN];
+ char *usefmt = fmt;
+ const int save_must_redraw = must_redraw;
+ const int save_redr_type = curwin->w_redr_type;
+
+ if (stl_items == NULL) {
+ stl_items = xmalloc(sizeof(stl_item_t) * stl_items_len);
+ stl_groupitems = xmalloc(sizeof(int) * stl_items_len);
+
+ // Allocate one more, because the last element is used to indicate the
+ // end of the list.
+ stl_hltab = xmalloc(sizeof(stl_hlrec_t) * (stl_items_len + 1));
+ stl_tabtab = xmalloc(sizeof(StlClickRecord) * (stl_items_len + 1));
+
+ stl_separator_locations = xmalloc(sizeof(int) * stl_items_len);
+ }
+
+ // When the format starts with "%!" then evaluate it as an expression and
+ // use the result as the actual format string.
+ if (fmt[0] == '%' && fmt[1] == '!') {
+ typval_T tv = {
+ .v_type = VAR_NUMBER,
+ .vval.v_number = wp->handle,
+ };
+ set_var(S_LEN("g:statusline_winid"), &tv, false);
+
+ usefmt = eval_to_string_safe(fmt + 2, NULL, use_sandbox);
+ if (usefmt == NULL) {
+ usefmt = fmt;
+ }
+
+ do_unlet(S_LEN("g:statusline_winid"), true);
+ }
+
+ if (fillchar == 0) {
+ fillchar = ' ';
+ }
+
+ // The cursor in windows other than the current one isn't always
+ // up-to-date, esp. because of autocommands and timers.
+ linenr_T lnum = wp->w_cursor.lnum;
+ if (lnum > wp->w_buffer->b_ml.ml_line_count) {
+ lnum = wp->w_buffer->b_ml.ml_line_count;
+ wp->w_cursor.lnum = lnum;
+ }
+
+ // Get line & check if empty (cursorpos will show "0-1").
+ const char *line_ptr = (char *)ml_get_buf(wp->w_buffer, lnum, false);
+ bool empty_line = (*line_ptr == NUL);
+
+ // Get the byte value now, in case we need it below. This is more
+ // efficient than making a copy of the line.
+ int byteval;
+ const size_t len = STRLEN(line_ptr);
+ if (wp->w_cursor.col > (colnr_T)len) {
+ // Line may have changed since checking the cursor column, or the lnum
+ // was adjusted above.
+ wp->w_cursor.col = (colnr_T)len;
+ wp->w_cursor.coladd = 0;
+ byteval = 0;
+ } else {
+ byteval = utf_ptr2char(line_ptr + wp->w_cursor.col);
+ }
+
+ int groupdepth = 0;
+ int evaldepth = 0;
+
+ int curitem = 0;
+ bool prevchar_isflag = true;
+ bool prevchar_isitem = false;
+
+ // out_p is the current position in the output buffer
+ char *out_p = out;
+
+ // out_end_p is the last valid character in the output buffer
+ // Note: The null termination character must occur here or earlier,
+ // so any user-visible characters must occur before here.
+ char *out_end_p = (out + outlen) - 1;
+
+ // Proceed character by character through the statusline format string
+ // fmt_p is the current position in the input buffer
+ for (char *fmt_p = usefmt; *fmt_p != NUL;) {
+ if (curitem == (int)stl_items_len) {
+ size_t new_len = stl_items_len * 3 / 2;
+
+ stl_items = xrealloc(stl_items, sizeof(stl_item_t) * new_len);
+ stl_groupitems = xrealloc(stl_groupitems, sizeof(int) * new_len);
+ stl_hltab = xrealloc(stl_hltab, sizeof(stl_hlrec_t) * (new_len + 1));
+ stl_tabtab = xrealloc(stl_tabtab, sizeof(StlClickRecord) * (new_len + 1));
+ stl_separator_locations =
+ xrealloc(stl_separator_locations, sizeof(int) * new_len);
+
+ stl_items_len = new_len;
+ }
+
+ if (*fmt_p != '%') {
+ prevchar_isflag = prevchar_isitem = false;
+ }
+
+ // Copy the formatting verbatim until we reach the end of the string
+ // or find a formatting item (denoted by `%`)
+ // or run out of room in our output buffer.
+ while (*fmt_p != NUL && *fmt_p != '%' && out_p < out_end_p) {
+ *out_p++ = *fmt_p++;
+ }
+
+ // If we have processed the entire format string or run out of
+ // room in our output buffer, exit the loop.
+ if (*fmt_p == NUL || out_p >= out_end_p) {
+ break;
+ }
+
+ // The rest of this loop will handle a single `%` item.
+ // Note: We increment here to skip over the `%` character we are currently
+ // on so we can process the item's contents.
+ fmt_p++;
+
+ // Ignore `%` at the end of the format string
+ if (*fmt_p == NUL) {
+ break;
+ }
+
+ // Two `%` in a row is the escape sequence to print a
+ // single `%` in the output buffer.
+ if (*fmt_p == '%') {
+ *out_p++ = *fmt_p++;
+ prevchar_isflag = prevchar_isitem = false;
+ continue;
+ }
+
+ // STL_SEPARATE: Separation place between left and right aligned items.
+ if (*fmt_p == STL_SEPARATE) {
+ fmt_p++;
+ // Ignored when we are inside of a grouping
+ if (groupdepth > 0) {
+ continue;
+ }
+ stl_items[curitem].type = Separate;
+ stl_items[curitem++].start = out_p;
+ continue;
+ }
+
+ // STL_TRUNCMARK: Where to begin truncating if the statusline is too long.
+ if (*fmt_p == STL_TRUNCMARK) {
+ fmt_p++;
+ stl_items[curitem].type = Trunc;
+ stl_items[curitem++].start = out_p;
+ continue;
+ }
+
+ // The end of a grouping
+ if (*fmt_p == ')') {
+ fmt_p++;
+ // Ignore if we are not actually inside a group currently
+ if (groupdepth < 1) {
+ continue;
+ }
+ groupdepth--;
+
+ // Determine how long the group is.
+ // Note: We set the current output position to null
+ // so `vim_strsize` will work.
+ char *t = stl_items[stl_groupitems[groupdepth]].start;
+ *out_p = NUL;
+ long group_len = vim_strsize(t);
+
+ // If the group contained internal items
+ // and the group did not have a minimum width,
+ // and if there were no normal items in the group,
+ // move the output pointer back to where the group started.
+ // Note: This erases any non-item characters that were in the group.
+ // Otherwise there would be no reason to do this step.
+ if (curitem > stl_groupitems[groupdepth] + 1
+ && stl_items[stl_groupitems[groupdepth]].minwid == 0) {
+ // remove group if all items are empty and highlight group
+ // doesn't change
+ int group_start_userhl = 0;
+ int group_end_userhl = 0;
+ int n;
+ for (n = stl_groupitems[groupdepth] - 1; n >= 0; n--) {
+ if (stl_items[n].type == Highlight) {
+ group_start_userhl = group_end_userhl = stl_items[n].minwid;
+ break;
+ }
+ }
+ for (n = stl_groupitems[groupdepth] + 1; n < curitem; n++) {
+ if (stl_items[n].type == Normal) {
+ break;
+ }
+ if (stl_items[n].type == Highlight) {
+ group_end_userhl = stl_items[n].minwid;
+ }
+ }
+ if (n == curitem && group_start_userhl == group_end_userhl) {
+ // empty group
+ out_p = t;
+ group_len = 0;
+ for (n = stl_groupitems[groupdepth] + 1; n < curitem; n++) {
+ // do not use the highlighting from the removed group
+ if (stl_items[n].type == Highlight) {
+ stl_items[n].type = Empty;
+ }
+ // adjust the start position of TabPage to the next
+ // item position
+ if (stl_items[n].type == TabPage) {
+ stl_items[n].start = out_p;
+ }
+ }
+ }
+ }
+
+ // If the group is longer than it is allowed to be
+ // truncate by removing bytes from the start of the group text.
+ if (group_len > stl_items[stl_groupitems[groupdepth]].maxwid) {
+ // { Determine the number of bytes to remove
+
+ // Find the first character that should be included.
+ long n = 0;
+ while (group_len >= stl_items[stl_groupitems[groupdepth]].maxwid) {
+ group_len -= ptr2cells(t + n);
+ n += utfc_ptr2len(t + n);
+ }
+ // }
+
+ // Prepend the `<` to indicate that the output was truncated.
+ *t = '<';
+
+ // { Move the truncated output
+ memmove(t + 1, t + n, (size_t)(out_p - (t + n)));
+ out_p = out_p - n + 1;
+ // Fill up space left over by half a double-wide char.
+ while (++group_len < stl_items[stl_groupitems[groupdepth]].minwid) {
+ MB_CHAR2BYTES(fillchar, out_p);
+ }
+ // }
+
+ // correct the start of the items for the truncation
+ for (int idx = stl_groupitems[groupdepth] + 1; idx < curitem; idx++) {
+ // Shift everything back by the number of removed bytes
+ // Minus one for the leading '<' added above.
+ stl_items[idx].start -= n - 1;
+
+ // If the item was partially or completely truncated, set its
+ // start to the start of the group
+ if (stl_items[idx].start < t) {
+ stl_items[idx].start = t;
+ }
+ }
+ // If the group is shorter than the minimum width, add padding characters.
+ } else if (abs(stl_items[stl_groupitems[groupdepth]].minwid) > group_len) {
+ long min_group_width = stl_items[stl_groupitems[groupdepth]].minwid;
+ // If the group is left-aligned, add characters to the right.
+ if (min_group_width < 0) {
+ min_group_width = 0 - min_group_width;
+ while (group_len++ < min_group_width && out_p < out_end_p) {
+ MB_CHAR2BYTES(fillchar, out_p);
+ }
+ // If the group is right-aligned, shift everything to the right and
+ // prepend with filler characters.
+ } else {
+ // { Move the group to the right
+ group_len = (min_group_width - group_len) * utf_char2len(fillchar);
+ memmove(t + group_len, t, (size_t)(out_p - t));
+ if (out_p + group_len >= (out_end_p + 1)) {
+ group_len = (long)(out_end_p - out_p);
+ }
+ out_p += group_len;
+ // }
+
+ // Adjust item start positions
+ for (int n = stl_groupitems[groupdepth] + 1; n < curitem; n++) {
+ stl_items[n].start += group_len;
+ }
+
+ // Prepend the fill characters
+ for (; group_len > 0; group_len--) {
+ MB_CHAR2BYTES(fillchar, t);
+ }
+ }
+ }
+ continue;
+ }
+ int minwid = 0;
+ int maxwid = 9999;
+ bool left_align = false;
+
+ // Denotes that numbers should be left-padded with zeros
+ bool zeropad = (*fmt_p == '0');
+ if (zeropad) {
+ fmt_p++;
+ }
+
+ // Denotes that the item should be left-aligned.
+ // This is tracked by using a negative length.
+ if (*fmt_p == '-') {
+ fmt_p++;
+ left_align = true;
+ }
+
+ // The first digit group is the item's min width
+ if (ascii_isdigit(*fmt_p)) {
+ minwid = getdigits_int(&fmt_p, false, 0);
+ }
+
+ // User highlight groups override the min width field
+ // to denote the styling to use.
+ if (*fmt_p == STL_USER_HL) {
+ stl_items[curitem].type = Highlight;
+ stl_items[curitem].start = out_p;
+ stl_items[curitem].minwid = minwid > 9 ? 1 : minwid;
+ fmt_p++;
+ curitem++;
+ continue;
+ }
+
+ // TABPAGE pairs are used to denote a region that when clicked will
+ // either switch to or close a tab.
+ //
+ // Ex: tabline=%0Ttab\ zero%X
+ // This tabline has a TABPAGENR item with minwid `0`,
+ // which is then closed with a TABCLOSENR item.
+ // Clicking on this region with mouse enabled will switch to tab 0.
+ // Setting the minwid to a different value will switch
+ // to that tab, if it exists
+ //
+ // Ex: tabline=%1Xtab\ one%X
+ // This tabline has a TABCLOSENR item with minwid `1`,
+ // which is then closed with a TABCLOSENR item.
+ // Clicking on this region with mouse enabled will close tab 0.
+ // This is determined by the following formula:
+ // tab to close = (1 - minwid)
+ // This is because for TABPAGENR we use `minwid` = `tab number`.
+ // For TABCLOSENR we store the tab number as a negative value.
+ // Because 0 is a valid TABPAGENR value, we have to
+ // start our numbering at `-1`.
+ // So, `-1` corresponds to us wanting to close tab `0`
+ //
+ // Note: These options are only valid when creating a tabline.
+ if (*fmt_p == STL_TABPAGENR || *fmt_p == STL_TABCLOSENR) {
+ if (*fmt_p == STL_TABCLOSENR) {
+ if (minwid == 0) {
+ // %X ends the close label, go back to the previous tab label nr.
+ for (long n = curitem - 1; n >= 0; n--) {
+ if (stl_items[n].type == TabPage && stl_items[n].minwid >= 0) {
+ minwid = stl_items[n].minwid;
+ break;
+ }
+ }
+ } else {
+ // close nrs are stored as negative values
+ minwid = -minwid;
+ }
+ }
+ stl_items[curitem].type = TabPage;
+ stl_items[curitem].start = out_p;
+ stl_items[curitem].minwid = minwid;
+ fmt_p++;
+ curitem++;
+ continue;
+ }
+
+ if (*fmt_p == STL_CLICK_FUNC) {
+ fmt_p++;
+ char *t = fmt_p;
+ while (*fmt_p != STL_CLICK_FUNC && *fmt_p) {
+ fmt_p++;
+ }
+ if (*fmt_p != STL_CLICK_FUNC) {
+ break;
+ }
+ stl_items[curitem].type = ClickFunc;
+ stl_items[curitem].start = out_p;
+ stl_items[curitem].cmd = xmemdupz(t, (size_t)(fmt_p - t));
+ stl_items[curitem].minwid = minwid;
+ fmt_p++;
+ curitem++;
+ continue;
+ }
+
+ // Denotes the end of the minwid
+ // the maxwid may follow immediately after
+ if (*fmt_p == '.') {
+ fmt_p++;
+ if (ascii_isdigit(*fmt_p)) {
+ maxwid = getdigits_int(&fmt_p, false, 50);
+ }
+ }
+
+ // Bound the minimum width at 50.
+ // Make the number negative to denote left alignment of the item
+ minwid = (minwid > 50 ? 50 : minwid) * (left_align ? -1 : 1);
+
+ // Denotes the start of a new group
+ if (*fmt_p == '(') {
+ stl_groupitems[groupdepth++] = curitem;
+ stl_items[curitem].type = Group;
+ stl_items[curitem].start = out_p;
+ stl_items[curitem].minwid = minwid;
+ stl_items[curitem].maxwid = maxwid;
+ fmt_p++;
+ curitem++;
+ continue;
+ }
+
+ // Denotes end of expanded %{} block
+ if (*fmt_p == '}' && evaldepth > 0) {
+ fmt_p++;
+ evaldepth--;
+ continue;
+ }
+
+ // An invalid item was specified.
+ // Continue processing on the next character of the format string.
+ if (vim_strchr(STL_ALL, *fmt_p) == NULL) {
+ fmt_p++;
+ continue;
+ }
+
+ // The status line item type
+ char opt = *fmt_p++;
+
+ // OK - now for the real work
+ NumberBase base = kNumBaseDecimal;
+ bool itemisflag = false;
+ bool fillable = true;
+ long num = -1;
+ char *str = NULL;
+ switch (opt) {
+ case STL_FILEPATH:
+ case STL_FULLPATH:
+ case STL_FILENAME:
+ // Set fillable to false so that ' ' in the filename will not
+ // get replaced with the fillchar
+ fillable = false;
+ if (buf_spname(wp->w_buffer) != NULL) {
+ STRLCPY(NameBuff, buf_spname(wp->w_buffer), MAXPATHL);
+ } else {
+ char *t = (opt == STL_FULLPATH) ? wp->w_buffer->b_ffname
+ : wp->w_buffer->b_fname;
+ home_replace(wp->w_buffer, t, (char *)NameBuff, MAXPATHL, true);
+ }
+ trans_characters((char *)NameBuff, MAXPATHL);
+ if (opt != STL_FILENAME) {
+ str = (char *)NameBuff;
+ } else {
+ str = path_tail((char *)NameBuff);
+ }
+ break;
+ case STL_VIM_EXPR: // '{'
+ {
+ char *block_start = fmt_p - 1;
+ int reevaluate = (*fmt_p == '%');
+ itemisflag = true;
+
+ if (reevaluate) {
+ fmt_p++;
+ }
+
+ // Attempt to copy the expression to evaluate into
+ // the output buffer as a null-terminated string.
+ char *t = out_p;
+ while ((*fmt_p != '}' || (reevaluate && fmt_p[-1] != '%'))
+ && *fmt_p != NUL && out_p < out_end_p) {
+ *out_p++ = *fmt_p++;
+ }
+ if (*fmt_p != '}') { // missing '}' or out of space
+ break;
+ }
+ fmt_p++;
+ if (reevaluate) {
+ out_p[-1] = 0; // remove the % at the end of %{% expr %}
+ } else {
+ *out_p = 0;
+ }
+
+ // Move our position in the output buffer
+ // to the beginning of the expression
+ out_p = t;
+
+ // { Evaluate the expression
+
+ // Store the current buffer number as a string variable
+ vim_snprintf(buf_tmp, sizeof(buf_tmp), "%d", curbuf->b_fnum);
+ set_internal_string_var("g:actual_curbuf", buf_tmp);
+ vim_snprintf((char *)win_tmp, sizeof(win_tmp), "%d", curwin->handle);
+ set_internal_string_var("g:actual_curwin", (char *)win_tmp);
+
+ buf_T *const save_curbuf = curbuf;
+ win_T *const save_curwin = curwin;
+ const int save_VIsual_active = VIsual_active;
+ curwin = wp;
+ curbuf = wp->w_buffer;
+ // Visual mode is only valid in the current window.
+ if (curwin != save_curwin) {
+ VIsual_active = false;
+ }
+
+ // Note: The result stored in `t` is unused.
+ str = eval_to_string_safe(out_p, &t, use_sandbox);
+
+ curwin = save_curwin;
+ curbuf = save_curbuf;
+ VIsual_active = save_VIsual_active;
+
+ // Remove the variable we just stored
+ do_unlet(S_LEN("g:actual_curbuf"), true);
+ do_unlet(S_LEN("g:actual_curwin"), true);
+
+ // }
+
+ // Check if the evaluated result is a number.
+ // If so, convert the number to an int and free the string.
+ if (str != NULL && *str != 0) {
+ if (*skipdigits(str) == NUL) {
+ num = atoi(str);
+ XFREE_CLEAR(str);
+ itemisflag = false;
+ }
+ }
+
+ // If the output of the expression needs to be evaluated
+ // replace the %{} block with the result of evaluation
+ if (reevaluate && str != NULL && *str != 0
+ && strchr((const char *)str, '%') != NULL
+ && evaldepth < MAX_STL_EVAL_DEPTH) {
+ size_t parsed_usefmt = (size_t)(block_start - usefmt);
+ size_t str_length = STRLEN(str);
+ size_t fmt_length = STRLEN(fmt_p);
+ size_t new_fmt_len = parsed_usefmt + str_length + fmt_length + 3;
+ char *new_fmt = xmalloc(new_fmt_len * sizeof(char));
+ char *new_fmt_p = new_fmt;
+
+ new_fmt_p = (char *)memcpy(new_fmt_p, usefmt, parsed_usefmt) + parsed_usefmt;
+ new_fmt_p = (char *)memcpy(new_fmt_p, str, str_length) + str_length;
+ new_fmt_p = (char *)memcpy(new_fmt_p, "%}", 2) + 2;
+ new_fmt_p = (char *)memcpy(new_fmt_p, fmt_p, fmt_length) + fmt_length;
+ *new_fmt_p = 0;
+ new_fmt_p = NULL;
+
+ if (usefmt != fmt) {
+ xfree(usefmt);
+ }
+ XFREE_CLEAR(str);
+ usefmt = new_fmt;
+ fmt_p = usefmt + parsed_usefmt;
+ evaldepth++;
+ continue;
+ }
+ break;
+ }
+
+ case STL_LINE:
+ num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY)
+ ? 0L : (long)(wp->w_cursor.lnum);
+ break;
+
+ case STL_NUMLINES:
+ num = wp->w_buffer->b_ml.ml_line_count;
+ break;
+
+ case STL_COLUMN:
+ num = (State & MODE_INSERT) == 0 && empty_line ? 0 : (int)wp->w_cursor.col + 1;
+ break;
+
+ case STL_VIRTCOL:
+ case STL_VIRTCOL_ALT: {
+ colnr_T virtcol = wp->w_virtcol + 1;
+ // Don't display %V if it's the same as %c.
+ if (opt == STL_VIRTCOL_ALT
+ && (virtcol == (colnr_T)((State & MODE_INSERT) == 0 && empty_line
+ ? 0 : (int)wp->w_cursor.col + 1))) {
+ break;
+ }
+ num = (long)virtcol;
+ break;
+ }
+
+ case STL_PERCENTAGE:
+ num = (int)(((long)wp->w_cursor.lnum * 100L) /
+ (long)wp->w_buffer->b_ml.ml_line_count);
+ break;
+
+ case STL_ALTPERCENT:
+ // Store the position percentage in our temporary buffer.
+ // Note: We cannot store the value in `num` because
+ // `get_rel_pos` can return a named position. Ex: "Top"
+ get_rel_pos(wp, buf_tmp, TMPLEN);
+ str = buf_tmp;
+ break;
+
+ case STL_ARGLISTSTAT:
+ fillable = false;
+
+ // Note: This is important because `append_arg_number` starts appending
+ // at the end of the null-terminated string.
+ // Setting the first byte to null means it will place the argument
+ // number string at the beginning of the buffer.
+ buf_tmp[0] = 0;
+
+ // Note: The call will only return true if it actually
+ // appended data to the `buf_tmp` buffer.
+ if (append_arg_number(wp, buf_tmp, (int)sizeof(buf_tmp), false)) {
+ str = buf_tmp;
+ }
+ break;
+
+ case STL_KEYMAP:
+ fillable = false;
+ if (get_keymap_str(wp, "<%s>", buf_tmp, TMPLEN)) {
+ str = buf_tmp;
+ }
+ break;
+ case STL_PAGENUM:
+ num = printer_page_num;
+ break;
+
+ case STL_BUFNO:
+ num = wp->w_buffer->b_fnum;
+ break;
+
+ case STL_OFFSET_X:
+ base = kNumBaseHexadecimal;
+ FALLTHROUGH;
+ case STL_OFFSET: {
+ long l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL,
+ false);
+ num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) || l < 0 ?
+ 0L : l + 1 + ((State & MODE_INSERT) == 0 && empty_line ?
+ 0 : (int)wp->w_cursor.col);
+ break;
+ }
+ case STL_BYTEVAL_X:
+ base = kNumBaseHexadecimal;
+ FALLTHROUGH;
+ case STL_BYTEVAL:
+ num = byteval;
+ if (num == NL) {
+ num = 0;
+ } else if (num == CAR && get_fileformat(wp->w_buffer) == EOL_MAC) {
+ num = NL;
+ }
+ break;
+
+ case STL_ROFLAG:
+ case STL_ROFLAG_ALT:
+ itemisflag = true;
+ if (wp->w_buffer->b_p_ro) {
+ str = (opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]");
+ }
+ break;
+
+ case STL_HELPFLAG:
+ case STL_HELPFLAG_ALT:
+ itemisflag = true;
+ if (wp->w_buffer->b_help) {
+ str = (opt == STL_HELPFLAG_ALT) ? ",HLP" : _("[Help]");
+ }
+ break;
+
+ case STL_FILETYPE:
+ // Copy the filetype if it is not null and the formatted string will fit
+ // in the temporary buffer
+ // (including the brackets and null terminating character)
+ if (*wp->w_buffer->b_p_ft != NUL
+ && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 3) {
+ vim_snprintf(buf_tmp, sizeof(buf_tmp), "[%s]",
+ wp->w_buffer->b_p_ft);
+ str = buf_tmp;
+ }
+ break;
+
+ case STL_FILETYPE_ALT:
+ itemisflag = true;
+ // Copy the filetype if it is not null and the formatted string will fit
+ // in the temporary buffer
+ // (including the comma and null terminating character)
+ if (*wp->w_buffer->b_p_ft != NUL
+ && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 2) {
+ vim_snprintf(buf_tmp, sizeof(buf_tmp), ",%s", wp->w_buffer->b_p_ft);
+ // Uppercase the file extension
+ for (char *t = buf_tmp; *t != 0; t++) {
+ *t = (char)TOUPPER_LOC(*t);
+ }
+ str = buf_tmp;
+ }
+ break;
+ case STL_PREVIEWFLAG:
+ case STL_PREVIEWFLAG_ALT:
+ itemisflag = true;
+ if (wp->w_p_pvw) {
+ str = (opt == STL_PREVIEWFLAG_ALT) ? ",PRV" : _("[Preview]");
+ }
+ break;
+
+ case STL_QUICKFIX:
+ if (bt_quickfix(wp->w_buffer)) {
+ str = wp->w_llist_ref ? _(msg_loclist) : _(msg_qflist);
+ }
+ break;
+
+ case STL_MODIFIED:
+ case STL_MODIFIED_ALT:
+ itemisflag = true;
+ switch ((opt == STL_MODIFIED_ALT)
+ + bufIsChanged(wp->w_buffer) * 2
+ + (!MODIFIABLE(wp->w_buffer)) * 4) {
+ case 2:
+ str = "[+]"; break;
+ case 3:
+ str = ",+"; break;
+ case 4:
+ str = "[-]"; break;
+ case 5:
+ str = ",-"; break;
+ case 6:
+ str = "[+-]"; break;
+ case 7:
+ str = ",+-"; break;
+ }
+ break;
+
+ case STL_HIGHLIGHT: {
+ // { The name of the highlight is surrounded by `#`
+ char *t = fmt_p;
+ while (*fmt_p != '#' && *fmt_p != NUL) {
+ fmt_p++;
+ }
+ // }
+
+ // Create a highlight item based on the name
+ if (*fmt_p == '#') {
+ stl_items[curitem].type = Highlight;
+ stl_items[curitem].start = out_p;
+ stl_items[curitem].minwid = -syn_name2id_len(t, (size_t)(fmt_p - t));
+ curitem++;
+ fmt_p++;
+ }
+ continue;
+ }
+ }
+
+ // If we made it this far, the item is normal and starts at
+ // our current position in the output buffer.
+ // Non-normal items would have `continued`.
+ stl_items[curitem].start = out_p;
+ stl_items[curitem].type = Normal;
+
+ // Copy the item string into the output buffer
+ if (str != NULL && *str) {
+ // { Skip the leading `,` or ` ` if the item is a flag
+ // and the proper conditions are met
+ char *t = str;
+ if (itemisflag) {
+ if ((t[0] && t[1])
+ && ((!prevchar_isitem && *t == ',')
+ || (prevchar_isflag && *t == ' '))) {
+ t++;
+ }
+ prevchar_isflag = true;
+ }
+ // }
+
+ long l = vim_strsize(t);
+
+ // If this item is non-empty, record that the last thing
+ // we put in the output buffer was an item
+ if (l > 0) {
+ prevchar_isitem = true;
+ }
+
+ // If the item is too wide, truncate it from the beginning
+ if (l > maxwid) {
+ while (l >= maxwid) {
+ l -= ptr2cells(t);
+ t += utfc_ptr2len(t);
+ }
+
+ // Early out if there isn't enough room for the truncation marker
+ if (out_p >= out_end_p) {
+ break;
+ }
+
+ // Add the truncation marker
+ *out_p++ = '<';
+ }
+
+ // If the item is right aligned and not wide enough,
+ // pad with fill characters.
+ if (minwid > 0) {
+ for (; l < minwid && out_p < out_end_p; l++) {
+ // Don't put a "-" in front of a digit.
+ if (l + 1 == minwid && fillchar == '-' && ascii_isdigit(*t)) {
+ *out_p++ = ' ';
+ } else {
+ MB_CHAR2BYTES(fillchar, out_p);
+ }
+ }
+ minwid = 0;
+ } else {
+ // Note: The negative value denotes a left aligned item.
+ // Here we switch the minimum width back to a positive value.
+ minwid *= -1;
+ }
+
+ // { Copy the string text into the output buffer
+ for (; *t && out_p < out_end_p; t++) {
+ // Change a space by fillchar, unless fillchar is '-' and a
+ // digit follows.
+ if (fillable && *t == ' '
+ && (!ascii_isdigit(*(t + 1)) || fillchar != '-')) {
+ MB_CHAR2BYTES(fillchar, out_p);
+ } else {
+ *out_p++ = *t;
+ }
+ }
+ // }
+
+ // For left-aligned items, fill any remaining space with the fillchar
+ for (; l < minwid && out_p < out_end_p; l++) {
+ MB_CHAR2BYTES(fillchar, out_p);
+ }
+
+ // Otherwise if the item is a number, copy that to the output buffer.
+ } else if (num >= 0) {
+ if (out_p + 20 > out_end_p) {
+ break; // not sufficient space
+ }
+ prevchar_isitem = true;
+
+ // { Build the formatting string
+ char nstr[20];
+ char *t = nstr;
+ if (opt == STL_VIRTCOL_ALT) {
+ *t++ = '-';
+ minwid--;
+ }
+ *t++ = '%';
+ if (zeropad) {
+ *t++ = '0';
+ }
+
+ // Note: The `*` means we take the width as one of the arguments
+ *t++ = '*';
+ *t++ = base == kNumBaseHexadecimal ? 'X' : 'd';
+ *t = 0;
+ // }
+
+ // { Determine how many characters the number will take up when printed
+ // Note: We have to cast the base because the compiler uses
+ // unsigned ints for the enum values.
+ long num_chars = 1;
+ for (long n = num; n >= (int)base; n /= (int)base) {
+ num_chars++;
+ }
+
+ // VIRTCOL_ALT takes up an extra character because
+ // of the `-` we added above.
+ if (opt == STL_VIRTCOL_ALT) {
+ num_chars++;
+ }
+ // }
+
+ assert(out_end_p >= out_p);
+ size_t remaining_buf_len = (size_t)(out_end_p - out_p) + 1;
+
+ // If the number is going to take up too much room
+ // Figure out the approximate number in "scientific" type notation.
+ // Ex: 14532 with maxwid of 4 -> '14>3'
+ if (num_chars > maxwid) {
+ // Add two to the width because the power piece will take
+ // two extra characters
+ num_chars += 2;
+
+ // How many extra characters there are
+ long n = num_chars - maxwid;
+
+ // { Reduce the number by base^n
+ while (num_chars-- > maxwid) {
+ num /= (long)base;
+ }
+ // }
+
+ // { Add the format string for the exponent bit
+ *t++ = '>';
+ *t++ = '%';
+ // Use the same base as the first number
+ *t = t[-3];
+ *++t = 0;
+ // }
+
+ vim_snprintf(out_p, remaining_buf_len, nstr, 0, num, n);
+ } else {
+ vim_snprintf(out_p, remaining_buf_len, nstr, minwid, num);
+ }
+
+ // Advance the output buffer position to the end of the
+ // number we just printed
+ out_p += STRLEN(out_p);
+
+ // Otherwise, there was nothing to print so mark the item as empty
+ } else {
+ stl_items[curitem].type = Empty;
+ }
+
+ // Only free the string buffer if we allocated it.
+ // Note: This is not needed if `str` is pointing at `tmp`
+ if (opt == STL_VIM_EXPR) {
+ XFREE_CLEAR(str);
+ }
+
+ if (num >= 0 || (!itemisflag && str && *str)) {
+ prevchar_isflag = false; // Item not NULL, but not a flag
+ }
+
+ // Item processed, move to the next
+ curitem++;
+ }
+
+ *out_p = NUL;
+ int itemcnt = curitem;
+
+ // Free the format buffer if we allocated it internally
+ if (usefmt != fmt) {
+ xfree(usefmt);
+ }
+
+ // We have now processed the entire statusline format string.
+ // What follows is post-processing to handle alignment and highlighting.
+
+ int width = vim_strsize(out);
+ if (maxwidth > 0 && width > maxwidth) {
+ // Result is too long, must truncate somewhere.
+ int item_idx = 0;
+ char *trunc_p;
+
+ // If there are no items, truncate from beginning
+ if (itemcnt == 0) {
+ trunc_p = out;
+
+ // Otherwise, look for the truncation item
+ } else {
+ // Default to truncating at the first item
+ trunc_p = stl_items[0].start;
+ item_idx = 0;
+
+ for (int i = 0; i < itemcnt; i++) {
+ if (stl_items[i].type == Trunc) {
+ // Truncate at %< stl_items.
+ trunc_p = stl_items[i].start;
+ item_idx = i;
+ break;
+ }
+ }
+ }
+
+ // If the truncation point we found is beyond the maximum
+ // length of the string, truncate the end of the string.
+ if (width - vim_strsize(trunc_p) >= maxwidth) {
+ // Walk from the beginning of the
+ // string to find the last character that will fit.
+ trunc_p = out;
+ width = 0;
+ for (;;) {
+ width += ptr2cells(trunc_p);
+ if (width >= maxwidth) {
+ break;
+ }
+
+ // Note: Only advance the pointer if the next
+ // character will fit in the available output space
+ trunc_p += utfc_ptr2len(trunc_p);
+ }
+
+ // Ignore any items in the statusline that occur after
+ // the truncation point
+ for (int i = 0; i < itemcnt; i++) {
+ if (stl_items[i].start > trunc_p) {
+ itemcnt = i;
+ break;
+ }
+ }
+
+ // Truncate the output
+ *trunc_p++ = '>';
+ *trunc_p = 0;
+
+ // Truncate at the truncation point we found
+ } else {
+ // { Determine how many bytes to remove
+ long trunc_len = 0;
+ while (width >= maxwidth) {
+ width -= ptr2cells(trunc_p + trunc_len);
+ trunc_len += utfc_ptr2len(trunc_p + trunc_len);
+ }
+ // }
+
+ // { Truncate the string
+ char *trunc_end_p = trunc_p + trunc_len;
+ STRMOVE(trunc_p + 1, trunc_end_p);
+
+ // Put a `<` to mark where we truncated at
+ *trunc_p = '<';
+
+ if (width + 1 < maxwidth) {
+ // Advance the pointer to the end of the string
+ trunc_p = trunc_p + STRLEN(trunc_p);
+ }
+
+ // Fill up for half a double-wide character.
+ while (++width < maxwidth) {
+ MB_CHAR2BYTES(fillchar, trunc_p);
+ *trunc_p = NUL;
+ }
+ // }
+
+ // { Change the start point for items based on
+ // their position relative to our truncation point
+
+ // Note: The offset is one less than the truncation length because
+ // the truncation marker `<` is not counted.
+ long item_offset = trunc_len - 1;
+
+ for (int i = item_idx; i < itemcnt; i++) {
+ // Items starting at or after the end of the truncated section need
+ // to be moved backwards.
+ if (stl_items[i].start >= trunc_end_p) {
+ stl_items[i].start -= item_offset;
+ // Anything inside the truncated area is set to start
+ // at the `<` truncation character.
+ } else {
+ stl_items[i].start = trunc_p;
+ }
+ }
+ // }
+ }
+ width = maxwidth;
+
+ // If there is room left in our statusline, and room left in our buffer,
+ // add characters at the separate marker (if there is one) to
+ // fill up the available space.
+ } else if (width < maxwidth
+ && STRLEN(out) + (size_t)(maxwidth - width) + 1 < outlen) {
+ // Find how many separators there are, which we will use when
+ // figuring out how many groups there are.
+ int num_separators = 0;
+ for (int i = 0; i < itemcnt; i++) {
+ if (stl_items[i].type == Separate) {
+ // Create an array of the start location for each
+ // separator mark.
+ stl_separator_locations[num_separators] = i;
+ num_separators++;
+ }
+ }
+
+ // If we have separated groups, then we deal with it now
+ if (num_separators) {
+ int standard_spaces = (maxwidth - width) / num_separators;
+ int final_spaces = (maxwidth - width) -
+ standard_spaces * (num_separators - 1);
+
+ for (int i = 0; i < num_separators; i++) {
+ int dislocation = (i == (num_separators - 1)) ? final_spaces : standard_spaces;
+ dislocation *= utf_char2len(fillchar);
+ char *start = stl_items[stl_separator_locations[i]].start;
+ char *seploc = start + dislocation;
+ STRMOVE(seploc, start);
+ for (char *s = start; s < seploc;) {
+ MB_CHAR2BYTES(fillchar, s);
+ }
+
+ for (int item_idx = stl_separator_locations[i] + 1;
+ item_idx < itemcnt;
+ item_idx++) {
+ stl_items[item_idx].start += dislocation;
+ }
+ }
+
+ width = maxwidth;
+ }
+ }
+
+ // Store the info about highlighting.
+ if (hltab != NULL) {
+ *hltab = stl_hltab;
+ stl_hlrec_t *sp = stl_hltab;
+ for (long l = 0; l < itemcnt; l++) {
+ if (stl_items[l].type == Highlight) {
+ sp->start = stl_items[l].start;
+ sp->userhl = stl_items[l].minwid;
+ sp++;
+ }
+ }
+ sp->start = NULL;
+ sp->userhl = 0;
+ }
+
+ // Store the info about tab pages labels.
+ if (tabtab != NULL) {
+ *tabtab = stl_tabtab;
+ StlClickRecord *cur_tab_rec = stl_tabtab;
+ for (long l = 0; l < itemcnt; l++) {
+ if (stl_items[l].type == TabPage) {
+ cur_tab_rec->start = stl_items[l].start;
+ if (stl_items[l].minwid == 0) {
+ cur_tab_rec->def.type = kStlClickDisabled;
+ cur_tab_rec->def.tabnr = 0;
+ } else {
+ int tabnr = stl_items[l].minwid;
+ if (stl_items[l].minwid > 0) {
+ cur_tab_rec->def.type = kStlClickTabSwitch;
+ } else {
+ cur_tab_rec->def.type = kStlClickTabClose;
+ tabnr = -tabnr;
+ }
+ cur_tab_rec->def.tabnr = tabnr;
+ }
+ cur_tab_rec->def.func = NULL;
+ cur_tab_rec++;
+ } else if (stl_items[l].type == ClickFunc) {
+ cur_tab_rec->start = stl_items[l].start;
+ cur_tab_rec->def.type = kStlClickFuncRun;
+ cur_tab_rec->def.tabnr = stl_items[l].minwid;
+ cur_tab_rec->def.func = stl_items[l].cmd;
+ cur_tab_rec++;
+ }
+ }
+ cur_tab_rec->start = NULL;
+ cur_tab_rec->def.type = kStlClickDisabled;
+ cur_tab_rec->def.tabnr = 0;
+ cur_tab_rec->def.func = NULL;
+ }
+
+ // When inside update_screen we do not want redrawing a statusline, ruler,
+ // title, etc. to trigger another redraw, it may cause an endless loop.
+ if (updating_screen) {
+ must_redraw = save_must_redraw;
+ curwin->w_redr_type = save_redr_type;
+ }
+
+ return width;
+}
diff --git a/src/nvim/statusline.h b/src/nvim/statusline.h
new file mode 100644
index 0000000000..357a9a821f
--- /dev/null
+++ b/src/nvim/statusline.h
@@ -0,0 +1,10 @@
+#ifndef NVIM_STATUSLINE_H
+#define NVIM_STATUSLINE_H
+
+#include "nvim/buffer_defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "statusline.h.generated.h"
+#endif
+
+#endif // NVIM_STATUSLINE_H
diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim
index 6b16e888a9..fcd3d5724c 100644
--- a/src/nvim/testdir/runtest.vim
+++ b/src/nvim/testdir/runtest.vim
@@ -257,6 +257,7 @@ endfunc
func EarlyExit(test)
" It's OK for the test we use to test the quit detection.
if a:test != 'Test_zz_quit_detected()'
+ call add(v:errors, v:errmsg)
call add(v:errors, 'Test caused Vim to exit: ' . a:test)
endif
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index 89e3c9927b..716511210d 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -2737,6 +2737,59 @@ func Test_autocmd_sigusr1()
unlet g:sigusr1_passed
endfunc
+" Test for BufReadPre autocmd deleting the file
+func Test_BufReadPre_delfile()
+ augroup TestAuCmd
+ au!
+ autocmd BufReadPre Xfile call delete('Xfile')
+ augroup END
+ call writefile([], 'Xfile')
+ call assert_fails('new Xfile', 'E200:')
+ call assert_equal('Xfile', @%)
+ call assert_equal(1, &readonly)
+ call delete('Xfile')
+ augroup TestAuCmd
+ au!
+ augroup END
+ close!
+endfunc
+
+" Test for BufReadPre autocmd changing the current buffer
+func Test_BufReadPre_changebuf()
+ augroup TestAuCmd
+ au!
+ autocmd BufReadPre Xfile edit Xsomeotherfile
+ augroup END
+ call writefile([], 'Xfile')
+ call assert_fails('new Xfile', 'E201:')
+ call assert_equal('Xsomeotherfile', @%)
+ call assert_equal(1, &readonly)
+ call delete('Xfile')
+ augroup TestAuCmd
+ au!
+ augroup END
+ close!
+endfunc
+
+" Test for BufWipeouti autocmd changing the current buffer when reading a file
+" in an empty buffer with 'f' flag in 'cpo'
+func Test_BufDelete_changebuf()
+ new
+ augroup TestAuCmd
+ au!
+ autocmd BufWipeout * let bufnr = bufadd('somefile') | exe "b " .. bufnr
+ augroup END
+ let save_cpo = &cpo
+ set cpo+=f
+ call assert_fails('r Xfile', 'E484:')
+ call assert_equal('somefile', @%)
+ let &cpo = save_cpo
+ augroup TestAuCmd
+ au!
+ augroup END
+ close!
+endfunc
+
" Test for the temporary internal window used to execute autocmds
func Test_autocmd_window()
%bw!
diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim
index a09346a595..e26bbdc5be 100644
--- a/src/nvim/testdir/test_edit.vim
+++ b/src/nvim/testdir/test_edit.vim
@@ -262,22 +262,6 @@ func Test_edit_09()
bw!
endfunc
-func Test_edit_10()
- " Test for starting selectmode
- new
- set selectmode=key keymodel=startsel
- call setline(1, ['abc', 'def', 'ghi'])
- call cursor(1, 4)
- call feedkeys("A\<s-home>start\<esc>", 'txin')
- call assert_equal(['startdef', 'ghi'], getline(1, '$'))
- " start select mode again with gv
- set selectmode=cmd
- call feedkeys('gvabc', 'xt')
- call assert_equal('abctdef', getline(1))
- set selectmode= keymodel=
- bw!
-endfunc
-
func Test_edit_11()
" Test that indenting kicks in
new
@@ -1620,6 +1604,7 @@ func Test_edit_InsertLeave_undo()
bwipe!
au! InsertLeave
call delete('XtestUndo')
+ call delete(undofile('XtestUndo'))
set undofile&
endfunc
@@ -1687,11 +1672,11 @@ func Test_edit_noesckeys()
endfunc
" Test for running an invalid ex command in insert mode using CTRL-O
-" Note that vim has a hard-coded sleep of 3 seconds. So this test will take
-" more than 3 seconds to complete.
func Test_edit_ctrl_o_invalid_cmd()
new
set showmode showcmd
+ " Avoid a sleep of 3 seconds. Zero might have side effects.
+ " call test_override('ui_delay', 50)
let caught_e492 = 0
try
call feedkeys("i\<C-O>:invalid\<CR>abc\<Esc>", "xt")
@@ -1701,6 +1686,18 @@ func Test_edit_ctrl_o_invalid_cmd()
call assert_equal(1, caught_e492)
call assert_equal('abc', getline(1))
set showmode& showcmd&
+ " call test_override('ui_delay', 0)
+ close!
+endfunc
+
+" Test for editing a file with a very long name
+func Test_edit_illegal_filename()
+ CheckEnglish
+ new
+ redir => msg
+ exe 'edit ' . repeat('f', 5000)
+ redir END
+ call assert_match("Illegal file name$", split(msg, "\n")[0])
close!
endfunc
@@ -1763,6 +1760,102 @@ func Test_edit_is_a_directory()
call delete(dirname, 'rf')
endfunc
+" Test for editing a file using invalid file encoding
+func Test_edit_invalid_encoding()
+ CheckEnglish
+ call writefile([], 'Xfile')
+ redir => msg
+ new ++enc=axbyc Xfile
+ redir END
+ call assert_match('\[NOT converted\]', msg)
+ call delete('Xfile')
+ close!
+endfunc
+
+" Test for the "charconvert" option
+func Test_edit_charconvert()
+ CheckEnglish
+ call writefile(['one', 'two'], 'Xfile')
+
+ " set 'charconvert' to a non-existing function
+ set charconvert=NonExitingFunc()
+ new
+ let caught_e117 = v:false
+ try
+ redir => msg
+ edit ++enc=axbyc Xfile
+ catch /E117:/
+ let caught_e117 = v:true
+ finally
+ redir END
+ endtry
+ call assert_true(caught_e117)
+ call assert_equal(['one', 'two'], getline(1, '$'))
+ call assert_match("Conversion with 'charconvert' failed", msg)
+ close!
+ set charconvert&
+
+ " 'charconvert' function doesn't create a output file
+ func Cconv1()
+ endfunc
+ set charconvert=Cconv1()
+ new
+ redir => msg
+ edit ++enc=axbyc Xfile
+ redir END
+ call assert_equal(['one', 'two'], getline(1, '$'))
+ call assert_match("can't read output of 'charconvert'", msg)
+ close!
+ delfunc Cconv1
+ set charconvert&
+
+ " 'charconvert' function to convert to upper case
+ func Cconv2()
+ let data = readfile(v:fname_in)
+ call map(data, 'toupper(v:val)')
+ call writefile(data, v:fname_out)
+ endfunc
+ set charconvert=Cconv2()
+ new Xfile
+ write ++enc=ucase Xfile1
+ call assert_equal(['ONE', 'TWO'], readfile('Xfile1'))
+ call delete('Xfile1')
+ close!
+ delfunc Cconv2
+ set charconvert&
+
+ " 'charconvert' function removes the input file
+ func Cconv3()
+ call delete(v:fname_in)
+ endfunc
+ set charconvert=Cconv3()
+ new
+ call assert_fails('edit ++enc=lcase Xfile', 'E202:')
+ call assert_equal([''], getline(1, '$'))
+ close!
+ delfunc Cconv3
+ set charconvert&
+
+ call delete('Xfile')
+endfunc
+
+" Test for editing a file without read permission
+func Test_edit_file_no_read_perm()
+ CheckUnix
+ CheckNotBSD
+ call writefile(['one', 'two'], 'Xfile')
+ call setfperm('Xfile', '-w-------')
+ new
+ redir => msg
+ edit Xfile
+ redir END
+ call assert_equal(1, &readonly)
+ call assert_equal([''], getline(1, '$'))
+ call assert_match('\[Permission Denied\]', msg)
+ close!
+ call delete('Xfile')
+endfunc
+
" Using :edit without leaving 'insertmode' should not cause Insert mode to be
" re-entered immediately after <C-L>
func Test_edit_insertmode_ex_edit()
diff --git a/src/nvim/testdir/test_filechanged.vim b/src/nvim/testdir/test_filechanged.vim
index c6e781a1ef..b77f02afd1 100644
--- a/src/nvim/testdir/test_filechanged.vim
+++ b/src/nvim/testdir/test_filechanged.vim
@@ -242,6 +242,15 @@ func Test_file_changed_dialog()
call assert_equal(1, line('$'))
call assert_equal('new line', getline(1))
+ " File created after starting to edit it
+ call delete('Xchanged_d')
+ new Xchanged_d
+ call writefile(['one'], 'Xchanged_d')
+ call feedkeys('L', 'L')
+ checktime Xchanged_d
+ call assert_equal(['one'], getline(1, '$'))
+ close!
+
bwipe!
call delete('Xchanged_d')
endfunc
diff --git a/src/nvim/testdir/test_global.vim b/src/nvim/testdir/test_global.vim
index 947f7efc7c..cb6851250c 100644
--- a/src/nvim/testdir/test_global.vim
+++ b/src/nvim/testdir/test_global.vim
@@ -9,7 +9,10 @@ func Test_yank_put_clipboard()
set clipboard=unnamed
g/^/normal yyp
call assert_equal(['a', 'a', 'b', 'b', 'c', 'c'], getline(1, 6))
-
+ set clipboard=unnamed,unnamedplus
+ call setline(1, ['a', 'b', 'c'])
+ g/^/normal yyp
+ call assert_equal(['a', 'a', 'b', 'b', 'c', 'c'], getline(1, 6))
set clipboard&
bwipe!
endfunc
diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim
index f125c860d5..2092b508ea 100644
--- a/src/nvim/testdir/test_normal.vim
+++ b/src/nvim/testdir/test_normal.vim
@@ -124,31 +124,6 @@ func Test_normal01_keymodel()
bw!
endfunc
-" Test for select mode
-func Test_normal02_selectmode()
- call Setup_NewWindow()
- 50
- norm! gHy
- call assert_equal('y51', getline('.'))
- call setline(1, range(1,100))
- 50
- exe ":norm! V9jo\<c-g>y"
- call assert_equal('y60', getline('.'))
- " clean up
- bw!
-endfunc
-
-func Test_normal02_selectmode2()
- " some basic select mode tests
- call Setup_NewWindow()
- 50
- " call feedkeys(":set im\n\<c-o>gHc\<c-o>:set noim\n", 'tx')
- call feedkeys("i\<c-o>gHc\<esc>", 'tx')
- call assert_equal('c51', getline('.'))
- " clean up
- bw!
-endfunc
-
func Test_normal03_join()
" basic join test
call Setup_NewWindow()
@@ -377,6 +352,70 @@ func Test_normal09a_operatorfunc()
norm V10j,,
call assert_equal(22, g:a)
+ " Use a lambda function for 'opfunc'
+ unmap <buffer> ,,
+ call cursor(1, 1)
+ let g:a=0
+ nmap <buffer><silent> ,, :set opfunc={type\ ->\ CountSpaces(type)}<CR>g@
+ vmap <buffer><silent> ,, :<C-U>call CountSpaces(visualmode(), 1)<CR>
+ 50
+ norm V2j,,
+ call assert_equal(6, g:a)
+ norm V,,
+ call assert_equal(2, g:a)
+ norm ,,l
+ call assert_equal(0, g:a)
+ 50
+ exe "norm 0\<c-v>10j2l,,"
+ call assert_equal(11, g:a)
+ 50
+ norm V10j,,
+ call assert_equal(22, g:a)
+
+ " use a partial function for 'opfunc'
+ let g:OpVal = 0
+ func! Test_opfunc1(x, y, type)
+ let g:OpVal = a:x + a:y
+ endfunc
+ set opfunc=function('Test_opfunc1',\ [5,\ 7])
+ normal! g@l
+ call assert_equal(12, g:OpVal)
+ " delete the function and try to use g@
+ delfunc Test_opfunc1
+ call test_garbagecollect_now()
+ call assert_fails('normal! g@l', 'E117:')
+ set opfunc=
+
+ " use a funcref for 'opfunc'
+ let g:OpVal = 0
+ func! Test_opfunc2(x, y, type)
+ let g:OpVal = a:x + a:y
+ endfunc
+ set opfunc=funcref('Test_opfunc2',\ [4,\ 3])
+ normal! g@l
+ call assert_equal(7, g:OpVal)
+ " delete the function and try to use g@
+ delfunc Test_opfunc2
+ call test_garbagecollect_now()
+ call assert_fails('normal! g@l', 'E933:')
+ set opfunc=
+
+ " Try to use a function with two arguments for 'operatorfunc'
+ let g:OpVal = 0
+ func! Test_opfunc3(x, y)
+ let g:OpVal = 4
+ endfunc
+ set opfunc=Test_opfunc3
+ call assert_fails('normal! g@l', 'E119:')
+ call assert_equal(0, g:OpVal)
+ set opfunc=
+ delfunc Test_opfunc3
+ unlet g:OpVal
+
+ " Try to use a lambda function with two arguments for 'operatorfunc'
+ set opfunc={x,\ y\ ->\ 'done'}
+ call assert_fails('normal! g@l', 'E119:')
+
" clean up
unmap <buffer> ,,
set opfunc=
diff --git a/src/nvim/testdir/test_selectmode.vim b/src/nvim/testdir/test_selectmode.vim
index b483841060..f2cab45450 100644
--- a/src/nvim/testdir/test_selectmode.vim
+++ b/src/nvim/testdir/test_selectmode.vim
@@ -2,6 +2,156 @@
source shared.vim
+" Test for select mode
+func Test_selectmode_basic()
+ new
+ call setline(1, range(1,100))
+ 50
+ norm! gHy
+ call assert_equal('y51', getline('.'))
+ call setline(1, range(1,100))
+ 50
+ exe ":norm! V9jo\<c-g>y"
+ call assert_equal('y60', getline('.'))
+ call setline(1, range(1,100))
+ 50
+ " call feedkeys(":set im\n\<c-o>gHc\<c-o>:set noim\n", 'tx')
+ call feedkeys("i\<c-o>gHc\<esc>", 'tx')
+ call assert_equal('c51', getline('.'))
+ " clean up
+ bw!
+endfunc
+
+" Test for starting selectmode
+func Test_selectmode_start()
+ new
+ set selectmode=key keymodel=startsel
+ call setline(1, ['abc', 'def', 'ghi'])
+ call cursor(1, 4)
+ call feedkeys("A\<s-home>start\<esc>", 'txin')
+ call assert_equal(['startdef', 'ghi'], getline(1, '$'))
+ " start select mode again with gv
+ set selectmode=cmd
+ call feedkeys('gvabc', 'xt')
+ call assert_equal('abctdef', getline(1))
+ set selectmode= keymodel=
+ bw!
+endfunc
+
+" Test for characterwise select mode
+func Test_characterwise_select_mode()
+ new
+
+ " Select mode maps
+ snoremap <lt>End> <End>
+ snoremap <lt>Down> <Down>
+ snoremap <lt>Del> <Del>
+
+ " characterwise select mode: delete middle line
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal Gkkgh\<End>\<Del>"
+ call assert_equal(['', 'b', 'c'], getline(1, '$'))
+
+ " characterwise select mode: delete middle two lines
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal Gkkgh\<Down>\<End>\<Del>"
+ call assert_equal(['', 'c'], getline(1, '$'))
+
+ " characterwise select mode: delete last line
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal Ggh\<End>\<Del>"
+ call assert_equal(['', 'a', 'b', ''], getline(1, '$'))
+
+ " characterwise select mode: delete last two lines
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal Gkgh\<Down>\<End>\<Del>"
+ call assert_equal(['', 'a', ''], getline(1, '$'))
+
+ " CTRL-H in select mode behaves like 'x'
+ call setline(1, 'abcdef')
+ exe "normal! gggh\<Right>\<Right>\<Right>\<C-H>"
+ call assert_equal('ef', getline(1))
+
+ " CTRL-O in select mode switches to visual mode for one command
+ call setline(1, 'abcdef')
+ exe "normal! gggh\<C-O>3lm"
+ call assert_equal('mef', getline(1))
+
+ sunmap <lt>End>
+ sunmap <lt>Down>
+ sunmap <lt>Del>
+ bwipe!
+endfunc
+
+" Test for linewise select mode
+func Test_linewise_select_mode()
+ new
+
+ " linewise select mode: delete middle line
+ call append('$', ['a', 'b', 'c'])
+ exe "normal GkkgH\<Del>"
+ call assert_equal(['', 'b', 'c'], getline(1, '$'))
+
+ " linewise select mode: delete middle two lines
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal GkkgH\<Down>\<Del>"
+ call assert_equal(['', 'c'], getline(1, '$'))
+
+ " linewise select mode: delete last line
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal GgH\<Del>"
+ call assert_equal(['', 'a', 'b'], getline(1, '$'))
+
+ " linewise select mode: delete last two lines
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal GkgH\<Down>\<Del>"
+ call assert_equal(['', 'a'], getline(1, '$'))
+
+ bwipe!
+endfunc
+
+" Test for blockwise select mode (g CTRL-H)
+func Test_blockwise_select_mode()
+ new
+ call setline(1, ['foo', 'bar'])
+ call feedkeys("g\<BS>\<Right>\<Down>mm", 'xt')
+ call assert_equal(['mmo', 'mmr'], getline(1, '$'))
+ close!
+endfunc
+
+" Test for using visual mode maps in select mode
+func Test_select_mode_map()
+ new
+ vmap <buffer> <F2> 3l
+ call setline(1, 'Test line')
+ call feedkeys("gh\<F2>map", 'xt')
+ call assert_equal('map line', getline(1))
+
+ vmap <buffer> <F2> ygV
+ call feedkeys("0gh\<Right>\<Right>\<F2>cwabc", 'xt')
+ call assert_equal('abc line', getline(1))
+
+ vmap <buffer> <F2> :<C-U>let v=100<CR>
+ call feedkeys("gggh\<Right>\<Right>\<F2>foo", 'xt')
+ call assert_equal('foo line', getline(1))
+
+ " reselect the select mode using gv from a visual mode map
+ vmap <buffer> <F2> gv
+ set selectmode=cmd
+ call feedkeys("0gh\<F2>map", 'xt')
+ call assert_equal('map line', getline(1))
+ set selectmode&
+
+ close!
+endfunc
+
" Test for selecting a register with CTRL-R
func Test_selectmode_register()
new
diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim
index 456f0e0edd..9c1ad0c099 100644
--- a/src/nvim/testdir/test_visual.vim
+++ b/src/nvim/testdir/test_visual.vim
@@ -664,92 +664,6 @@ func Test_characterwise_visual_mode()
bwipe!
endfunc
-func Test_characterwise_select_mode()
- new
-
- " Select mode maps
- snoremap <lt>End> <End>
- snoremap <lt>Down> <Down>
- snoremap <lt>Del> <Del>
-
- " characterwise select mode: delete middle line
- call deletebufline('', 1, '$')
- call append('$', ['a', 'b', 'c'])
- exe "normal Gkkgh\<End>\<Del>"
- call assert_equal(['', 'b', 'c'], getline(1, '$'))
-
- " characterwise select mode: delete middle two lines
- call deletebufline('', 1, '$')
- call append('$', ['a', 'b', 'c'])
- exe "normal Gkkgh\<Down>\<End>\<Del>"
- call assert_equal(['', 'c'], getline(1, '$'))
-
- " characterwise select mode: delete last line
- call deletebufline('', 1, '$')
- call append('$', ['a', 'b', 'c'])
- exe "normal Ggh\<End>\<Del>"
- call assert_equal(['', 'a', 'b', ''], getline(1, '$'))
-
- " characterwise select mode: delete last two lines
- call deletebufline('', 1, '$')
- call append('$', ['a', 'b', 'c'])
- exe "normal Gkgh\<Down>\<End>\<Del>"
- call assert_equal(['', 'a', ''], getline(1, '$'))
-
- " CTRL-H in select mode behaves like 'x'
- call setline(1, 'abcdef')
- exe "normal! gggh\<Right>\<Right>\<Right>\<C-H>"
- call assert_equal('ef', getline(1))
-
- " CTRL-O in select mode switches to visual mode for one command
- call setline(1, 'abcdef')
- exe "normal! gggh\<C-O>3lm"
- call assert_equal('mef', getline(1))
-
- sunmap <lt>End>
- sunmap <lt>Down>
- sunmap <lt>Del>
- bwipe!
-endfunc
-
-func Test_linewise_select_mode()
- new
-
- " linewise select mode: delete middle line
- call append('$', ['a', 'b', 'c'])
- exe "normal GkkgH\<Del>"
- call assert_equal(['', 'b', 'c'], getline(1, '$'))
-
- " linewise select mode: delete middle two lines
- call deletebufline('', 1, '$')
- call append('$', ['a', 'b', 'c'])
- exe "normal GkkgH\<Down>\<Del>"
- call assert_equal(['', 'c'], getline(1, '$'))
-
- " linewise select mode: delete last line
- call deletebufline('', 1, '$')
- call append('$', ['a', 'b', 'c'])
- exe "normal GgH\<Del>"
- call assert_equal(['', 'a', 'b'], getline(1, '$'))
-
- " linewise select mode: delete last two lines
- call deletebufline('', 1, '$')
- call append('$', ['a', 'b', 'c'])
- exe "normal GkgH\<Down>\<Del>"
- call assert_equal(['', 'a'], getline(1, '$'))
-
- bwipe!
-endfunc
-
-" Test for blockwise select mode (g CTRL-H)
-func Test_blockwise_select_mode()
- new
- call setline(1, ['foo', 'bar'])
- call feedkeys("g\<BS>\<Right>\<Down>mm", 'xt')
- call assert_equal(['mmo', 'mmr'], getline(1, '$'))
- close!
-endfunc
-
func Test_visual_mode_put()
new
@@ -789,16 +703,16 @@ func Test_visual_mode_put()
bwipe!
endfunc
-func Test_select_mode_gv()
+func Test_gv_with_exclusive_selection()
new
- " gv in exclusive select mode after operation
+ " gv with exclusive selection after an operation
call append('$', ['zzz ', 'äà '])
set selection=exclusive
normal Gkv3lyjv3lpgvcxxx
call assert_equal(['', 'zzz ', 'xxx '], getline(1, '$'))
- " gv in exclusive select mode without operation
+ " gv with exclusive selection without an operation
call deletebufline('', 1, '$')
call append('$', 'zzz ')
set selection=exclusive
@@ -1147,32 +1061,6 @@ func Test_star_register()
close!
endfunc
-" Test for using visual mode maps in select mode
-func Test_select_mode_map()
- new
- vmap <buffer> <F2> 3l
- call setline(1, 'Test line')
- call feedkeys("gh\<F2>map", 'xt')
- call assert_equal('map line', getline(1))
-
- vmap <buffer> <F2> ygV
- call feedkeys("0gh\<Right>\<Right>\<F2>cwabc", 'xt')
- call assert_equal('abc line', getline(1))
-
- vmap <buffer> <F2> :<C-U>let v=100<CR>
- call feedkeys("gggh\<Right>\<Right>\<F2>foo", 'xt')
- call assert_equal('foo line', getline(1))
-
- " reselect the select mode using gv from a visual mode map
- vmap <buffer> <F2> gv
- set selectmode=cmd
- call feedkeys("0gh\<F2>map", 'xt')
- call assert_equal('map line', getline(1))
- set selectmode&
-
- close!
-endfunc
-
" Test for changing text in visual mode with 'exclusive' selection
func Test_exclusive_selection()
new
diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim
index bfbba1f793..a8735bcaf1 100644
--- a/src/nvim/testdir/test_writefile.vim
+++ b/src/nvim/testdir/test_writefile.vim
@@ -128,6 +128,25 @@ func Test_nowrite_quit_split()
bwipe Xfile
endfunc
+func Test_writefile_sync_arg()
+ " This doesn't check if fsync() works, only that the argument is accepted.
+ call writefile(['one'], 'Xtest', 's')
+ call writefile(['two'], 'Xtest', 'S')
+ call delete('Xtest')
+endfunc
+
+func Test_writefile_sync_dev_stdout()
+ if !has('unix')
+ return
+ endif
+ if filewritable('/dev/stdout')
+ " Just check that this doesn't cause an error.
+ call writefile(['one'], '/dev/stdout', 's')
+ else
+ throw 'Skipped: /dev/stdout is not writable'
+ endif
+endfunc
+
func Test_writefile_autowrite()
set autowrite
new
@@ -237,29 +256,18 @@ func Test_write_errors()
call delete('Xfile')
endfunc
-func Test_writefile_sync_dev_stdout()
- if !has('unix')
- return
- endif
- if filewritable('/dev/stdout')
- " Just check that this doesn't cause an error.
- call writefile(['one'], '/dev/stdout', 's')
- else
- throw 'Skipped: /dev/stdout is not writable'
- endif
-endfunc
-
-func Test_writefile_sync_arg()
- " This doesn't check if fsync() works, only that the argument is accepted.
- call writefile(['one'], 'Xtest', 's')
- call writefile(['two'], 'Xtest', 'S')
- call delete('Xtest')
+" Test for writing a file using invalid file encoding
+func Test_write_invalid_encoding()
+ new
+ call setline(1, 'abc')
+ call assert_fails('write ++enc=axbyc Xfile', 'E213:')
+ close!
endfunc
" Tests for reading and writing files with conversion for Win32.
func Test_write_file_encoding()
- CheckMSWindows
throw 'skipped: Nvim does not support :w ++enc=cp1251'
+ CheckMSWindows
let save_encoding = &encoding
let save_fileencodings = &fileencodings
set encoding& fileencodings&
diff --git a/test/functional/legacy/filechanged_spec.lua b/test/functional/legacy/filechanged_spec.lua
index ecb861098c..1f23528d61 100644
--- a/test/functional/legacy/filechanged_spec.lua
+++ b/test/functional/legacy/filechanged_spec.lua
@@ -67,6 +67,15 @@ describe('file changed dialog', function()
call assert_equal(1, line('$'))
call assert_equal('new line', getline(1))
+ " File created after starting to edit it
+ call delete('Xchanged_d')
+ new Xchanged_d
+ call writefile(['one'], 'Xchanged_d')
+ call nvim_input('L')
+ checktime Xchanged_d
+ call assert_equal(['one'], getline(1, '$'))
+ close!
+
bwipe!
call delete('Xchanged_d')
endfunc
diff --git a/test/functional/ui/inccommand_user_spec.lua b/test/functional/ui/inccommand_user_spec.lua
index b5816f6fe6..0b25d4f8d2 100644
--- a/test/functional/ui/inccommand_user_spec.lua
+++ b/test/functional/ui/inccommand_user_spec.lua
@@ -7,61 +7,82 @@ local feed = helpers.feed
local command = helpers.command
local assert_alive = helpers.assert_alive
--- Implements a :Replace command that works like :substitute.
+-- Implements a :Replace command that works like :substitute and has multibuffer support.
local setup_replace_cmd = [[
- local function show_replace_preview(buf, use_preview_win, preview_ns, preview_buf, matches)
+ local function show_replace_preview(use_preview_win, preview_ns, preview_buf, matches)
-- Find the width taken by the largest line number, used for padding the line numbers
local highest_lnum = math.max(matches[#matches][1], 1)
local highest_lnum_width = math.floor(math.log10(highest_lnum))
local preview_buf_line = 0
-
- vim.g.prevns = preview_ns
- vim.g.prevbuf = preview_buf
+ local multibuffer = #matches > 1
for _, match in ipairs(matches) do
- local lnum = match[1]
- local line_matches = match[2]
- local prefix
-
- if use_preview_win then
- prefix = string.format(
- '|%s%d| ',
- string.rep(' ', highest_lnum_width - math.floor(math.log10(lnum))),
- lnum
- )
+ local buf = match[1]
+ local buf_matches = match[2]
+
+ if multibuffer and #buf_matches > 0 and use_preview_win then
+ local bufname = vim.api.nvim_buf_get_name(buf)
+
+ if bufname == "" then
+ bufname = string.format("Buffer #%d", buf)
+ end
vim.api.nvim_buf_set_lines(
preview_buf,
preview_buf_line,
preview_buf_line,
0,
- { prefix .. vim.api.nvim_buf_get_lines(buf, lnum - 1, lnum, false)[1] }
+ { bufname .. ':' }
)
+
+ preview_buf_line = preview_buf_line + 1
end
- for _, line_match in ipairs(line_matches) do
- vim.api.nvim_buf_add_highlight(
- buf,
- preview_ns,
- 'Substitute',
- lnum - 1,
- line_match[1],
- line_match[2]
- )
+ for _, buf_match in ipairs(buf_matches) do
+ local lnum = buf_match[1]
+ local line_matches = buf_match[2]
+ local prefix
if use_preview_win then
- vim.api.nvim_buf_add_highlight(
+ prefix = string.format(
+ '|%s%d| ',
+ string.rep(' ', highest_lnum_width - math.floor(math.log10(lnum))),
+ lnum
+ )
+
+ vim.api.nvim_buf_set_lines(
preview_buf,
+ preview_buf_line,
+ preview_buf_line,
+ 0,
+ { prefix .. vim.api.nvim_buf_get_lines(buf, lnum - 1, lnum, false)[1] }
+ )
+ end
+
+ for _, line_match in ipairs(line_matches) do
+ vim.api.nvim_buf_add_highlight(
+ buf,
preview_ns,
'Substitute',
- preview_buf_line,
- #prefix + line_match[1],
- #prefix + line_match[2]
+ lnum - 1,
+ line_match[1],
+ line_match[2]
)
+
+ if use_preview_win then
+ vim.api.nvim_buf_add_highlight(
+ preview_buf,
+ preview_ns,
+ 'Substitute',
+ preview_buf_line,
+ #prefix + line_match[1],
+ #prefix + line_match[2]
+ )
+ end
end
- end
- preview_buf_line = preview_buf_line + 1
+ preview_buf_line = preview_buf_line + 1
+ end
end
if use_preview_win then
@@ -72,94 +93,121 @@ local setup_replace_cmd = [[
end
local function do_replace(opts, preview, preview_ns, preview_buf)
- local pat1 = opts.fargs[1] or ''
+ local pat1 = opts.fargs[1]
+
+ if not pat1 then return end
+
local pat2 = opts.fargs[2] or ''
local line1 = opts.line1
local line2 = opts.line2
-
- local buf = vim.api.nvim_get_current_buf()
- local lines = vim.api.nvim_buf_get_lines(buf, line1 - 1, line2, 0)
local matches = {}
- for i, line in ipairs(lines) do
- local startidx, endidx = 0, 0
- local line_matches = {}
- local num = 1
+ -- Get list of valid and listed buffers
+ local buffers = vim.tbl_filter(
+ function(buf)
+ if not (vim.api.nvim_buf_is_valid(buf) and vim.bo[buf].buflisted and buf ~= preview_buf)
+ then
+ return false
+ end
- while startidx ~= -1 do
- local match = vim.fn.matchstrpos(line, pat1, 0, num)
- startidx, endidx = match[2], match[3]
+ -- Check if there's at least one window using the buffer
+ for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
+ if vim.api.nvim_win_get_buf(win) == buf then
+ return true
+ end
+ end
- if startidx ~= -1 then
- line_matches[#line_matches+1] = { startidx, endidx }
- end
+ return false
+ end,
+ vim.api.nvim_list_bufs()
+ )
- num = num + 1
- end
+ for _, buf in ipairs(buffers) do
+ local lines = vim.api.nvim_buf_get_lines(buf, line1 - 1, line2, false)
+ local buf_matches = {}
- if #line_matches > 0 then
- matches[#matches+1] = { line1 + i - 1, line_matches }
- end
- end
+ for i, line in ipairs(lines) do
+ local startidx, endidx = 0, 0
+ local line_matches = {}
+ local num = 1
- local new_lines = {}
+ while startidx ~= -1 do
+ local match = vim.fn.matchstrpos(line, pat1, 0, num)
+ startidx, endidx = match[2], match[3]
- for _, match in ipairs(matches) do
- local lnum = match[1]
- local line_matches = match[2]
- local line = lines[lnum - line1 + 1]
- local pat_width_differences = {}
-
- -- If previewing, only replace the text in current buffer if pat2 isn't empty
- -- Otherwise, always replace the text
- if pat2 ~= '' or not preview then
- if preview then
- for _, line_match in ipairs(line_matches) do
- local startidx, endidx = unpack(line_match)
- local pat_match = line:sub(startidx + 1, endidx)
-
- pat_width_differences[#pat_width_differences+1] =
- #vim.fn.substitute(pat_match, pat1, pat2, 'g') - #pat_match
+ if startidx ~= -1 then
+ line_matches[#line_matches+1] = { startidx, endidx }
end
+
+ num = num + 1
end
- new_lines[lnum] = vim.fn.substitute(line, pat1, pat2, 'g')
+ if #line_matches > 0 then
+ buf_matches[#buf_matches+1] = { line1 + i - 1, line_matches }
+ end
end
- -- Highlight the matches if previewing
- if preview then
- local idx_offset = 0
- for i, line_match in ipairs(line_matches) do
- local startidx, endidx = unpack(line_match)
- -- Starting index of replacement text
- local repl_startidx = startidx + idx_offset
- -- Ending index of the replacement text (if pat2 isn't empty)
- local repl_endidx
-
- if pat2 ~= '' then
- repl_endidx = endidx + idx_offset + pat_width_differences[i]
- else
- repl_endidx = endidx + idx_offset
+ local new_lines = {}
+
+ for _, buf_match in ipairs(buf_matches) do
+ local lnum = buf_match[1]
+ local line_matches = buf_match[2]
+ local line = lines[lnum - line1 + 1]
+ local pat_width_differences = {}
+
+ -- If previewing, only replace the text in current buffer if pat2 isn't empty
+ -- Otherwise, always replace the text
+ if pat2 ~= '' or not preview then
+ if preview then
+ for _, line_match in ipairs(line_matches) do
+ local startidx, endidx = unpack(line_match)
+ local pat_match = line:sub(startidx + 1, endidx)
+
+ pat_width_differences[#pat_width_differences+1] =
+ #vim.fn.substitute(pat_match, pat1, pat2, 'g') - #pat_match
+ end
end
- if pat2 ~= '' then
- idx_offset = idx_offset + pat_width_differences[i]
- end
+ new_lines[lnum] = vim.fn.substitute(line, pat1, pat2, 'g')
+ end
- line_matches[i] = { repl_startidx, repl_endidx }
+ -- Highlight the matches if previewing
+ if preview then
+ local idx_offset = 0
+ for i, line_match in ipairs(line_matches) do
+ local startidx, endidx = unpack(line_match)
+ -- Starting index of replacement text
+ local repl_startidx = startidx + idx_offset
+ -- Ending index of the replacement text (if pat2 isn't empty)
+ local repl_endidx
+
+ if pat2 ~= '' then
+ repl_endidx = endidx + idx_offset + pat_width_differences[i]
+ else
+ repl_endidx = endidx + idx_offset
+ end
+
+ if pat2 ~= '' then
+ idx_offset = idx_offset + pat_width_differences[i]
+ end
+
+ line_matches[i] = { repl_startidx, repl_endidx }
+ end
end
end
- end
- for lnum, line in pairs(new_lines) do
- vim.api.nvim_buf_set_lines(buf, lnum - 1, lnum, false, { line })
+ for lnum, line in pairs(new_lines) do
+ vim.api.nvim_buf_set_lines(buf, lnum - 1, lnum, false, { line })
+ end
+
+ matches[#matches+1] = { buf, buf_matches }
end
if preview then
local lnum = vim.api.nvim_win_get_cursor(0)[1]
-- Use preview window only if preview buffer is provided and range isn't just the current line
local use_preview_win = (preview_buf ~= nil) and (line1 ~= lnum or line2 ~= lnum)
- return show_replace_preview(buf, use_preview_win, preview_ns, preview_buf, matches)
+ return show_replace_preview(use_preview_win, preview_ns, preview_buf, matches)
end
end
@@ -354,3 +402,120 @@ describe("'inccommand' for user commands", function()
assert_alive()
end)
end)
+
+describe("'inccommand' with multiple buffers", function()
+ local screen
+
+ before_each(function()
+ clear()
+ screen = Screen.new(40, 17)
+ screen:set_default_attr_ids({
+ [1] = {background = Screen.colors.Yellow1},
+ [2] = {foreground = Screen.colors.Blue1, bold = true},
+ [3] = {reverse = true},
+ [4] = {reverse = true, bold = true}
+ })
+ screen:attach()
+ exec_lua(setup_replace_cmd)
+ command('set cmdwinheight=10')
+ insert[[
+ foo bar baz
+ bar baz foo
+ baz foo bar
+ ]]
+ command('vsplit | enew')
+ insert[[
+ bar baz foo
+ baz foo bar
+ foo bar baz
+ ]]
+ end)
+
+ it('works', function()
+ command('set inccommand=nosplit')
+ feed(':Replace foo bar')
+ screen:expect([[
+ bar baz {1:bar} │ {1:bar} bar baz |
+ baz {1:bar} bar │ bar baz {1:bar} |
+ {1:bar} bar baz │ baz {1:bar} bar |
+ │ |
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {4:[No Name] [+] }{3:[No Name] [+] }|
+ :Replace foo bar^ |
+ ]])
+ feed('<CR>')
+ screen:expect([[
+ bar baz bar │ bar bar baz |
+ baz bar bar │ bar baz bar |
+ bar bar baz │ baz bar bar |
+ ^ │ |
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {4:[No Name] [+] }{3:[No Name] [+] }|
+ :Replace foo bar |
+ ]])
+ end)
+
+ it('works with inccommand=split', function()
+ command('set inccommand=split')
+ feed(':Replace foo bar')
+ screen:expect([[
+ bar baz {1:bar} │ {1:bar} bar baz |
+ baz {1:bar} bar │ bar baz {1:bar} |
+ {1:bar} bar baz │ baz {1:bar} bar |
+ │ |
+ {4:[No Name] [+] }{3:[No Name] [+] }|
+ Buffer #1: |
+ |1| {1:bar} bar baz |
+ |2| bar baz {1:bar} |
+ |3| baz {1:bar} bar |
+ Buffer #2: |
+ |1| bar baz {1:bar} |
+ |2| baz {1:bar} bar |
+ |3| {1:bar} bar baz |
+ |
+ {2:~ }|
+ {3:[Preview] }|
+ :Replace foo bar^ |
+ ]])
+ feed('<CR>')
+ screen:expect([[
+ bar baz bar │ bar bar baz |
+ baz bar bar │ bar baz bar |
+ bar bar baz │ baz bar bar |
+ ^ │ |
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {2:~ }│{2:~ }|
+ {4:[No Name] [+] }{3:[No Name] [+] }|
+ :Replace foo bar |
+ ]])
+ end)
+end)
diff --git a/test/unit/buffer_spec.lua b/test/unit/buffer_spec.lua
index 204d713fb7..5dccc2f5a2 100644
--- a/test/unit/buffer_spec.lua
+++ b/test/unit/buffer_spec.lua
@@ -9,6 +9,7 @@ local NULL = helpers.NULL
local globals = helpers.cimport("./src/nvim/globals.h")
local buffer = helpers.cimport("./src/nvim/buffer.h")
+local stl = helpers.cimport("./src/nvim/statusline.h")
describe('buffer functions', function()
@@ -228,7 +229,7 @@ describe('buffer functions', function()
local fillchar = arg.fillchar or (' '):byte()
local maximum_cell_count = arg.maximum_cell_count or buffer_byte_size
- return buffer.build_stl_str_hl(globals.curwin,
+ return stl.build_stl_str_hl(globals.curwin,
output_buffer,
buffer_byte_size,
to_cstr(pat),