feat(ui): show "empty" cursor behind unfocused floatwin #37624

Problem:
There is no indication of when cursor is "behind" an unfocused, floating window.

Solution:
Set underline cursor to indicate when an unfocused floatwin is over the active cursor.
This commit is contained in:
glepnir
2026-02-04 23:09:50 +08:00
committed by GitHub
parent 379e307148
commit a2b92a5efb
2 changed files with 438 additions and 0 deletions

View File

@@ -571,6 +571,17 @@ void ui_flush(void)
// by nvim core, not the compositor)
win_ui_flush(false);
}
// Show "empty box" (underline style) cursor if behind a floatwin.
if (!(State & MODE_CMDLINE)) {
bool cursor_obscured = ui_cursor_is_behind_floatwin();
int new_idx = cursor_obscured ? SHAPE_IDX_R : cursor_get_mode_idx();
if (ui_mode_idx != new_idx) {
ui_mode_idx = new_idx;
pending_mode_update = true;
}
}
if (pending_mode_info_update) {
Arena arena = ARENA_EMPTY;
Array style = mode_style_array(&arena);
@@ -674,6 +685,22 @@ void ui_cursor_shape(void)
conceal_check_cursor_line();
}
/// Check if the cursor is behind a floating window (only in compositor mode).
/// @return true if cursor is obscured by a float with higher zindex
static bool ui_cursor_is_behind_floatwin(void)
{
if (!ui_comp_should_draw()) {
return false;
}
int crow = curwin->w_winrow + curwin->w_winrow_off + curwin->w_wrow;
int ccol = curwin->w_wincol + curwin->w_wincol_off
+ (curwin->w_p_rl ? curwin->w_view_width - curwin->w_wcol - 1 : curwin->w_wcol);
ScreenGrid *top_grid = ui_comp_get_grid_at_coord(crow, ccol);
return top_grid != &curwin->w_grid_alloc && top_grid != &default_grid;
}
/// Returns true if the given UI extension is enabled.
bool ui_has(UIExtension ext)
{

View File

@@ -11251,6 +11251,417 @@ describe('float window', function()
eq('Vim(set):E474: Invalid argument: winborder=+,-,+,|,+,-,+,', pcall_err(command, [[set winborder=+,-,+,\|,+,-,+,]]))
eq('Vim(set):E474: Invalid argument: winborder=custom', pcall_err(command, 'set winborder=custom'))
end)
it('cursor shape when the cursor is covered by a floating window', function()
local normal_win = api.nvim_get_current_win()
api.nvim_buf_set_lines(0, 0, -1, true, { 'one', 'two' })
api.nvim_win_set_cursor(0, { 2, 2 })
local buf = api.nvim_create_buf(false, false)
api.nvim_buf_set_lines(buf, 0, 0, true, { 'the only line' })
local win = api.nvim_open_win(buf, false, { relative = 'editor', row = 0, col = 0, height = 2, width = 20 })
if multigrid then
screen:expect({
grid = [[
## grid 1
[2:----------------------------------------]|*6
[3:----------------------------------------]|
## grid 2
one |
tw^o |
{0:~ }|*4
## grid 3
|
## grid 4
{1:the only line }|
{1: }|
]],
win_pos = {
[2] = { height = 6, startcol = 0, startrow = 0, width = 40, win = 1000 },
},
float_pos = {
[4] = { 1001, 'NW', 1, 0, 0, true, 50, 1, 0, 0 },
},
mode = 'normal',
})
else
screen:expect {
grid = [[
{1:the only line } |
{1: ^ } |
{0:~ }|*4
|
]],
mode = 'replace',
}
end
api.nvim_buf_set_lines(0, 0, -1, true, { 'one', 'two', 'three' })
feed('<Down>')
if multigrid then
screen:expect({
grid = [[
## grid 1
[2:----------------------------------------]|*6
[3:----------------------------------------]|
## grid 2
one |
two |
th^ree |
{0:~ }|*3
## grid 3
|
## grid 4
{1:the only line }|
{1: }|
]],
win_pos = {
[2] = { height = 6, startcol = 0, startrow = 0, width = 40, win = 1000 },
},
float_pos = {
[4] = { 1001, 'NW', 1, 0, 0, true, 50, 1, 0, 0 },
},
mode = 'normal',
})
else
screen:expect { mode = 'normal' }
end
-- Cursor shape on a lower z-index floating window
buf = api.nvim_create_buf(false, false)
api.nvim_buf_set_lines(buf, 0, 0, true, { 'highest' })
local high_win = api.nvim_open_win(buf, false, { relative = 'editor', row = 0, col = 0, height = 2, width = 7, zindex = 150 })
api.nvim_set_current_win(win)
api.nvim_win_set_cursor(win, { 2, 1 })
if multigrid then
screen:expect({
grid = [[
## grid 1
[2:----------------------------------------]|*6
[3:----------------------------------------]|
## grid 2
one |
two |
three |
{0:~ }|*3
## grid 3
|
## grid 4
{1:the only line }|
{1:^ }|
## grid 5
{1:highest}|
{1: }|
]],
win_pos = {
[2] = { height = 6, startcol = 0, startrow = 0, width = 40, win = 1000 },
},
float_pos = {
[5] = { 1002, 'NW', 1, 0, 0, true, 150, 2, 0, 0 },
[4] = { 1001, 'NW', 1, 0, 0, true, 50, 1, 0, 0 },
},
mode = 'normal',
})
else
screen:expect { mode = 'replace' }
end
api.nvim_set_current_win(high_win)
if multigrid then
screen:expect({
grid = [[
## grid 1
[2:----------------------------------------]|*6
[3:----------------------------------------]|
## grid 2
one |
two |
three |
{0:~ }|*3
## grid 3
|
## grid 4
{1:the only line }|
{1: }|
## grid 5
{1:^highest}|
{1: }|
]],
win_pos = {
[2] = { height = 6, startcol = 0, startrow = 0, width = 40, win = 1000 },
},
float_pos = {
[5] = { 1002, 'NW', 1, 0, 0, true, 150, 2, 0, 0 },
[4] = { 1001, 'NW', 1, 0, 0, true, 50, 1, 0, 0 },
},
mode = 'normal',
})
else
screen:expect {
grid = [[
{1:^highesty line } |
{1: } |
three |
{0:~ }|*3
|
]],
mode = 'normal',
}
end
buf = api.nvim_create_buf(false, false)
api.nvim_buf_set_lines(buf, 0, 0, true, { 'another' })
api.nvim_open_win(buf, true, { relative = 'editor', row = 0, col = 0, height = 2, width = 7, zindex = 160 })
if multigrid then
screen:expect({
grid = [[
## grid 1
[2:----------------------------------------]|*6
[3:----------------------------------------]|
## grid 2
one |
two |
three |
{0:~ }|*3
## grid 3
|
## grid 4
{1:the only line }|
{1: }|
## grid 5
{1:highest}|
{1: }|
## grid 6
{1:^another}|
{1: }|
]],
win_pos = {
[2] = { height = 6, startcol = 0, startrow = 0, width = 40, win = 1000 },
},
float_pos = {
[4] = { 1001, 'NW', 1, 0, 0, true, 50, 1, 0, 0 },
[5] = { 1002, 'NW', 1, 0, 0, true, 150, 2, 0, 0 },
[6] = { 1003, 'NW', 1, 0, 0, true, 160, 3, 0, 0 },
},
mode = 'normal',
})
else
screen:expect {
grid = [[
{1:^anothery line } |
{1: } |
three |
{0:~ }|*3
|
]],
mode = 'normal',
}
end
api.nvim_set_current_win(normal_win)
command('only')
screen:try_resize(50, 20)
buf = api.nvim_create_buf(false, false)
api.nvim_buf_set_lines(buf, 0, -1, true, { 'x' })
local float_win = api.nvim_open_win(buf, true, {
relative = 'editor',
width = 5,
height = 5,
row = 8,
col = 9,
border = 'single',
zindex = 1,
})
local buf2 = api.nvim_create_buf(false, false)
api.nvim_open_win(buf2, false, {
relative = 'editor',
width = 10,
height = 10,
row = 0,
col = 0,
zindex = 2,
})
if multigrid then
screen:expect({
grid = [[
## grid 1
[2:--------------------------------------------------]|*19
[3:--------------------------------------------------]|
## grid 2
one |
two |
three |
{0:~ }|*16
## grid 3
|
## grid 7
{5:┌─────┐}|
{5:│}{1:^x }{5:│}|
{5:│}{2:~ }{5:│}|*4
{5:└─────┘}|
## grid 8
{1: }|
{2:~ }|*9
]],
win_pos = {
[2] = { height = 19, startcol = 0, startrow = 0, width = 50, win = 1000 },
},
float_pos = {
[7] = { 1004, 'NW', 1, 8, 9, true, 1, 1, 8, 9 },
[8] = { 1005, 'NW', 1, 0, 0, true, 2, 2, 0, 0 },
},
mode = 'normal',
})
else
screen:expect {
grid = [[
{1: } |
{2:~ } |*2
{2:~ }{0: }|*5
{2:~ }{5:─────┐}{0: }|
{2:~ }{1:^x }{5:│}{0: }|
{0:~ }{5:│}{2:~ }{5:│}{0: }|*4
{0:~ }{5:└─────┘}{0: }|
{0:~ }|*4
|
]],
mode = 'normal',
}
end
-- Move window
api.nvim_win_set_config(float_win, { relative = 'editor', row = 9, col = 8 })
if multigrid then
screen:expect({
grid = [[
## grid 1
[2:--------------------------------------------------]|*19
[3:--------------------------------------------------]|
## grid 2
one |
two |
three |
{0:~ }|*16
## grid 3
|
## grid 7
{5:┌─────┐}|
{5:│}{1:^x }{5:│}|
{5:│}{2:~ }{5:│}|*4
{5:└─────┘}|
## grid 8
{1: }|
{2:~ }|*9
]],
win_pos = {
[2] = { height = 19, startcol = 0, startrow = 0, width = 50, win = 1000 },
},
float_pos = {
[7] = { 1004, 'NW', 1, 9, 8, true, 1, 1, 9, 8 },
[8] = { 1005, 'NW', 1, 0, 0, true, 2, 2, 0, 0 },
},
mode = 'normal',
})
else
screen:expect {
grid = [[
{1: } |
{2:~ } |*2
{2:~ }{0: }|*6
{2:~ }{5:────┐}{0: }|
{0:~ }{5:│}{1:^x }{5:│}{0: }|
{0:~ }{5:│}{2:~ }{5:│}{0: }|*4
{0:~ }{5:└─────┘}{0: }|
{0:~ }|*3
|
]],
mode = 'normal',
}
end
-- rightleft
api.nvim_win_set_config(float_win, { relative = 'editor', row = 8, col = 8 })
command('set rightleft')
if multigrid then
screen:expect({
grid = [[
## grid 1
[2:--------------------------------------------------]|*19
[3:--------------------------------------------------]|
## grid 2
one |
two |
three |
{0:~ }|*16
## grid 3
|
## grid 7
{5:┌─────┐}|
{5:│}{1: ^x}{5:│}|
{5:│}{2: ~}{5:│}|*4
{5:└─────┘}|
## grid 8
{1: }|
{2:~ }|*9
]],
win_pos = {
[2] = { height = 19, startcol = 0, startrow = 0, width = 50, win = 1000 },
},
float_pos = {
[7] = { 1004, 'NW', 1, 8, 8, true, 1, 1, 8, 8 },
[8] = { 1005, 'NW', 1, 0, 0, true, 2, 2, 0, 0 },
},
mode = 'normal',
})
else
screen:expect {
grid = [[
{1: } |
{2:~ } |*2
{2:~ }{0: }|*5
{2:~ }{5:────┐}{0: }|
{2:~ }{1: ^x}{5:│}{0: }|
{0:~ }{5:│}{2: ~}{5:│}{0: }|*4
{0:~ }{5:└─────┘}{0: }|
{0:~ }|*4
|
]],
mode = 'normal',
}
end
command('set virtualedit=all')
fn.setpos('.', { 0, 1, 1, 4 })
if multigrid then
screen:expect({
grid = [[
## grid 1
[2:--------------------------------------------------]|*19
[3:--------------------------------------------------]|
## grid 2
one |
two |
three |
{0:~ }|*16
## grid 3
|
## grid 7
{5:┌─────┐}|
{5:│}{1:^ x}{5:│}|
{5:│}{2: ~}{5:│}|*4
{5:└─────┘}|
## grid 8
{1: }|
{2:~ }|*9
]],
win_pos = {
[2] = { height = 19, startcol = 0, startrow = 0, width = 50, win = 1000 },
},
float_pos = {
[7] = { 1004, 'NW', 1, 8, 8, true, 1, 1, 8, 8 },
[8] = { 1005, 'NW', 1, 0, 0, true, 2, 2, 0, 0 },
},
mode = 'normal',
})
else
screen:expect { mode = 'replace' }
end
end)
end
describe('with ext_multigrid and actual mouse grid', function()