diff options
-rw-r--r-- | src/nvim/statusline.c | 27 | ||||
-rw-r--r-- | test/functional/ui/statusline_spec.lua | 505 |
2 files changed, 267 insertions, 265 deletions
diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c index 6947a14a2c..ddae023ad5 100644 --- a/src/nvim/statusline.c +++ b/src/nvim/statusline.c @@ -927,6 +927,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex op static stl_hlrec_t *stl_hltab = NULL; static StlClickRecord *stl_tabtab = NULL; static int *stl_separator_locations = NULL; + static int curitem = 0; #define TMPLEN 70 char buf_tmp[TMPLEN]; @@ -1013,7 +1014,11 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex op int groupdepth = 0; int evaldepth = 0; - int curitem = 0; + // nvim_eval_statusline() can be called from inside a {-expression item so + // this may be a recursive call. Keep track of the start index into "stl_items". + // During post-processing only treat items filled in a certain recursion level. + int evalstart = curitem; + bool prevchar_isflag = true; bool prevchar_isitem = false; @@ -1949,7 +1954,9 @@ stcsign: } *out_p = NUL; - int itemcnt = curitem; + // Subtract offset from `itemcnt` and restore `curitem` to previous recursion level. + int itemcnt = curitem - evalstart; + curitem = evalstart; // Free the format buffer if we allocated it internally if (usefmt != fmt) { @@ -1975,7 +1982,7 @@ stcsign: trunc_p = stl_items[0].start; item_idx = 0; - for (int i = 0; i < itemcnt; i++) { + for (int i = evalstart; i < itemcnt + evalstart; i++) { if (stl_items[i].type == Trunc) { // Truncate at %< stl_items. trunc_p = stl_items[i].start; @@ -2005,9 +2012,9 @@ stcsign: // Ignore any items in the statusline that occur after // the truncation point - for (int i = 0; i < itemcnt; i++) { + for (int i = evalstart; i < itemcnt + evalstart; i++) { if (stl_items[i].start > trunc_p) { - for (int j = i; j < itemcnt; j++) { + for (int j = i; j < itemcnt + evalstart; j++) { if (stl_items[j].type == ClickFunc) { XFREE_CLEAR(stl_items[j].cmd); } @@ -2046,7 +2053,7 @@ stcsign: // the truncation marker `<` is not counted. int item_offset = trunc_len - 1; - for (int i = item_idx; i < itemcnt; i++) { + for (int i = item_idx; i < itemcnt + evalstart; 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) { @@ -2079,7 +2086,7 @@ stcsign: // 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++) { + for (int i = evalstart; i < itemcnt + evalstart; i++) { if (stl_items[i].type == Separate) { // Create an array of the start location for each separator mark. stl_separator_locations[num_separators] = i; @@ -2104,7 +2111,7 @@ stcsign: } for (int item_idx = stl_separator_locations[l] + 1; - item_idx < itemcnt; + item_idx < itemcnt + evalstart; item_idx++) { stl_items[item_idx].start += dislocation; } @@ -2118,7 +2125,7 @@ stcsign: if (hltab != NULL) { *hltab = stl_hltab; stl_hlrec_t *sp = stl_hltab; - for (int l = 0; l < itemcnt; l++) { + for (int l = evalstart; l < itemcnt + evalstart; l++) { if (stl_items[l].type == Highlight || stl_items[l].type == HighlightFold || stl_items[l].type == HighlightSign) { sp->start = stl_items[l].start; @@ -2139,7 +2146,7 @@ stcsign: if (tabtab != NULL) { *tabtab = stl_tabtab; StlClickRecord *cur_tab_rec = stl_tabtab; - for (int l = 0; l < itemcnt; l++) { + for (int l = evalstart; l < itemcnt + evalstart; l++) { if (stl_items[l].type == TabPage) { cur_tab_rec->start = stl_items[l].start; if (stl_items[l].minwid == 0) { diff --git a/test/functional/ui/statusline_spec.lua b/test/functional/ui/statusline_spec.lua index 1d0f181244..50e31ac6a9 100644 --- a/test/functional/ui/statusline_spec.lua +++ b/test/functional/ui/statusline_spec.lua @@ -507,268 +507,263 @@ describe('global statusline', function() end) end) -it('statusline does not crash if it has Arabic characters #19447', function() - clear() - api.nvim_set_option_value('statusline', 'غً', {}) - api.nvim_set_option_value('laststatus', 2, {}) - command('redraw!') - assert_alive() -end) +describe('statusline', function() + local screen + before_each(function() + clear() + screen = Screen.new(40, 8) + screen:add_extra_attr_ids { + [100] = { bold = true, reverse = true, foreground = Screen.colors.Blue }, + [101] = { reverse = true, bold = true, foreground = Screen.colors.SlateBlue }, + } + end) -it('statusline is redrawn with :resize from <Cmd> mapping #19629', function() - clear() - local screen = Screen.new(40, 8) - exec([[ - set laststatus=2 - nnoremap <Up> <cmd>resize -1<CR> - nnoremap <Down> <cmd>resize +1<CR> - ]]) - feed('<Up>') - screen:expect([[ - ^ | - {1:~ }|*4 - {3:[No Name] }| - |*2 - ]]) - feed('<Down>') - screen:expect([[ - ^ | - {1:~ }|*5 - {3:[No Name] }| - | - ]]) -end) + it('does not crash if it has Arabic characters #19447', function() + api.nvim_set_option_value('statusline', 'غً', {}) + api.nvim_set_option_value('laststatus', 2, {}) + command('redraw!') + assert_alive() + end) -it('showcmdloc=statusline does not show if statusline is too narrow', function() - clear() - local screen = Screen.new(40, 8) - command('set showcmd') - command('set showcmdloc=statusline') - command('1vsplit') - screen:expect([[ - ^ │ | - {1:~}│{1:~ }|*5 - {3:< }{2:[No Name] }| - | - ]]) - feed('1234') - screen:expect_unchanged() -end) + it('is redrawn with :resize from <Cmd> mapping #19629', function() + exec([[ + set laststatus=2 + nnoremap <Up> <cmd>resize -1<CR> + nnoremap <Down> <cmd>resize +1<CR> + ]]) + feed('<Up>') + screen:expect([[ + ^ | + {1:~ }|*4 + {3:[No Name] }| + |*2 + ]]) + feed('<Down>') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:[No Name] }| + | + ]]) + end) -it('K_EVENT does not trigger a statusline redraw unnecessarily', function() - clear() - local _ = Screen.new(40, 8) - -- does not redraw on vim.schedule (#17937) - command([[ - set laststatus=2 - let g:counter = 0 - func Status() - let g:counter += 1 - lua vim.schedule(function() end) - return g:counter - endfunc - set statusline=%!Status() - ]]) - sleep(50) - eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) - -- also in insert mode - feed('i') - sleep(50) - eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) - -- does not redraw on timer call (#14303) - command([[ - let g:counter = 0 - func Timer(timer) - endfunc - call timer_start(1, 'Timer', {'repeat': 100}) - ]]) - sleep(50) - eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) -end) + it('does not contain showmcd with showcmdloc=statusline when too narrow', function() + command('set showcmd') + command('set showcmdloc=statusline') + command('1vsplit') + screen:expect([[ + ^ │ | + {1:~}│{1:~ }|*5 + {3:< }{2:[No Name] }| + | + ]]) + feed('1234') + screen:expect_unchanged() + end) -it('statusline is redrawn on various state changes', function() - clear() - local screen = Screen.new(40, 4) - - -- recording state change #22683 - command('set ls=2 stl=%{repeat(reg_recording(),5)}') - screen:expect([[ - ^ | - {1:~ }| - {3: }| - | - ]]) - feed('qQ') - screen:expect([[ - ^ | - {1:~ }| - {3:QQQQQ }| - {5:recording @Q} | - ]]) - feed('q') - screen:expect([[ - ^ | - {1:~ }| - {3: }| - | - ]]) - - -- Visual mode change #23932 - command('set ls=2 stl=%{mode(1)}') - screen:expect([[ - ^ | - {1:~ }| - {3:n }| - | - ]]) - feed('v') - screen:expect([[ - ^ | - {1:~ }| - {3:v }| - {5:-- VISUAL --} | - ]]) - feed('V') - screen:expect([[ - ^ | - {1:~ }| - {3:V }| - {5:-- VISUAL LINE --} | - ]]) - feed('<C-V>') - screen:expect([[ - ^ | - {1:~ }| - {3:^V }| - {5:-- VISUAL BLOCK --} | - ]]) - feed('<Esc>') - screen:expect([[ - ^ | - {1:~ }| - {3:n }| - | - ]]) -end) + it('does not redraw unnecessarily after K_EVENT', function() + -- does not redraw on vim.schedule (#17937) + command([[ + set laststatus=2 + let g:counter = 0 + func Status() + let g:counter += 1 + lua vim.schedule(function() end) + return g:counter + endfunc + set statusline=%!Status() + ]]) + sleep(50) + eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) + -- also in insert mode + feed('i') + sleep(50) + eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) + -- does not redraw on timer call (#14303) + command([[ + let g:counter = 0 + func Timer(timer) + endfunc + call timer_start(1, 'Timer', {'repeat': 100}) + ]]) + sleep(50) + eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) + end) -it('ruler is redrawn in cmdline with redrawstatus #22804', function() - clear() - local screen = Screen.new(40, 2) - command([[ - let g:n = 'initial value' - set ls=1 ru ruf=%{g:n} - redraw - let g:n = 'other value' - redrawstatus - ]]) - screen:expect([[ - ^ | - other value | - ]]) -end) + it('is redrawn on various state changes', function() + -- recording state change #22683 + command('set ls=2 stl=%{repeat(reg_recording(),5)}') + local s1 = [[ + ^ | + {1:~ }|*5 + {3: }| + | + ]] + screen:expect(s1) + feed('qQ') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:QQQQQ }| + {5:recording @Q} | + ]]) + feed('q') + screen:expect(s1) + + -- Visual mode change #23932 + command('set ls=2 stl=%{mode(1)}') + local s2 = [[ + ^ | + {1:~ }|*5 + {3:n }| + | + ]] + screen:expect(s2) + feed('v') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:v }| + {5:-- VISUAL --} | + ]]) + feed('V') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:V }| + {5:-- VISUAL LINE --} | + ]]) + feed('<C-V>') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:^V }| + {5:-- VISUAL BLOCK --} | + ]]) + feed('<Esc>') + screen:expect(s2) + end) -it('shows correct ruler in cmdline with no statusline', function() - clear() - local screen = Screen.new(30, 8) - -- Use long ruler to check 'ruler' with 'rulerformat' set has correct width. - command [[ - set ruler rulerformat=%{winnr()}longlonglong ls=0 winwidth=10 - split - wincmd b - vsplit - wincmd t - wincmd | - mode - ]] - -- Window 1 is current. It has a statusline, so cmdline should show the - -- last window's ruler, which has no statusline. - command '1wincmd w' - screen:expect [[ - ^ | - {1:~ }|*2 - {3:[No Name] 1longlonglong }| - │ | - {1:~ }│{1:~ }|*2 - 3longlonglong | - ]] - -- Window 2 is current. It has no statusline, so cmdline should show its - -- ruler instead. - command '2wincmd w' - screen:expect [[ - | - {1:~ }|*2 - {2:[No Name] 1longlonglong }| - ^ │ | - {1:~ }│{1:~ }|*2 - 2longlonglong | - ]] - -- Window 3 is current. Cmdline should again show its ruler. - command '3wincmd w' - screen:expect [[ - | - {1:~ }|*2 - {2:[No Name] 1longlonglong }| - │^ | - {1:~ }│{1:~ }|*2 - 3longlonglong | - ]] -end) + it('ruler is redrawn in cmdline with redrawstatus #22804', function() + command([[ + let g:n = 'initial value' + set ls=1 ru ruf=%{g:n} + redraw + let g:n = 'other value' + redrawstatus + ]]) + screen:expect([[ + ^ | + {1:~ }|*6 + other value | + ]]) + end) -it('uses "stl" and "stlnc" fillchars even if they are the same #19803', function() - clear() - local screen = Screen.new(53, 4) - command('hi clear StatusLine') - command('hi clear StatusLineNC') - command('vsplit') - screen:expect { - grid = [[ - ^ │ | - {1:~ }│{1:~ }| - [No Name] [No Name] | - | - ]], - } -end) + it('hidden moves ruler to cmdline', function() + -- Use long ruler to check 'ruler' with 'rulerformat' set has correct width. + command [[ + set ruler rulerformat=%{winnr()}longlonglong ls=0 winwidth=10 + split + wincmd b + vsplit + wincmd t + wincmd | + mode + ]] + -- Window 1 is current. It has a statusline, so cmdline should show the + -- last window's ruler, which has no statusline. + command '1wincmd w' + screen:expect([[ + ^ | + {1:~ }|*2 + {3:[No Name] 1longlonglong }| + │ | + {1:~ }│{1:~ }|*2 + 3longlonglong | + ]]) + -- Window 2 is current. It has no statusline, so cmdline should show its + -- ruler instead. + command '2wincmd w' + screen:expect([[ + | + {1:~ }|*2 + {2:[No Name] 1longlonglong }| + ^ │ | + {1:~ }│{1:~ }|*2 + 2longlonglong | + ]]) + -- Window 3 is current. Cmdline should again show its ruler. + command '3wincmd w' + screen:expect([[ + | + {1:~ }|*2 + {2:[No Name] 1longlonglong }| + │^ | + {1:~ }│{1:~ }|*2 + 3longlonglong | + ]]) + end) -it('showcmdloc=statusline works with vertical splits', function() - clear() - local screen = Screen.new(53, 4) - command('rightbelow vsplit') - command('set showcmd showcmdloc=statusline') - feed('1234') - screen:expect([[ - │^ | - {1:~ }│{1:~ }| - {2:[No Name] }{3:[No Name] 1234 }| - | - ]]) - feed('<Esc>') - command('set laststatus=3') - feed('1234') - screen:expect([[ - │^ | - {1:~ }│{1:~ }| - {3:[No Name] 1234 }| - | - ]]) -end) + it('uses "stl" and "stlnc" fillchars even if they are the same #19803', function() + command('hi clear StatusLine') + command('hi clear StatusLineNC') + command('vsplit') + screen:expect([[ + ^ │ | + {1:~ }│{1:~ }|*5 + [No Name] [No Name] | + | + ]]) + end) -it('keymap is shown with vertical splits #27269', function() - clear() - local screen = Screen.new(53, 4) - command('setlocal keymap=dvorak') - command('rightbelow vsplit') - screen:expect([[ - │^ | - {1:~ }│{1:~ }| - {2:[No Name] <en-dv> }{3:[No Name] <en-dv> }| - | - ]]) - command('set laststatus=3') - screen:expect([[ - │^ | - {1:~ }│{1:~ }| - {3:[No Name] <en-dv> }| - | - ]]) + it('showcmdloc=statusline works with vertical splits', function() + command('rightbelow vsplit') + command('set showcmd showcmdloc=statusline') + feed('1234') + screen:expect([[ + │^ | + {1:~ }│{1:~ }|*5 + {2:[No Name] }{3:[No Name] 1234 }| + | + ]]) + feed('<Esc>') + command('set laststatus=3') + feed('1234') + screen:expect([[ + │^ | + {1:~ }│{1:~ }|*5 + {3:[No Name] 1234 }| + | + ]]) + end) + + it('keymap is shown with vertical splits #27269', function() + command('setlocal keymap=dvorak') + command('rightbelow vsplit') + screen:expect([[ + │^ | + {1:~ }│{1:~ }|*5 + {2:[No Name] <en-dv> }{3:[No Name] <en-dv> }| + | + ]]) + + command('set laststatus=3') + screen:expect([[ + │^ | + {1:~ }│{1:~ }|*5 + {3:[No Name] <en-dv> }| + | + ]]) + end) + + it("nested call from nvim_eval_statusline() doesn't overwrite items #32259", function() + exec_lua('vim.o.laststatus = 2') + exec_lua([[vim.o.statusline = '%#Special#B:%{nvim_eval_statusline("%f", []).str}']]) + screen:expect([[ + ^ | + {1:~ }|*5 + {101:B:[No Name] }| + | + ]]) + end) end) |