feat(api): nvim_open_win() relative to tabline and laststatus #32006

Problem:  Anchoring a floating window to the tabline and laststatus is
          cumbersome; requiring autocommands and looping over all
          windows/tabpages.
Solution: Add new "tabline" and "laststatus" options to the `relative`
          field of nvim_open_win() to place a window relative to.
This commit is contained in:
luukvbaal 2025-01-14 14:02:46 +01:00 committed by GitHub
parent e8ddb7a469
commit 25d8c3a5ad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 133 additions and 12 deletions

View File

@ -3150,11 +3150,13 @@ nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()*
• {config} Map defining the window configuration. Keys: • {config} Map defining the window configuration. Keys:
• relative: Sets the window layout to "floating", placed at • relative: Sets the window layout to "floating", placed at
(row,col) coordinates relative to: (row,col) coordinates relative to:
• "editor" The global editor grid • "cursor" Cursor position in current window.
• "editor" The global editor grid.
• "laststatus" 'laststatus' if present, or last row.
• "mouse" Mouse position.
• "tabline" Tabline if present, or first row.
• "win" Window given by the `win` field, or current • "win" Window given by the `win` field, or current
window. window.
• "cursor" Cursor position in current window.
• "mouse" Mouse position
• win: |window-ID| window to split, or relative window when • win: |window-ID| window to split, or relative window when
creating a float (relative="win"). creating a float (relative="win").
• anchor: Decides which corner of the float to place at • anchor: Decides which corner of the float to place at

View File

@ -187,6 +187,7 @@ API
• |nvim__ns_set()| can set properties for a namespace • |nvim__ns_set()| can set properties for a namespace
• |nvim_echo()| `err` field to print error messages and `chunks` accepts • |nvim_echo()| `err` field to print error messages and `chunks` accepts
highlight group IDs. highlight group IDs.
• |nvim_open_win()| `relative` field can be set to "laststatus" and "tabline".
DEFAULTS DEFAULTS

View File

@ -1754,10 +1754,12 @@ function vim.api.nvim_open_term(buffer, opts) end
--- @param config vim.api.keyset.win_config Map defining the window configuration. Keys: --- @param config vim.api.keyset.win_config Map defining the window configuration. Keys:
--- - relative: Sets the window layout to "floating", placed at (row,col) --- - relative: Sets the window layout to "floating", placed at (row,col)
--- coordinates relative to: --- coordinates relative to:
--- - "editor" The global editor grid --- - "cursor" Cursor position in current window.
--- - "win" Window given by the `win` field, or current window. --- - "editor" The global editor grid.
--- - "cursor" Cursor position in current window. --- - "laststatus" 'laststatus' if present, or last row.
--- - "mouse" Mouse position --- - "mouse" Mouse position.
--- - "tabline" Tabline if present, or first row.
--- - "win" Window given by the `win` field, or current window.
--- - win: `window-ID` window to split, or relative window when creating a --- - win: `window-ID` window to split, or relative window when creating a
--- float (relative="win"). --- float (relative="win").
--- - anchor: Decides which corner of the float to place at (row,col): --- - anchor: Decides which corner of the float to place at (row,col):

View File

@ -101,10 +101,12 @@
/// @param config Map defining the window configuration. Keys: /// @param config Map defining the window configuration. Keys:
/// - relative: Sets the window layout to "floating", placed at (row,col) /// - relative: Sets the window layout to "floating", placed at (row,col)
/// coordinates relative to: /// coordinates relative to:
/// - "editor" The global editor grid /// - "cursor" Cursor position in current window.
/// - "win" Window given by the `win` field, or current window. /// - "editor" The global editor grid.
/// - "cursor" Cursor position in current window. /// - "laststatus" 'laststatus' if present, or last row.
/// - "mouse" Mouse position /// - "mouse" Mouse position.
/// - "tabline" Tabline if present, or first row.
/// - "win" Window given by the `win` field, or current window.
/// - win: |window-ID| window to split, or relative window when creating a /// - win: |window-ID| window to split, or relative window when creating a
/// float (relative="win"). /// float (relative="win").
/// - anchor: Decides which corner of the float to place at (row,col): /// - anchor: Decides which corner of the float to place at (row,col):
@ -699,7 +701,9 @@ Dict(win_config) nvim_win_get_config(Window window, Arena *arena, Error *err)
FUNC_API_SINCE(6) FUNC_API_SINCE(6)
{ {
/// Keep in sync with FloatRelative in buffer_defs.h /// Keep in sync with FloatRelative in buffer_defs.h
static const char *const float_relative_str[] = { "editor", "win", "cursor", "mouse" }; static const char *const float_relative_str[] = {
"editor", "win", "cursor", "mouse", "tabline", "laststatus"
};
/// Keep in sync with WinSplit in buffer_defs.h /// Keep in sync with WinSplit in buffer_defs.h
static const char *const win_split_str[] = { "left", "right", "above", "below" }; static const char *const win_split_str[] = { "left", "right", "above", "below" };
@ -805,6 +809,10 @@ static bool parse_float_relative(String relative, FloatRelative *out)
*out = kFloatRelativeCursor; *out = kFloatRelativeCursor;
} else if (striequal(str, "mouse")) { } else if (striequal(str, "mouse")) {
*out = kFloatRelativeMouse; *out = kFloatRelativeMouse;
} else if (striequal(str, "tabline")) {
*out = kFloatRelativeTabline;
} else if (striequal(str, "laststatus")) {
*out = kFloatRelativeLaststatus;
} else { } else {
return false; return false;
} }

View File

@ -900,6 +900,8 @@ typedef enum {
kFloatRelativeWindow = 1, kFloatRelativeWindow = 1,
kFloatRelativeCursor = 2, kFloatRelativeCursor = 2,
kFloatRelativeMouse = 3, kFloatRelativeMouse = 3,
kFloatRelativeTabline = 4,
kFloatRelativeLaststatus = 5,
} FloatRelative; } FloatRelative;
/// Keep in sync with win_split_str[] in nvim_win_get_config() (api/win_config.c) /// Keep in sync with win_split_str[] in nvim_win_get_config() (api/win_config.c)

View File

@ -835,6 +835,10 @@ void ui_ext_win_position(win_T *wp, bool validate)
col += tcol - 1; col += tcol - 1;
} }
} }
} else if (c.relative == kFloatRelativeLaststatus) {
row += Rows - (int)p_ch - last_stl_height(false);
} else if (c.relative == kFloatRelativeTabline) {
row += tabline_height();
} }
bool resort = wp->w_grid_alloc.comp_index != 0 bool resort = wp->w_grid_alloc.comp_index != 0
@ -1066,6 +1070,7 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir, frame_T *to_fl
return NULL; return NULL;
} }
need_status = STATUS_HEIGHT; need_status = STATUS_HEIGHT;
win_float_anchor_laststatus();
} }
bool do_equal = false; bool do_equal = false;
@ -6803,6 +6808,7 @@ void last_status(bool morewin)
{ {
// Don't make a difference between horizontal or vertical split. // Don't make a difference between horizontal or vertical split.
last_status_rec(topframe, last_stl_height(morewin) > 0, global_stl_height() > 0); last_status_rec(topframe, last_stl_height(morewin) > 0, global_stl_height() > 0);
win_float_anchor_laststatus();
} }
// Remove status line from window, replacing it with a horizontal separator if needed. // Remove status line from window, replacing it with a horizontal separator if needed.

View File

@ -307,6 +307,15 @@ void win_check_anchored_floats(win_T *win)
} }
} }
void win_float_anchor_laststatus(void)
{
FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
if (win->w_config.relative == kFloatRelativeLaststatus) {
win->w_pos_changed = true;
}
}
}
void win_reconfig_floats(void) void win_reconfig_floats(void)
{ {
for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) { for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) {

View File

@ -1012,6 +1012,97 @@ describe('float window', function()
end) end)
end) end)
it('placed relative to tabline and laststatus', function()
local screen = Screen.new(20, 10)
screen:add_extra_attr_ids({ [100] = { bold = true, foreground = Screen.colors.Magenta } })
command('set showtabline=1 laststatus=1')
api.nvim_open_win(0, false, {
relative = 'laststatus',
border = 'single',
anchor = 'SE',
width = 5,
height = 1,
row = 0,
col = 1000,
})
local tabwin = api.nvim_open_win(0, false, {
relative = 'tabline',
border = 'single',
width = 5,
height = 1,
row = 0,
col = 1000,
})
screen:expect([[
^ {2:}|
{1:~ }{2:}{4: }{2:}|
{1:~ }{2:}|
{1:~ }|*3
{1:~ }{2:}|
{1:~ }{2:}{4: }{2:}|
{1:~ }{2:}|
|
]])
command('tabnew | tabnext')
screen:expect([[
{5: }{100:3}{5: Name] }{24: No Name]X}|
^ {2:}|
{1:~ }{2:}{4: }{2:}|
{1:~ }{2:}|
{1:~ }|*2
{1:~ }{2:}|
{1:~ }{2:}{4: }{2:}|
{1:~ }{2:}|
|
]])
command('vsplit')
screen:expect([[
{5: }{100:4}{5: Name] }{24: No Name]X}|
^ {2:}|
{1:~ }{2:}{4: }{2:}|
{1:~ }{2:}|
{1:~ }{2:}{1:~}|
{1:~ }{2:}|
{1:~ }{2:}{4: }{2:}|
{1:~ }{2:}|
{3:[No Name] }{2:<}|
|
]])
command('quit')
api.nvim_win_set_config(tabwin, {
relative = 'tabline',
border = 'single',
width = 5,
height = 1,
row = 1,
col = 0,
})
screen:expect([[
{5: }{100:3}{5: Name] }{24: No Name]X}|
^ |
{2:}{1: }|
{2:}{4: }{2:}{1: }|
{2:}{1: }|
{1:~ }|
{1:~ }{2:}|
{1:~ }{2:}{4: }{2:}|
{1:~ }{2:}|
|
]])
command('tabonly')
screen:expect([[
^ |
{2:}{1: }|
{2:}{4: }{2:}{1: }|
{2:}{1: }|
{1:~ }|*2
{1:~ }{2:}|
{1:~ }{2:}{4: }{2:}|
{1:~ }{2:}|
|
]])
end)
local function with_ext_multigrid(multigrid) local function with_ext_multigrid(multigrid)
local screen, attrs local screen, attrs
before_each(function() before_each(function()