mirror of
https://github.com/neovim/neovim.git
synced 2026-07-23 09:23:09 +00:00
Problem: Separation markers (%=) are ignored within item groups. This lead to a regression when the C implementation of the statusline was replaced with a default expression. When the user configured a custom ruler expression with a %= and used the overloaded item group syntax to set the ruler width, the separation marker worked in the ruler, but not when the ruler was incorporated into the statusline where the item group syntax was interpreted in the usual way. Solution: Analogously to top-level behaviour, expand separation markers evenly within item groups until `minwid` is reached (if set). ref https://github.com/neovim/neovim/pull/33036 fix https://github.com/neovim/neovim/issues/39984 ref https://github.com/neovim/neovim/issues/40247 Problem: The recursion offset into the static `stl_items` was not taken into account when adjusting the item count after truncation. Steps to reproduce: first prepare `stl_items`: set stl=%{%repeat('%#Error#',10)%} then watch how the Error highlight leaks into the recursive call: set stl=%l%l%l%{%nvim_eval_statusline('test%l%<',{'maxwidth':3,'highlights':1}).highlights%} ref https://github.com/neovim/neovim/issues/32259 * fix(statusline): consistent truncation at multicell character Problem 1: truncation of item groups at multicell character didn't take into account that minwid can be specified as a negative number. Problem 2: after truncation at top-level from the right at multicell character, the returned width was always `maxwidth`, even though the actual width was reduced. In vim, this can be observed as a statusline that is not fully drawn until the edge of the screen: vim --clean +"set ls=2 stl=%{%repeat('x',&columns-2)%}🙂x%<" Problem 3: after truncation at top-level from the left at multicell character, the resulting gap to reach `maxwidth` again was filled with fillchars, but then the final NUL was not set correctly. This can be seen in the following example, where the statuscolumn spills into the editing area starting from line 10: nvim --clean +"set number stc=%<x🙂%{repeat('x',43)}%l" +"norm yy10p" Solution: fix the small errors and, at top-level, consistently reduce the size instead of compensating with fillchars. In the case of the statusline and the winbar, the remaining place is filled with the configured fillchars in `win_redr_custom`, after `build_stl_str_hl` has returned. In all other cases (title, icon, statuscol, tabline, ruler), there seems to be no point in adding additional spaces at the end. * feat(statusline)!: scope %< to item groups Problem: Previously, item groups were only truncated at the beginning, which is often not desired. In the example %.15(path: %f%) the group's title/label is truncated away: <th/to/file.txt Truncation markers (%<) in item groups were processed at the top-level in the end, which can be confusing. Only the first %< is used for the whole string, and it is used even if the containing item group is hidden. Additionally, in the case of hidden item groups, the marker's position was not adapted. For example, %(hidden%<%)%f had the effect of truncating the path somewhere in the middle: /path/<file.txt Solution: Make truncation consistent with top-level behaviour, which has a better default of truncating at the first `Normal` item, i.e. path: <file.txt and allows for fine-grained control with truncation markers (%<). E.g. %.15(path: %f%<%) now yields path: /path/to> The original behaviour can be restored like so: %.15(%<path: %f%) BREAKING CHANGE: %< is no longer processed at top-level - the default truncation behaviour has changed: now at first item - truncation markers inside item groups don't affect truncation outside of the item group anymore - several truncation markers can now have an effect when separated with item groups, whereas previously only the first one globally had ref https://github.com/neovim/neovim/issues/39984
1470 lines
61 KiB
Lua
1470 lines
61 KiB
Lua
local t = require('test.testutil')
|
|
local n = require('test.functional.testnvim')()
|
|
local tt = require('test.functional.testterm')
|
|
local Screen = require('test.functional.ui.screen')
|
|
|
|
local assert_alive = n.assert_alive
|
|
local clear = n.clear
|
|
local command = n.command
|
|
local feed = n.feed
|
|
local eq = t.eq
|
|
local fn = n.fn
|
|
local api = n.api
|
|
local exec = n.exec
|
|
local exec_lua = n.exec_lua
|
|
local eval = n.eval
|
|
local sleep = vim.uv.sleep
|
|
local pcall_err = t.pcall_err
|
|
local testprg = n.testprg
|
|
local expect_exitcode = tt.expect_exitcode
|
|
|
|
local mousemodels = { 'extend', 'popup', 'popup_setpos' }
|
|
|
|
for _, model in ipairs(mousemodels) do
|
|
describe('statusline clicks with mousemodel=' .. model, 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 },
|
|
}
|
|
command('set laststatus=2 mousemodel=' .. model)
|
|
exec([=[
|
|
function! MyClickFunc(minwid, clicks, button, mods)
|
|
let g:testvar = printf("%d %d %s", a:minwid, a:clicks, a:button)
|
|
if a:mods !=# ' '
|
|
let g:testvar ..= '(' .. a:mods .. ')'
|
|
endif
|
|
endfunction
|
|
let g:testvar = ''
|
|
]=])
|
|
end)
|
|
|
|
it('works', function()
|
|
api.nvim_set_option_value('statusline', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T', {})
|
|
api.nvim_input_mouse('left', 'press', '', 0, 6, 16)
|
|
eq('', eval('g:testvar'))
|
|
api.nvim_input_mouse('right', 'press', '', 0, 6, 29)
|
|
eq('', eval('g:testvar'))
|
|
api.nvim_input_mouse('left', 'press', '', 0, 6, 17)
|
|
eq('0 1 l', eval('g:testvar'))
|
|
api.nvim_input_mouse('left', 'press', '', 0, 6, 17)
|
|
eq('0 2 l', eval('g:testvar'))
|
|
api.nvim_input_mouse('left', 'press', '', 0, 6, 17)
|
|
eq('0 3 l', eval('g:testvar'))
|
|
api.nvim_input_mouse('left', 'press', '', 0, 6, 17)
|
|
eq('0 4 l', eval('g:testvar'))
|
|
api.nvim_input_mouse('right', 'press', '', 0, 6, 28)
|
|
eq('0 1 r', eval('g:testvar'))
|
|
api.nvim_input_mouse('right', 'press', '', 0, 6, 28)
|
|
eq('0 2 r', eval('g:testvar'))
|
|
api.nvim_input_mouse('right', 'press', '', 0, 6, 28)
|
|
eq('0 3 r', eval('g:testvar'))
|
|
api.nvim_input_mouse('right', 'press', '', 0, 6, 28)
|
|
eq('0 4 r', eval('g:testvar'))
|
|
api.nvim_input_mouse('x1', 'press', '', 0, 6, 17)
|
|
eq('0 1 x1', eval('g:testvar'))
|
|
api.nvim_input_mouse('x1', 'press', '', 0, 6, 17)
|
|
eq('0 2 x1', eval('g:testvar'))
|
|
api.nvim_input_mouse('x1', 'press', '', 0, 6, 17)
|
|
eq('0 3 x1', eval('g:testvar'))
|
|
api.nvim_input_mouse('x1', 'press', '', 0, 6, 17)
|
|
eq('0 4 x1', eval('g:testvar'))
|
|
api.nvim_input_mouse('x2', 'press', '', 0, 6, 28)
|
|
eq('0 1 x2', eval('g:testvar'))
|
|
api.nvim_input_mouse('x2', 'press', '', 0, 6, 28)
|
|
eq('0 2 x2', eval('g:testvar'))
|
|
api.nvim_input_mouse('x2', 'press', '', 0, 6, 28)
|
|
eq('0 3 x2', eval('g:testvar'))
|
|
api.nvim_input_mouse('x2', 'press', '', 0, 6, 28)
|
|
eq('0 4 x2', eval('g:testvar'))
|
|
end)
|
|
|
|
it('works with control characters and highlight', function()
|
|
api.nvim_set_option_value('statusline', '\t%#NonText#\1%0@MyClickFunc@\t\1%T\t%##\1', {})
|
|
screen:expect {
|
|
grid = [[
|
|
^ |
|
|
{1:~ }|*5
|
|
{3:^I}{100:^A^I^A^I}{3:^A }|
|
|
|
|
|
]],
|
|
}
|
|
api.nvim_input_mouse('right', 'press', '', 0, 6, 3)
|
|
eq('', eval('g:testvar'))
|
|
api.nvim_input_mouse('left', 'press', '', 0, 6, 8)
|
|
eq('', eval('g:testvar'))
|
|
api.nvim_input_mouse('right', 'press', '', 0, 6, 4)
|
|
eq('0 1 r', eval('g:testvar'))
|
|
api.nvim_input_mouse('left', 'press', '', 0, 6, 7)
|
|
eq('0 1 l', eval('g:testvar'))
|
|
end)
|
|
|
|
it('works with combined highlight attributes', function()
|
|
screen:add_extra_attr_ids({
|
|
[131] = { reverse = true, bold = true, background = Screen.colors.LightMagenta },
|
|
[132] = {
|
|
reverse = true,
|
|
foreground = Screen.colors.Magenta,
|
|
bold = true,
|
|
background = Screen.colors.LightMagenta,
|
|
},
|
|
[133] = { reverse = true, bold = true, foreground = Screen.colors.Magenta1 },
|
|
[134] = {
|
|
bold = true,
|
|
background = Screen.colors.LightMagenta,
|
|
reverse = true,
|
|
undercurl = true,
|
|
special = Screen.colors.Red,
|
|
},
|
|
[135] = {
|
|
bold = true,
|
|
background = Screen.colors.LightMagenta,
|
|
reverse = true,
|
|
undercurl = true,
|
|
foreground = Screen.colors.Fuchsia,
|
|
special = Screen.colors.Red,
|
|
},
|
|
})
|
|
|
|
api.nvim_set_option_value(
|
|
'statusline',
|
|
'\t%#Pmenu#foo%$SpellBad$bar%$String$baz%#Constant#qux',
|
|
{}
|
|
)
|
|
|
|
screen:expect([[
|
|
^ |
|
|
{1:~ }|*5
|
|
{3:^I}{131:foo}{134:bar}{135:baz}{133:qux }|
|
|
|
|
|
]])
|
|
end)
|
|
|
|
it('works for winbar', function()
|
|
api.nvim_set_option_value('winbar', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T', {})
|
|
api.nvim_input_mouse('left', 'press', '', 0, 0, 17)
|
|
eq('0 1 l', eval('g:testvar'))
|
|
api.nvim_input_mouse('right', 'press', '', 0, 0, 17)
|
|
eq('0 1 r', eval('g:testvar'))
|
|
end)
|
|
|
|
it('works for winbar in floating window', function()
|
|
api.nvim_open_win(
|
|
0,
|
|
true,
|
|
{ width = 30, height = 4, relative = 'editor', row = 1, col = 5, border = 'single' }
|
|
)
|
|
api.nvim_set_option_value(
|
|
'winbar',
|
|
'Not clicky stuff %0@MyClickFunc@Clicky stuff%T',
|
|
{ scope = 'local' }
|
|
)
|
|
api.nvim_input_mouse('left', 'press', '', 0, 2, 23)
|
|
eq('0 1 l', eval('g:testvar'))
|
|
end)
|
|
|
|
it('works when there are multiple windows', function()
|
|
command('split')
|
|
api.nvim_set_option_value('statusline', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T', {})
|
|
api.nvim_set_option_value('winbar', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T', {})
|
|
api.nvim_input_mouse('left', 'press', '', 0, 0, 17)
|
|
eq('0 1 l', eval('g:testvar'))
|
|
api.nvim_input_mouse('right', 'press', '', 0, 4, 17)
|
|
eq('0 1 r', eval('g:testvar'))
|
|
api.nvim_input_mouse('middle', 'press', '', 0, 3, 17)
|
|
eq('0 1 m', eval('g:testvar'))
|
|
api.nvim_input_mouse('left', 'press', '', 0, 6, 17)
|
|
eq('0 1 l', eval('g:testvar'))
|
|
end)
|
|
|
|
it('works with Lua function', function()
|
|
exec_lua([[
|
|
function clicky_func(minwid, clicks, button, mods)
|
|
vim.g.testvar = string.format("%d %d %s", minwid, clicks, button)
|
|
end
|
|
]])
|
|
api.nvim_set_option_value(
|
|
'statusline',
|
|
'Not clicky stuff %0@v:lua.clicky_func@Clicky stuff%T',
|
|
{}
|
|
)
|
|
api.nvim_input_mouse('left', 'press', '', 0, 6, 17)
|
|
eq('0 1 l', eval('g:testvar'))
|
|
end)
|
|
|
|
it('ignores unsupported click items', function()
|
|
command('tabnew | tabprevious')
|
|
api.nvim_set_option_value('statusline', '%2TNot clicky stuff%T', {})
|
|
api.nvim_input_mouse('left', 'press', '', 0, 6, 0)
|
|
eq(1, api.nvim_get_current_tabpage())
|
|
api.nvim_set_option_value('statusline', '%2XNot clicky stuff%X', {})
|
|
api.nvim_input_mouse('left', 'press', '', 0, 6, 0)
|
|
eq(2, #api.nvim_list_tabpages())
|
|
end)
|
|
|
|
it("right click works when statusline isn't focused #18994", function()
|
|
api.nvim_set_option_value('statusline', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T', {})
|
|
api.nvim_input_mouse('right', 'press', '', 0, 6, 17)
|
|
eq('0 1 r', eval('g:testvar'))
|
|
api.nvim_input_mouse('right', 'press', '', 0, 6, 17)
|
|
eq('0 2 r', eval('g:testvar'))
|
|
end)
|
|
|
|
it('works with modifiers #18994', function()
|
|
api.nvim_set_option_value('statusline', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T', {})
|
|
-- Note: alternate between left and right mouse buttons to avoid triggering multiclicks
|
|
api.nvim_input_mouse('left', 'press', 'S', 0, 6, 17)
|
|
eq('0 1 l(s )', eval('g:testvar'))
|
|
api.nvim_input_mouse('right', 'press', 'S', 0, 6, 17)
|
|
eq('0 1 r(s )', eval('g:testvar'))
|
|
api.nvim_input_mouse('left', 'press', 'A', 0, 6, 17)
|
|
eq('0 1 l( a )', eval('g:testvar'))
|
|
api.nvim_input_mouse('right', 'press', 'A', 0, 6, 17)
|
|
eq('0 1 r( a )', eval('g:testvar'))
|
|
api.nvim_input_mouse('left', 'press', 'AS', 0, 6, 17)
|
|
eq('0 1 l(s a )', eval('g:testvar'))
|
|
api.nvim_input_mouse('right', 'press', 'AS', 0, 6, 17)
|
|
eq('0 1 r(s a )', eval('g:testvar'))
|
|
api.nvim_input_mouse('left', 'press', 'T', 0, 6, 17)
|
|
eq('0 1 l( m)', eval('g:testvar'))
|
|
api.nvim_input_mouse('right', 'press', 'T', 0, 6, 17)
|
|
eq('0 1 r( m)', eval('g:testvar'))
|
|
api.nvim_input_mouse('left', 'press', 'TS', 0, 6, 17)
|
|
eq('0 1 l(s m)', eval('g:testvar'))
|
|
api.nvim_input_mouse('right', 'press', 'TS', 0, 6, 17)
|
|
eq('0 1 r(s m)', eval('g:testvar'))
|
|
api.nvim_input_mouse('left', 'press', 'C', 0, 6, 17)
|
|
eq('0 1 l( c )', eval('g:testvar'))
|
|
-- <C-RightMouse> is for tag jump
|
|
end)
|
|
|
|
it('works for global statusline with vertical splits #19186', function()
|
|
command('set laststatus=3')
|
|
api.nvim_set_option_value(
|
|
'statusline',
|
|
'%0@MyClickFunc@Clicky stuff%T %= %0@MyClickFunc@Clicky stuff%T',
|
|
{}
|
|
)
|
|
command('vsplit')
|
|
screen:expect {
|
|
grid = [[
|
|
^ │ |
|
|
{1:~ }│{1:~ }|*5
|
|
{3:Clicky stuff Clicky stuff}|
|
|
|
|
|
]],
|
|
}
|
|
|
|
-- clickable area on the right
|
|
api.nvim_input_mouse('left', 'press', '', 0, 6, 35)
|
|
eq('0 1 l', eval('g:testvar'))
|
|
api.nvim_input_mouse('right', 'press', '', 0, 6, 35)
|
|
eq('0 1 r', eval('g:testvar'))
|
|
|
|
-- clickable area on the left
|
|
api.nvim_input_mouse('left', 'press', '', 0, 6, 5)
|
|
eq('0 1 l', eval('g:testvar'))
|
|
api.nvim_input_mouse('right', 'press', '', 0, 6, 5)
|
|
eq('0 1 r', eval('g:testvar'))
|
|
end)
|
|
|
|
it('no memory leak with zero-width click labels', function()
|
|
command([[
|
|
let &stl = '%@Test@%T%@MyClickFunc@%=%T%@Test@'
|
|
]])
|
|
api.nvim_input_mouse('left', 'press', '', 0, 6, 0)
|
|
eq('0 1 l', eval('g:testvar'))
|
|
api.nvim_input_mouse('right', 'press', '', 0, 6, 39)
|
|
eq('0 1 r', eval('g:testvar'))
|
|
end)
|
|
|
|
it('no memory leak with truncated click labels', function()
|
|
command([[
|
|
let &stl = '%@MyClickFunc@foo%X' .. repeat('a', 40) .. '%<t%@Test@bar%X%@Test@baz'
|
|
]])
|
|
api.nvim_input_mouse('left', 'press', '', 0, 6, 2)
|
|
eq('0 1 l', eval('g:testvar'))
|
|
end)
|
|
end)
|
|
end
|
|
|
|
describe('global statusline', function()
|
|
local screen
|
|
|
|
before_each(function()
|
|
clear()
|
|
screen = Screen.new(60, 16)
|
|
screen:add_extra_attr_ids {
|
|
[100] = { foreground = Screen.colors.Magenta1, bold = true },
|
|
}
|
|
command('set laststatus=3')
|
|
command('set ruler')
|
|
end)
|
|
|
|
it('works', function()
|
|
screen:expect([[
|
|
^ |
|
|
{1:~ }|*13
|
|
{3:[No Name] 0,0-1 All}|
|
|
|
|
|
]])
|
|
|
|
feed('i<CR><CR>')
|
|
screen:expect([[
|
|
|*2
|
|
^ |
|
|
{1:~ }|*11
|
|
{3:[No Name] [+] 3,1 All}|
|
|
{5:-- INSERT --} |
|
|
]])
|
|
end)
|
|
|
|
it('works with splits', function()
|
|
command('vsplit | split | vsplit | vsplit | wincmd l | split | 2wincmd l | split')
|
|
screen:expect([[
|
|
│ │ │^ |
|
|
{1:~ }│{1:~ }│{1:~}│{1:~ }|*3
|
|
{1:~ }├────────────────┤{1:~}│{1:~ }|
|
|
{1:~ }│ │{1:~}│{1:~ }|
|
|
{1:~ }│{1:~ }│{1:~}│{1:~ }|
|
|
{1:~ }│{1:~ }│{1:~}├────────────────────|
|
|
{1:~ }│{1:~ }│{1:~}│ |
|
|
────────────────────┴────────────────┴─┤{1:~ }|
|
|
│{1:~ }|
|
|
{1:~ }│{1:~ }|*3
|
|
{3:[No Name] 0,0-1 All}|
|
|
|
|
|
]])
|
|
end)
|
|
|
|
it('works when switching between values of laststatus', function()
|
|
command('set laststatus=1')
|
|
screen:expect([[
|
|
^ |
|
|
{1:~ }|*14
|
|
0,0-1 All |
|
|
]])
|
|
|
|
command('set laststatus=3')
|
|
screen:expect([[
|
|
^ |
|
|
{1:~ }|*13
|
|
{3:[No Name] 0,0-1 All}|
|
|
|
|
|
]])
|
|
|
|
command('vsplit | split | vsplit | vsplit | wincmd l | split | 2wincmd l | split')
|
|
command('set laststatus=2')
|
|
screen:expect([[
|
|
│ │ │^ |
|
|
{1:~ }│{1:~ }│{1:~}│{1:~ }|*3
|
|
{1:~ }│{2:<-1 All}│{1:~}│{1:~ }|
|
|
{1:~ }│ │{1:~}│{1:~ }|
|
|
{1:~ }│{1:~ }│{1:~}│{1:~ }|
|
|
{1:~ }│{1:~ }│{1:~}│{3:< 0,0-1 All}|
|
|
{1:~ }│{1:~ }│{1:~}│ |
|
|
{2:< 0,0-1 All <-1 All <}│{1:~ }|
|
|
│{1:~ }|
|
|
{1:~ }│{1:~ }|*3
|
|
{2:[No Name] 0,0-1 All < 0,0-1 All}|
|
|
|
|
|
]])
|
|
|
|
command('set laststatus=3')
|
|
screen:expect([[
|
|
│ │ │^ |
|
|
{1:~ }│{1:~ }│{1:~}│{1:~ }|*3
|
|
{1:~ }├────────────────┤{1:~}│{1:~ }|
|
|
{1:~ }│ │{1:~}│{1:~ }|
|
|
{1:~ }│{1:~ }│{1:~}│{1:~ }|
|
|
{1:~ }│{1:~ }│{1:~}├────────────────────|
|
|
{1:~ }│{1:~ }│{1:~}│ |
|
|
────────────────────┴────────────────┴─┤{1:~ }|
|
|
│{1:~ }|
|
|
{1:~ }│{1:~ }|*3
|
|
{3:[No Name] 0,0-1 All}|
|
|
|
|
|
]])
|
|
|
|
command('set laststatus=0')
|
|
screen:expect([[
|
|
│ │ │^ |
|
|
{1:~ }│{1:~ }│{1:~}│{1:~ }|*3
|
|
{1:~ }│{2:<-1 All}│{1:~}│{1:~ }|
|
|
{1:~ }│ │{1:~}│{1:~ }|
|
|
{1:~ }│{1:~ }│{1:~}│{1:~ }|
|
|
{1:~ }│{1:~ }│{1:~}│{3:< 0,0-1 All}|
|
|
{1:~ }│{1:~ }│{1:~}│ |
|
|
{2:< 0,0-1 All <-1 All <}│{1:~ }|
|
|
│{1:~ }|
|
|
{1:~ }│{1:~ }|*4
|
|
0,0-1 All |
|
|
]])
|
|
|
|
command('set laststatus=3')
|
|
screen:expect([[
|
|
│ │ │^ |
|
|
{1:~ }│{1:~ }│{1:~}│{1:~ }|*3
|
|
{1:~ }├────────────────┤{1:~}│{1:~ }|
|
|
{1:~ }│ │{1:~}│{1:~ }|
|
|
{1:~ }│{1:~ }│{1:~}│{1:~ }|
|
|
{1:~ }│{1:~ }│{1:~}├────────────────────|
|
|
{1:~ }│{1:~ }│{1:~}│ |
|
|
────────────────────┴────────────────┴─┤{1:~ }|
|
|
│{1:~ }|
|
|
{1:~ }│{1:~ }|*3
|
|
{3:[No Name] 0,0-1 All}|
|
|
|
|
|
]])
|
|
end)
|
|
|
|
it('win_move_statusline() can reduce cmdheight to 1', function()
|
|
eq(1, api.nvim_get_option_value('cmdheight', {}))
|
|
fn.win_move_statusline(0, -1)
|
|
eq(2, api.nvim_get_option_value('cmdheight', {}))
|
|
fn.win_move_statusline(0, -1)
|
|
eq(3, api.nvim_get_option_value('cmdheight', {}))
|
|
fn.win_move_statusline(0, 1)
|
|
eq(2, api.nvim_get_option_value('cmdheight', {}))
|
|
fn.win_move_statusline(0, 1)
|
|
eq(1, api.nvim_get_option_value('cmdheight', {}))
|
|
end)
|
|
|
|
it('mouse dragging can reduce cmdheight to 1', function()
|
|
command('set mouse=a')
|
|
api.nvim_input_mouse('left', 'press', '', 0, 14, 10)
|
|
eq(1, api.nvim_get_option_value('cmdheight', {}))
|
|
api.nvim_input_mouse('left', 'drag', '', 0, 13, 10)
|
|
eq(2, api.nvim_get_option_value('cmdheight', {}))
|
|
api.nvim_input_mouse('left', 'drag', '', 0, 12, 10)
|
|
eq(3, api.nvim_get_option_value('cmdheight', {}))
|
|
api.nvim_input_mouse('left', 'drag', '', 0, 13, 10)
|
|
eq(2, api.nvim_get_option_value('cmdheight', {}))
|
|
api.nvim_input_mouse('left', 'drag', '', 0, 14, 10)
|
|
eq(1, api.nvim_get_option_value('cmdheight', {}))
|
|
api.nvim_input_mouse('left', 'drag', '', 0, 15, 10)
|
|
eq(1, api.nvim_get_option_value('cmdheight', {}))
|
|
api.nvim_input_mouse('left', 'drag', '', 0, 14, 10)
|
|
eq(1, api.nvim_get_option_value('cmdheight', {}))
|
|
end)
|
|
|
|
it('cmdline row is correct after setting cmdheight #20514', function()
|
|
command('botright split test/functional/fixtures/bigfile.txt')
|
|
api.nvim_set_option_value('cmdheight', 1, {})
|
|
feed('L')
|
|
screen:expect([[
|
|
|
|
|
{1:~ }|*5
|
|
────────────────────────────────────────────────────────────|
|
|
0000;<control>;Cc;0;BN;;;;;N;NULL;;;; |
|
|
0001;<control>;Cc;0;BN;;;;;N;START OF HEADING;;;; |
|
|
0002;<control>;Cc;0;BN;;;;;N;START OF TEXT;;;; |
|
|
0003;<control>;Cc;0;BN;;;;;N;END OF TEXT;;;; |
|
|
0004;<control>;Cc;0;BN;;;;;N;END OF TRANSMISSION;;;; |
|
|
0005;<control>;Cc;0;BN;;;;;N;ENQUIRY;;;; |
|
|
^0006;<control>;Cc;0;BN;;;;;N;ACKNOWLEDGE;;;; |
|
|
{3:test/functional/fixtures/bigfile.txt 7,1 Top}|
|
|
|
|
|
]])
|
|
feed('j')
|
|
screen:expect([[
|
|
|
|
|
{1:~ }|*5
|
|
────────────────────────────────────────────────────────────|
|
|
0001;<control>;Cc;0;BN;;;;;N;START OF HEADING;;;; |
|
|
0002;<control>;Cc;0;BN;;;;;N;START OF TEXT;;;; |
|
|
0003;<control>;Cc;0;BN;;;;;N;END OF TEXT;;;; |
|
|
0004;<control>;Cc;0;BN;;;;;N;END OF TRANSMISSION;;;; |
|
|
0005;<control>;Cc;0;BN;;;;;N;ENQUIRY;;;; |
|
|
0006;<control>;Cc;0;BN;;;;;N;ACKNOWLEDGE;;;; |
|
|
^0007;<control>;Cc;0;BN;;;;;N;BELL;;;; |
|
|
{3:test/functional/fixtures/bigfile.txt 8,1 0%}|
|
|
|
|
|
]])
|
|
api.nvim_set_option_value('showtabline', 2, {})
|
|
screen:expect([[
|
|
{5: }{100:2}{5: t/f/f/bigfile.txt }{2: }|
|
|
|
|
|
{1:~ }|*5
|
|
────────────────────────────────────────────────────────────|
|
|
0002;<control>;Cc;0;BN;;;;;N;START OF TEXT;;;; |
|
|
0003;<control>;Cc;0;BN;;;;;N;END OF TEXT;;;; |
|
|
0004;<control>;Cc;0;BN;;;;;N;END OF TRANSMISSION;;;; |
|
|
0005;<control>;Cc;0;BN;;;;;N;ENQUIRY;;;; |
|
|
0006;<control>;Cc;0;BN;;;;;N;ACKNOWLEDGE;;;; |
|
|
^0007;<control>;Cc;0;BN;;;;;N;BELL;;;; |
|
|
{3:test/functional/fixtures/bigfile.txt 8,1 0%}|
|
|
|
|
|
]])
|
|
api.nvim_set_option_value('cmdheight', 0, {})
|
|
screen:expect([[
|
|
{5: }{100:2}{5: t/f/f/bigfile.txt }{2: }|
|
|
|
|
|
{1:~ }|*5
|
|
────────────────────────────────────────────────────────────|
|
|
0001;<control>;Cc;0;BN;;;;;N;START OF HEADING;;;; |
|
|
0002;<control>;Cc;0;BN;;;;;N;START OF TEXT;;;; |
|
|
0003;<control>;Cc;0;BN;;;;;N;END OF TEXT;;;; |
|
|
0004;<control>;Cc;0;BN;;;;;N;END OF TRANSMISSION;;;; |
|
|
0005;<control>;Cc;0;BN;;;;;N;ENQUIRY;;;; |
|
|
0006;<control>;Cc;0;BN;;;;;N;ACKNOWLEDGE;;;; |
|
|
^0007;<control>;Cc;0;BN;;;;;N;BELL;;;; |
|
|
{3:test/functional/fixtures/bigfile.txt 8,1 0%}|
|
|
]])
|
|
api.nvim_set_option_value('cmdheight', 1, {})
|
|
screen:expect([[
|
|
{5: }{100:2}{5: t/f/f/bigfile.txt }{2: }|
|
|
|
|
|
{1:~ }|*5
|
|
────────────────────────────────────────────────────────────|
|
|
0002;<control>;Cc;0;BN;;;;;N;START OF TEXT;;;; |
|
|
0003;<control>;Cc;0;BN;;;;;N;END OF TEXT;;;; |
|
|
0004;<control>;Cc;0;BN;;;;;N;END OF TRANSMISSION;;;; |
|
|
0005;<control>;Cc;0;BN;;;;;N;ENQUIRY;;;; |
|
|
0006;<control>;Cc;0;BN;;;;;N;ACKNOWLEDGE;;;; |
|
|
^0007;<control>;Cc;0;BN;;;;;N;BELL;;;; |
|
|
{3:test/functional/fixtures/bigfile.txt 8,1 0%}|
|
|
|
|
|
]])
|
|
end)
|
|
|
|
it('vertical separator connector is not lost when switching window', function()
|
|
screen:add_extra_attr_ids {
|
|
[101] = { background = tonumber('0x282828') },
|
|
[102] = { bold = true, background = tonumber('0x282828'), foreground = Screen.colors.Blue1 },
|
|
}
|
|
command('hi NormalNC guibg=#282828')
|
|
command('vsplit | wincmd l | split')
|
|
-- Cursor is in top-right. Move to bottom-right.
|
|
feed('<C-w>j')
|
|
-- Verify the ├ connector is present.
|
|
screen:expect([[
|
|
{101: }│{101: }|
|
|
{102:~ }│{102:~ }|*6
|
|
{102:~ }├─────────────────────────────|
|
|
{102:~ }│^ |
|
|
{102:~ }│{1:~ }|*5
|
|
{3:[No Name] 0,0-1 All}|
|
|
|
|
|
]])
|
|
-- Navigate from bottom-right to left.
|
|
feed('<C-w>h')
|
|
-- The ├ connector must still be present.
|
|
screen:expect([[
|
|
^ │{101: }|
|
|
{1:~ }│{102:~ }|*6
|
|
{1:~ }├─────────────────────────────|
|
|
{1:~ }│{101: }|
|
|
{1:~ }│{102:~ }|*5
|
|
{3:[No Name] 0,0-1 All}|
|
|
|
|
|
]])
|
|
end)
|
|
|
|
it('horizontal separator connector is not lost when switching window', function()
|
|
screen:add_extra_attr_ids {
|
|
[101] = { background = tonumber('0x282828') },
|
|
[102] = { bold = true, background = tonumber('0x282828'), foreground = Screen.colors.Blue1 },
|
|
}
|
|
command('hi NormalNC guibg=#282828')
|
|
command('split | wincmd j | vsplit')
|
|
-- Cursor is in bottom-left. Move to bottom-right.
|
|
feed('<C-w>l')
|
|
-- Verify the ┬ connector is present.
|
|
screen:expect([[
|
|
{101: }|
|
|
{102:~ }|*6
|
|
──────────────────────────────┬─────────────────────────────|
|
|
{101: }│^ |
|
|
{102:~ }│{1:~ }|*5
|
|
{3:[No Name] 0,0-1 All}|
|
|
|
|
|
]])
|
|
-- Navigate from bottom-right to top.
|
|
feed('<C-w>k')
|
|
-- The ┬ connector must still be present.
|
|
screen:expect([[
|
|
^ |
|
|
{1:~ }|*6
|
|
──────────────────────────────┬─────────────────────────────|
|
|
{101: }│{101: }|
|
|
{102:~ }│{102:~ }|*5
|
|
{3:[No Name] 0,0-1 All}|
|
|
|
|
|
]])
|
|
end)
|
|
|
|
it('horizontal separators unchanged when failing to split-move window', function()
|
|
exec([[
|
|
botright split
|
|
let &winwidth = &columns
|
|
let &winminwidth = &columns
|
|
]])
|
|
eq('Vim(wincmd):E36: Not enough room', pcall_err(command, 'wincmd L'))
|
|
command('mode')
|
|
screen:expect([[
|
|
|
|
|
{1:~ }|*5
|
|
────────────────────────────────────────────────────────────|
|
|
^ |
|
|
{1:~ }|*6
|
|
{3:[No Name] 0,0-1 All}|
|
|
|
|
|
]])
|
|
|
|
-- Shouldn't gain a hsep if the global statusline is turned off.
|
|
command('set laststatus=2')
|
|
eq('Vim(wincmd):E36: Not enough room', pcall_err(command, 'wincmd L'))
|
|
command('mode')
|
|
screen:expect([[
|
|
|
|
|
{1:~ }|*5
|
|
{2:[No Name] 0,0-1 All}|
|
|
^ |
|
|
{1:~ }|*6
|
|
{3:[No Name] 0,0-1 All}|
|
|
|
|
|
]])
|
|
end)
|
|
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('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('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('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('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('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)
|
|
|
|
-- Visual selection other end change #36280
|
|
exec([[
|
|
function! DebugVisualSelection()
|
|
return printf("v %s %s", col("v"), col("."))
|
|
endfunction
|
|
set statusline=%!DebugVisualSelection()
|
|
]])
|
|
feed('iabc<Esc>v')
|
|
screen:expect([[
|
|
ab^c |
|
|
{1:~ }|*5
|
|
{3:v 3 3 }|
|
|
{5:-- VISUAL --} |
|
|
]])
|
|
feed('iw')
|
|
screen:expect([[
|
|
{17:ab}^c |
|
|
{1:~ }|*5
|
|
{3:v 1 3 }|
|
|
{5:-- VISUAL --} |
|
|
]])
|
|
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('ruler is cleared when window without statusline is closed', function()
|
|
local cfg = { relative = 'editor', row = 1, col = 1, height = 1, width = 1 }
|
|
local win = api.nvim_open_win(0, true, cfg)
|
|
-- Last line ruler does not take on window-local highlights #38777
|
|
command('hi link MsgArea Normal | setlocal winhl=Normal:Statement | set ruler laststatus=2')
|
|
screen:expect([[
|
|
|
|
|
{1:~}{15:^ }{1: }|
|
|
{1:~ }|*4
|
|
{2:[No Name] 0,0-1 All}|
|
|
0,0-1 All |
|
|
]])
|
|
api.nvim_win_close(win, true)
|
|
screen:expect([[
|
|
^ |
|
|
{1:~ }|*5
|
|
{3:[No Name] 0,0-1 All}|
|
|
|
|
|
]])
|
|
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('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('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:<o 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)
|
|
|
|
it('truncation inside nested nvim_eval_statusline does not crash #36616', function()
|
|
exec_lua(function()
|
|
function _G.statusline_truncating()
|
|
local win = vim.api.nvim_get_current_win()
|
|
local res = vim.api.nvim_eval_statusline('%f', { winid = win, maxwidth = 5 })
|
|
return res.str
|
|
end
|
|
vim.o.laststatus = 2
|
|
vim.o.statusline = '%#Special#B:%{%v:lua.statusline_truncating()%}'
|
|
end)
|
|
local truncated = exec_lua(function()
|
|
return vim.api.nvim_eval_statusline('%f', { maxwidth = 5 }).str
|
|
end)
|
|
local rendered = exec_lua(function()
|
|
return vim.api.nvim_eval_statusline(
|
|
vim.o.statusline,
|
|
{ winid = vim.api.nvim_get_current_win() }
|
|
).str
|
|
end)
|
|
eq('B:' .. truncated, rendered)
|
|
end)
|
|
|
|
it('truncation inside nested nvim_eval_statusline correctly adjusts highlights', function()
|
|
exec_lua('vim.o.laststatus = 2')
|
|
exec_lua([[vim.o.statusline = "%{%repeat('%#Error#',20)%}"]])
|
|
exec_lua([[
|
|
vim.o.statusline = "%l%l%l%l%{%nvim_eval_statusline('test%#Error#%l%<',{'maxwidth':4,'highlights':1}).highlights%}"
|
|
]])
|
|
screen:expect([[
|
|
^ |
|
|
{1:~ }|*5
|
|
{3:< 'groups': ['StatusLine'], 'start': 0}]}|
|
|
|
|
|
]])
|
|
end)
|
|
|
|
it('no cmdline ruler for autocmd window #39938', function()
|
|
command('set ruler laststatus=2')
|
|
api.nvim_create_autocmd('BufDelete', { command = 'redrawstatus' })
|
|
screen:expect([[
|
|
^ |
|
|
{1:~ }|*5
|
|
{3:[No Name] 0,0-1 All}|
|
|
|
|
|
]])
|
|
api.nvim_exec_autocmds('BufDelete', { buf = api.nvim_create_buf(true, true) })
|
|
screen:expect_unchanged(true)
|
|
end)
|
|
|
|
it('active window is correct after nvim_exec_autocmds({buf}) #40153', function()
|
|
command('set laststatus=2')
|
|
local caller_win = api.nvim_get_current_win()
|
|
command('split')
|
|
local target = api.nvim_create_buf(true, false)
|
|
api.nvim_set_current_buf(target)
|
|
api.nvim_set_current_win(caller_win)
|
|
exec_lua(function(buf)
|
|
_G.in_aucmd_statusline = false
|
|
_G.statusline_contexts = {}
|
|
_G.Status = function()
|
|
local current = vim.api.nvim_get_current_win()
|
|
local statusline = vim.g.statusline_winid
|
|
local focus = current == statusline
|
|
table.insert(_G.statusline_contexts, {
|
|
current = current,
|
|
focus = focus,
|
|
in_aucmd = _G.in_aucmd_statusline,
|
|
statusline = statusline,
|
|
})
|
|
return focus and 'CUR' or 'nc'
|
|
end
|
|
vim.o.statusline = '%!v:lua.Status()'
|
|
vim.api.nvim_create_autocmd('User', {
|
|
buffer = buf,
|
|
callback = function(ev)
|
|
_G.in_aucmd_statusline = true
|
|
vim.api.nvim__redraw({ buf = ev.buf, statusline = true })
|
|
_G.in_aucmd_statusline = false
|
|
end,
|
|
})
|
|
end, target)
|
|
-- Ignore statusline evaluations from setup; only the autocmd redraw matters.
|
|
exec_lua('_G.statusline_contexts = {}')
|
|
api.nvim_exec_autocmds('User', { buf = target })
|
|
screen:expect([[
|
|
|
|
|
{1:~ }|*2
|
|
{2:nc }|
|
|
^ |
|
|
{1:~ }|
|
|
{3:CUR }|
|
|
|
|
|
]])
|
|
-- `%!` may evaluate during the callback, but must not see the target as focused.
|
|
eq(
|
|
{},
|
|
exec_lua(function(win)
|
|
return vim
|
|
.iter(_G.statusline_contexts)
|
|
:filter(function(context)
|
|
return context.in_aucmd and context.focus and context.current ~= win
|
|
end)
|
|
:totable()
|
|
end, caller_win)
|
|
)
|
|
end)
|
|
|
|
it('statusline evaluation during nvim_exec_autocmds({buf}) #40153', function()
|
|
command('set laststatus=2')
|
|
local caller_win = api.nvim_get_current_win()
|
|
command('split')
|
|
local target = api.nvim_create_buf(true, false)
|
|
api.nvim_set_current_buf(target)
|
|
api.nvim_set_current_win(caller_win)
|
|
exec_lua(function(buf)
|
|
_G.in_aucmd_statusline = false
|
|
_G.statusline_contexts = {}
|
|
_G.Status = function()
|
|
table.insert(_G.statusline_contexts, {
|
|
in_aucmd = _G.in_aucmd_statusline,
|
|
current = vim.api.nvim_get_current_win(),
|
|
actual = tonumber(vim.g.actual_curwin or -1),
|
|
})
|
|
return vim.api.nvim_get_current_win() == tonumber(vim.g.actual_curwin or -1) and 'CUR'
|
|
or 'nc'
|
|
end
|
|
-- `%{%...%}` sets g:actual_curwin while evaluating the statusline.
|
|
vim.o.statusline = '%{%v:lua.Status()%}'
|
|
vim.api.nvim_create_autocmd('User', {
|
|
buffer = buf,
|
|
callback = function(ev)
|
|
_G.in_aucmd_statusline = true
|
|
vim.api.nvim__redraw({ buf = ev.buf, statusline = true })
|
|
_G.in_aucmd_statusline = false
|
|
end,
|
|
})
|
|
end, target)
|
|
screen:expect([[
|
|
|
|
|
{1:~ }|*2
|
|
{2:nc }|
|
|
^ |
|
|
{1:~ }|
|
|
{3:CUR }|
|
|
|
|
|
]])
|
|
-- Ignore statusline evaluations from setup; only the autocmd redraw matters.
|
|
exec_lua('_G.statusline_contexts = {}')
|
|
api.nvim_exec_autocmds('User', { buf = target })
|
|
screen:expect({ unchanged = true })
|
|
local contexts = exec_lua(function()
|
|
return _G.statusline_contexts
|
|
end)
|
|
eq(true, #contexts > 0)
|
|
eq(caller_win, contexts[#contexts].actual)
|
|
end)
|
|
end)
|
|
|
|
describe('default statusline', function()
|
|
local screen
|
|
|
|
before_each(function()
|
|
clear()
|
|
screen = Screen.new(60, 16)
|
|
screen:add_extra_attr_ids {
|
|
[100] = { foreground = Screen.colors.Magenta1, bold = true },
|
|
[131] = { foreground = Screen.colors.NvimDarkGreen },
|
|
}
|
|
command('set laststatus=2')
|
|
command('set ruler')
|
|
end)
|
|
|
|
it('setting statusline to empty string sets default statusline', function()
|
|
exec_lua("vim.o.statusline = 'asdf'")
|
|
eq('asdf', eval('&statusline'))
|
|
screen:expect([[
|
|
^ |
|
|
{1:~ }|*13
|
|
{3:asdf }|
|
|
|
|
|
]])
|
|
|
|
local default_statusline = table.concat({
|
|
'%<',
|
|
'%f %h%w%m%r ',
|
|
"%{% v:lua.require('vim._core.util').term_exitcode() %}",
|
|
'%=',
|
|
"%{% luaeval('(package.loaded[''vim.ui''] and vim.api.nvim_get_current_win() == tonumber(vim.g.actual_curwin or -1) and vim.ui.progress_status()) or '''' ')%}",
|
|
"%{% &showcmdloc == 'statusline' ? '%-10.S ' : '' %}",
|
|
"%{% exists('b:keymap_name') ? '<'..b:keymap_name..'> ' : '' %}",
|
|
"%{% &busy > 0 ? '◐ ' : '' %}",
|
|
"%{% luaeval('(package.loaded[''vim.diagnostic''] and next(vim.diagnostic.count()) and vim.diagnostic.status() .. '' '') or '''' ') %}",
|
|
"%{% &ruler ? ( &rulerformat == '' ? '%-14.(%l,%c%V%) %P' : &rulerformat ) : '' %}",
|
|
})
|
|
|
|
exec_lua("vim.o.statusline = ''")
|
|
eq(default_statusline, eval('&statusline'))
|
|
screen:expect([[
|
|
^ |
|
|
{1:~ }|*13
|
|
{3:[No Name] 0,0-1 All}|
|
|
|
|
|
]])
|
|
|
|
-- Reset to default (via the correct scope) if there's an error.
|
|
command('setglobal statusline=%{a%}')
|
|
eq(default_statusline, eval('&statusline'))
|
|
eq(default_statusline, eval('&g:statusline'))
|
|
eq('', eval('&l:statusline'))
|
|
command('redrawstatus') -- like Vim, statusline isn't immediately redrawn after an error
|
|
screen:expect([[
|
|
^ |
|
|
{1:~ }|*13
|
|
{3:[No Name] 0,0-1 All}|
|
|
{9:E121: Undefined variable: a} |
|
|
]])
|
|
command('setlocal statusline=%{b%}')
|
|
eq(default_statusline, eval('&statusline'))
|
|
eq(default_statusline, eval('&g:statusline'))
|
|
eq('', eval('&l:statusline'))
|
|
command('redrawstatus') -- like Vim, statusline isn't immediately redrawn after an error
|
|
screen:expect([[
|
|
^ |
|
|
{1:~ }|*13
|
|
{3:[No Name] 0,0-1 All}|
|
|
{9:E121: Undefined variable: b} |
|
|
]])
|
|
end)
|
|
|
|
it('shows busy status when buffer is set to be busy', function()
|
|
exec_lua("vim.o.statusline = ''")
|
|
|
|
screen:expect([[
|
|
^ |
|
|
{1:~ }|*13
|
|
{3:[No Name] 0,0-1 All}|
|
|
|
|
|
]])
|
|
exec_lua('vim.o.busy = 1')
|
|
screen:expect([[
|
|
^ |
|
|
{1:~ }|*13
|
|
{3:[No Name] ◐ 0,0-1 All}|
|
|
|
|
|
]])
|
|
|
|
exec_lua('vim.o.busy = 0')
|
|
screen:expect([[
|
|
^ |
|
|
{1:~ }|*13
|
|
{3:[No Name] 0,0-1 All}|
|
|
|
|
|
]])
|
|
end)
|
|
|
|
it('shows exit code when terminal exits #14986', function()
|
|
exec_lua("vim.o.statusline = ''")
|
|
api.nvim_set_option_value('shell', testprg('shell-test'), {})
|
|
api.nvim_set_option_value('shellcmdflag', 'EXIT', {})
|
|
api.nvim_set_option_value('shellxquote', '', {}) -- win: avoid extra quotes
|
|
command('terminal 9')
|
|
screen:expect({ any = '%[Exit: 9%]' })
|
|
expect_exitcode(9)
|
|
end)
|
|
|
|
it('shows and updates progress status', function()
|
|
exec_lua("vim.o.statusline = ''")
|
|
local function get_progress()
|
|
return exec_lua(function()
|
|
local stl_str = vim.ui.progress_status()
|
|
return vim.api.nvim_eval_statusline(stl_str, {}).str
|
|
end)
|
|
end
|
|
|
|
eq('', get_progress())
|
|
---@type integer|string
|
|
local id1 = api.nvim_echo(
|
|
{ { 'searching...' } },
|
|
true,
|
|
{ kind = 'progress', source = 'tests', title = 'test', status = 'running', percent = 10 }
|
|
)
|
|
eq('10%(1) ', get_progress())
|
|
|
|
api.nvim_echo({ { 'searching' } }, true, {
|
|
id = id1,
|
|
kind = 'progress',
|
|
source = 'tests',
|
|
percent = 50,
|
|
status = 'running',
|
|
title = 'terminal(ripgrep)',
|
|
})
|
|
eq('50%(1) ', get_progress())
|
|
|
|
api.nvim_echo({ { 'searching...' } }, true, {
|
|
kind = 'progress',
|
|
source = 'tests',
|
|
title = 'second-item',
|
|
status = 'running',
|
|
percent = 20,
|
|
})
|
|
eq('35%(2) ', get_progress())
|
|
|
|
api.nvim_echo({ { 'searching' } }, true, {
|
|
id = id1,
|
|
kind = 'progress',
|
|
source = 'tests',
|
|
percent = 100,
|
|
status = 'success',
|
|
title = 'terminal(ripgrep)',
|
|
})
|
|
eq('20%(1) ', get_progress())
|
|
|
|
exec('redrawstatus')
|
|
screen:expect([[
|
|
^ |
|
|
{1:~ }|*13
|
|
{3:[No Name] 20%(1) 0,0-1 All}|
|
|
{131:terminal(ripgrep)}: {19:100% }searching |
|
|
]])
|
|
|
|
-- Progress_status only shown on active window
|
|
exec('split')
|
|
screen:expect([[
|
|
^ |
|
|
{1:~ }|*6
|
|
{3:[No Name] 20%(1) 0,0-1 All}|
|
|
|
|
|
{1:~ }|*5
|
|
{2:[No Name] 0,0-1 All}|
|
|
{131:terminal(ripgrep)}: {19:100% }searching |
|
|
]])
|
|
|
|
exec('wincmd w')
|
|
screen:expect([[
|
|
|
|
|
{1:~ }|*6
|
|
{2:[No Name] 0,0-1 All}|
|
|
^ |
|
|
{1:~ }|*5
|
|
{3:[No Name] 20%(1) 0,0-1 All}|
|
|
{131:terminal(ripgrep)}: {19:100% }searching |
|
|
]])
|
|
end)
|
|
end)
|
|
|
|
describe("'statusline' in floatwin", function()
|
|
local screen
|
|
before_each(function()
|
|
clear()
|
|
screen = Screen.new(30, 20)
|
|
screen:add_extra_attr_ids({
|
|
[101] = { foreground = Screen.colors.Magenta1, bold = true },
|
|
[102] = {
|
|
foreground = Screen.colors.Magenta1,
|
|
underline = true,
|
|
background = Screen.colors.LightGray,
|
|
bold = true,
|
|
},
|
|
})
|
|
end)
|
|
|
|
it('controlled by ":setlocal statusline" and "style" and "laststatus"', function()
|
|
local buf = api.nvim_create_buf(false, false)
|
|
api.nvim_buf_set_lines(buf, 0, -1, false, { '1', '2', '3', '4' })
|
|
local cfg = {
|
|
relative = 'editor',
|
|
row = 1,
|
|
col = 1,
|
|
height = 4,
|
|
width = 10,
|
|
border = 'single',
|
|
}
|
|
local win = api.nvim_open_win(buf, true, cfg)
|
|
local set_stl = [[setlocal stl=%f\ %m]]
|
|
command('set laststatus=2')
|
|
command(set_stl)
|
|
local has_stl = [[
|
|
|
|
|
{1:~}┌──────────┐{1: }|
|
|
{1:~}│{4:^1 }│{1: }|
|
|
{1:~}│{4:2 }│{1: }|
|
|
{1:~}│{4:3 }│{1: }|
|
|
{1:~}│{4:4 }│{1: }|
|
|
{1:~}│{3:<Name] [+]}│{1: }|
|
|
{1:~}└──────────┘{1: }|
|
|
{1:~ }|*10
|
|
{2:[No Name] }|
|
|
|
|
|
]]
|
|
screen:expect(has_stl)
|
|
|
|
-- setting the style will clear the statusline expression for floating windows
|
|
api.nvim_win_set_config(win, { style = 'minimal' })
|
|
local without_stl = [[
|
|
|
|
|
{1:~}┌──────────┐{1: }|
|
|
{1:~}│{4:^1 }│{1: }|
|
|
{1:~}│{4:2 }│{1: }|
|
|
{1:~}│{4:3 }│{1: }|
|
|
{1:~}│{4:4 }│{1: }|
|
|
{1:~}└──────────┘{1: }|
|
|
{1:~ }|*11
|
|
{2:[No Name] }|
|
|
|
|
|
]]
|
|
screen:expect(without_stl)
|
|
|
|
-- no statusline is displayed because the statusline option was cleared
|
|
api.nvim_win_set_config(win, cfg)
|
|
screen:expect_unchanged()
|
|
|
|
-- displayed after the option is reset
|
|
command(set_stl)
|
|
screen:expect(has_stl)
|
|
|
|
-- Show in a new window in a new tab, then return to the previous tab;
|
|
-- remove the statusline of the new window, When re-entering this new tab,
|
|
-- the statusline of the new window is cleared
|
|
command('tabnew')
|
|
local win2 = api.nvim_open_win(buf, false, cfg)
|
|
command(set_stl)
|
|
screen:expect([[
|
|
{24: }{102:2}{24:+ [No Name] }{5: }{101:2}{5:+ [No Name] }{2: }{24:X}|
|
|
^ ┌──────────┐ |
|
|
{1:~}│{4:1 }│{1: }|
|
|
{1:~}│{4:2 }│{1: }|
|
|
{1:~}│{4:3 }│{1: }|
|
|
{1:~}│{4:4 }│{1: }|
|
|
{1:~}└──────────┘{1: }|
|
|
{1:~ }|*11
|
|
{3:[No Name] }|
|
|
|
|
|
]])
|
|
command('tabfirst')
|
|
api.nvim_win_set_config(win2, { style = 'minimal' })
|
|
screen:expect([[
|
|
{5: }{101:2}{5:+ [No Name] }{24: }{102:2}{24:+ [No Name] }{2: }{24:X}|
|
|
┌──────────┐ |
|
|
{1:~}│{4:^1 }│{1: }|
|
|
{1:~}│{4:2 }│{1: }|
|
|
{1:~}│{4:3 }│{1: }|
|
|
{1:~}│{4:4 }│{1: }|
|
|
{1:~}│{3:<Name] [+]}│{1: }|
|
|
{1:~}└──────────┘{1: }|
|
|
{1:~ }|*10
|
|
{2:[No Name] }|
|
|
|
|
|
]])
|
|
command('tabnext')
|
|
screen:expect([[
|
|
{24: }{102:2}{24:+ [No Name] }{5: }{101:2}{5:+ [No Name] }{2: }{24:X}|
|
|
^ ┌──────────┐ |
|
|
{1:~}│{4:1 }│{1: }|
|
|
{1:~}│{4:2 }│{1: }|
|
|
{1:~}│{4:3 }│{1: }|
|
|
{1:~}│{4:4 }│{1: }|
|
|
{1:~}└──────────┘{1: }|
|
|
{1:~ }|*11
|
|
{3:[No Name] }|
|
|
|
|
|
]])
|
|
-- clear statusline when laststatus is 3
|
|
command('tabclose | set laststatus=2')
|
|
screen:expect([[
|
|
|
|
|
{1:~}┌──────────┐{1: }|
|
|
{1:~}│{4:^1 }│{1: }|
|
|
{1:~}│{4:2 }│{1: }|
|
|
{1:~}│{4:3 }│{1: }|
|
|
{1:~}│{4:4 }│{1: }|
|
|
{1:~}│{3:<Name] [+]}│{1: }|
|
|
{1:~}└──────────┘{1: }|
|
|
{1:~ }|*10
|
|
{2:[No Name] }|
|
|
|
|
|
]])
|
|
command('set laststatus=0')
|
|
screen:expect([[
|
|
|
|
|
{1:~}┌──────────┐{1: }|
|
|
{1:~}│{4:^1 }│{1: }|
|
|
{1:~}│{4:2 }│{1: }|
|
|
{1:~}│{4:3 }│{1: }|
|
|
{1:~}│{4:4 }│{1: }|
|
|
{1:~}└──────────┘{1: }|
|
|
{1:~ }|*12
|
|
|
|
|
]])
|
|
|
|
command('set laststatus=3')
|
|
screen:expect([[
|
|
|
|
|
{1:~}┌──────────┐{1: }|
|
|
{1:~}│{4:^1 }│{1: }|
|
|
{1:~}│{4:2 }│{1: }|
|
|
{1:~}│{4:3 }│{1: }|
|
|
{1:~}│{4:4 }│{1: }|
|
|
{1:~}└──────────┘{1: }|
|
|
{1:~ }|*11
|
|
{3:[No Name] [+] }|
|
|
|
|
|
]])
|
|
api.nvim_buf_set_name(buf, 'stl_test')
|
|
screen:expect([[
|
|
|
|
|
{1:~}┌──────────┐{1: }|
|
|
{1:~}│{4:^1 }│{1: }|
|
|
{1:~}│{4:2 }│{1: }|
|
|
{1:~}│{4:3 }│{1: }|
|
|
{1:~}│{4:4 }│{1: }|
|
|
{1:~}└──────────┘{1: }|
|
|
{1:~ }|*11
|
|
{3:stl_test [+] }|
|
|
|
|
|
]])
|
|
end)
|
|
|
|
it("clears inherited window-local 'statusline' on creation", function()
|
|
command('set laststatus=2')
|
|
api.nvim_set_option_value('statusline', 'global', {})
|
|
local curwin = api.nvim_get_current_win()
|
|
api.nvim_set_option_value('statusline', 'split-local', { win = curwin })
|
|
api.nvim_open_win(0, true, { relative = 'editor', row = 1, col = 1, height = 2, width = 4 })
|
|
screen:expect([[
|
|
|
|
|
{1:~}{4:^ }{1: }|
|
|
{1:~}{11:~ }{1: }|
|
|
{1:~ }|*15
|
|
{2:split-local }|
|
|
|
|
|
]])
|
|
end)
|
|
end)
|