From c924c2a7b316afb982cd7786923446517c6fec6b Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Tue, 10 Mar 2026 10:06:20 +0000 Subject: fix(api): win_config_float_tp grid removal, redraw Problem: when nvim_win_set_config moves a floatwin between tabpages, its grid may remain if temporarily inside another tabpage. Also, things like tablines aren't redrawn. Solution: always remove its grid. Set must_redraw so things are redrawn even if w_redr_type was already set for the old tabpage. I don't think it's necessary to do anything extra here when removing the grid: - win_ui_flush calls ui_call_win_hide anyway, and calling it manually ends up sending two win_hide events. - ui_comp_remove_grid safely does nothing if the grid doesn't exist. - w_pos_changed is set by win_config_float later, if that's needed. I think the pending_comp_index_update set by ui_comp_remove_grid is enough anyway, at least for making sure win_ui_flush sends win_hide. Added test fails with the prior approach of checking `parent_tp != curtab`, but also `win_tp == curtab`. (which is a better, but still flawed alternative) The added redrawing here also supersedes setting w_hl_needs_update, and also redraws stuff like the tabline to pass the new test. --- test/functional/ui/float_spec.lua | 214 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) (limited to 'test/functional/ui/float_spec.lua') diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 15b7004f7e..0369e55a30 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -11869,6 +11869,220 @@ describe('float window', function() ]]) end end) + + it('redrawn after moving tabpages via nvim_win_set_config()', function() + local tab1_win = api.nvim_get_current_win() + fn.setline(1, 'hello') + command('tab split') + local tab2_win = api.nvim_get_current_win() + -- Schedule an UPD_NOT_VALID redraw, but in one event move the float out of curtab before it's + -- handled. Do not flush before then. + local float = exec_lua(function() + local float = vim.api.nvim_open_win(0, true, { relative = 'editor', width = 10, height = 5, row = 0, col = 0 }) + vim.api.nvim__redraw({ valid = false, flush = false }) + vim.api.nvim_win_set_config(float, { relative = 'win', win = tab1_win, row = 1, col = 1 }) + return float + end) + + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + {9: }{10:2}{9:+ [No Name] }{3: + [No Name] }{5: }{9:X}| + [4:----------------------------------------]|*5 + [3:----------------------------------------]| + ## grid 2 (hidden) + hello | + {0:~ }|*5 + ## grid 3 + | + ## grid 4 + ^hello | + {0:~ }|*4 + ]], + }) + else + screen:expect([[ + {9: }{10:2}{9:+ [No Name] }{3: + [No Name] }{5: }{9:X}| + ^hello | + {0:~ }|*4 + | + ]]) + end + + -- Importantly, want tabline redrawn and float's hl attribs to be correct here. + api.nvim_win_set_config(float, { relative = 'win', win = 0, row = 1, col = 1 }) + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + {9: + [No Name] }{3: }{11:2}{3:+ [No Name] }{5: }{9:X}| + [4:----------------------------------------]|*5 + [3:----------------------------------------]| + ## grid 2 (hidden) + hello | + {0:~ }|*5 + ## grid 3 + | + ## grid 4 + ^hello | + {0:~ }|*4 + ## grid 5 + {1:hello }| + {2:~ }|*4 + ]], + float_pos = { + [5] = { 1002, 'NW', 4, 1, 1, true, 50, 1, 1, 1 }, + }, + }) + else + screen:expect([[ + {9: + [No Name] }{3: }{11:2}{3:+ [No Name] }{5: }{9:X}| + ^h{1:hello } | + {0:~}{2:~ }{0: }|*4 + | + ]]) + end + + -- Autocommand runs within the first tabpage, but won't refresh grids when switching to it due + -- to switch_win_noblock having no_display set. + command( + ('autocmd OptionSet rightleft ++once call nvim_win_set_config(%d, #{relative: "win", win: %d, row: 0, col: 0})'):format( + float, + tab1_win + ) + ) + api.nvim_set_option_value('rightleft', true, { win = tab1_win }) + -- Stale grids should not have caused issues with removing the float's grid. + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + {9: }{10:2}{9:+ [No Name] }{3: + [No Name] }{5: }{9:X}| + [4:----------------------------------------]|*5 + [3:----------------------------------------]| + ## grid 2 (hidden) + hello | + {0:~ }|*5 + ## grid 3 + | + ## grid 4 + ^hello | + {0:~ }|*4 + ## grid 5 (hidden) + {1:hello }| + {2:~ }|*4 + ]], + }) + else + screen:expect([[ + {9: }{10:2}{9:+ [No Name] }{3: + [No Name] }{5: }{9:X}| + ^hello | + {0:~ }|*4 + | + ]]) + end + + command('tabfirst') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + {3: }{11:2}{3:+ [No Name] }{9: + [No Name] }{5: }{9:X}| + [2:----------------------------------------]|*5 + [3:----------------------------------------]| + ## grid 2 + olle^h| + {0: ~}|*4 + ## grid 3 + | + ## grid 4 (hidden) + hello | + {0:~ }|*4 + ## grid 5 + {1:hello }| + {2:~ }|*4 + ]], + float_pos = { + [5] = { 1002, 'NW', 2, 0, 0, true, 50, 1, 1, 0 }, + }, + }) + else + screen:expect([[ + {3: }{11:2}{3:+ [No Name] }{9: + [No Name] }{5: }{9:X}| + {1:hello } olle^h| + {2:~ }{0: ~}|*4 + | + ]]) + end + + -- Check tablines are redrawn even when moving floats between two non-current tabpages. + command('tabnew') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + {9: }{10:2}{9:+ No Name] }{3: [No Name] }{9: + [No Name] }{5: }{9:X}| + [6:----------------------------------------]|*5 + [3:----------------------------------------]| + ## grid 2 (hidden) + olleh| + {0: ~}|*4 + ## grid 3 + | + ## grid 4 (hidden) + hello | + {0:~ }|*4 + ## grid 5 (hidden) + {1:hello }| + {2:~ }|*4 + ## grid 6 + ^ | + {0: ~}|*4 + ]], + }) + else + screen:expect([[ + {9: }{10:2}{9:+ No Name] }{3: [No Name] }{9: + [No Name] }{5: }{9:X}| + ^ | + {0: ~}|*4 + | + ]]) + end + + api.nvim_win_set_config(float, { relative = 'win', win = tab2_win, row = 1, col = 1 }) + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + {9: + [No Name] }{3: [No Name] }{9: }{10:2}{9:+ No Name] }{5: }{9:X}| + [6:----------------------------------------]|*5 + [3:----------------------------------------]| + ## grid 2 (hidden) + olleh| + {0: ~}|*4 + ## grid 3 + | + ## grid 4 (hidden) + hello | + {0:~ }|*4 + ## grid 5 (hidden) + {1:hello }| + {2:~ }|*4 + ## grid 6 + ^ | + {0: ~}|*4 + ]], + }) + else + screen:expect([[ + {9: + [No Name] }{3: [No Name] }{9: }{10:2}{9:+ No Name] }{5: }{9:X}| + ^ | + {0: ~}|*4 + | + ]]) + end + end) end describe('with ext_multigrid and actual mouse grid', function() -- cgit From ef084b5c225c16b23e3e456e8d4d12138f415ea0 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Wed, 11 Mar 2026 09:46:28 +0000 Subject: fix(api): don't config split as floatwin relative to itself Problem: possible to configure a split as a floatwin with relative=win that is relative to itself. Solution: fix the check. Not caused by this PR; just something I noticed when about to fix the validation logic. --- test/functional/ui/float_spec.lua | 3 +++ 1 file changed, 3 insertions(+) (limited to 'test/functional/ui/float_spec.lua') diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 0369e55a30..1c7f309627 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -11191,6 +11191,9 @@ describe('float window', function() local winid = api.nvim_open_win(buf, false, config) api.nvim_set_current_win(winid) eq('floating window cannot be relative to itself', pcall_err(api.nvim_win_set_config, winid, config)) + -- Also when configuring split into float. + command('split') + eq('floating window cannot be relative to itself', pcall_err(api.nvim_win_set_config, 0, config)) end) it('bufpos out of range', function() -- cgit From 7be4ae796faa2066044b6cafe266ebe6354c55fe Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Tue, 10 Mar 2026 20:59:48 +0000 Subject: fix(api): relax config validation for "win" Problem: only possible to move floats between tabpages if relative=win, which has the restrictive effect of also anchoring it to the target window. Solution: allow "win" without "relative" or "split"/"vertical". Only assume missing "win" is 0 if relative=win is given to maintain that behaviour. (or when configuring a new window) Also add an error when attempting to change a split into a float that's in another tabpage, as this isn't actually supported yet. (until the next commit) Maybe this could do with some bikeshedding. Unclear if "win" should require "relative" to be given, like with "row"/"col"; this can be annoying though as specifying "relative" requires other fields to be given too. --- test/functional/ui/float_spec.lua | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) (limited to 'test/functional/ui/float_spec.lua') diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 1c7f309627..a3d4289520 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -3783,8 +3783,8 @@ describe('float window', function() local buf = api.nvim_create_buf(false, false) eq("Invalid key: 'bork'", pcall_err(api.nvim_open_win, buf, false, { width = 20, height = 2, bork = true })) eq( - "'win' key is only valid with relative='win' and relative=''", - pcall_err(api.nvim_open_win, buf, false, { width = 20, height = 2, relative = 'editor', row = 0, col = 0, win = 0 }) + "Must specify 'relative' or 'external' when creating a float", + pcall_err(api.nvim_open_win, buf, false, { win = 0 }) ) eq( "floating windows cannot have 'vertical'", @@ -11191,7 +11191,17 @@ describe('float window', function() local winid = api.nvim_open_win(buf, false, config) api.nvim_set_current_win(winid) eq('floating window cannot be relative to itself', pcall_err(api.nvim_win_set_config, winid, config)) - -- Also when configuring split into float. + eq('floating window cannot be relative to itself', pcall_err(api.nvim_win_set_config, winid, { win = winid })) + -- Don't assume win=0 if no win given for existing relative=win float; so no error. + api.nvim_win_set_config(winid, { width = 7 }) + eq(7, api.nvim_win_get_config(winid).width) + -- Don't expect the error when configuring to something other than relative=win, as win=self + -- is fine in those cases. (though maybe pointless) Other errors might be expected, though. + eq('Cannot split a floating window', pcall_err(api.nvim_win_set_config, winid, { split = 'above', win = winid })) + eq('win', api.nvim_win_get_config(winid).relative) + api.nvim_win_set_config(winid, { relative = 'editor', win = winid, row = 3, col = 3 }) + eq('editor', api.nvim_win_get_config(winid).relative) + -- An error when configuring split into relative=win float. command('split') eq('floating window cannot be relative to itself', pcall_err(api.nvim_win_set_config, 0, config)) end) @@ -11881,9 +11891,9 @@ describe('float window', function() -- Schedule an UPD_NOT_VALID redraw, but in one event move the float out of curtab before it's -- handled. Do not flush before then. local float = exec_lua(function() - local float = vim.api.nvim_open_win(0, true, { relative = 'editor', width = 10, height = 5, row = 0, col = 0 }) + local float = vim.api.nvim_open_win(0, true, { relative = 'editor', width = 10, height = 5, row = 1, col = 1 }) vim.api.nvim__redraw({ valid = false, flush = false }) - vim.api.nvim_win_set_config(float, { relative = 'win', win = tab1_win, row = 1, col = 1 }) + vim.api.nvim_win_set_config(float, { win = tab1_win }) return float end) @@ -11914,7 +11924,7 @@ describe('float window', function() end -- Importantly, want tabline redrawn and float's hl attribs to be correct here. - api.nvim_win_set_config(float, { relative = 'win', win = 0, row = 1, col = 1 }) + api.nvim_win_set_config(float, { win = 0 }) if multigrid then screen:expect({ grid = [[ @@ -11935,7 +11945,7 @@ describe('float window', function() {2:~ }|*4 ]], float_pos = { - [5] = { 1002, 'NW', 4, 1, 1, true, 50, 1, 1, 1 }, + [5] = { 1002, 'NW', 1, 1, 1, true, 50, 1, 1, 1 }, }, }) else @@ -12053,7 +12063,7 @@ describe('float window', function() ]]) end - api.nvim_win_set_config(float, { relative = 'win', win = tab2_win, row = 1, col = 1 }) + api.nvim_win_set_config(float, { win = tab2_win }) if multigrid then screen:expect({ grid = [[ -- cgit From 3325536150eda0de4dc69f25beca416c0a0b3e3a Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Thu, 12 Mar 2026 09:19:06 +0000 Subject: fix(winfloat): last_status when changing split to floatwin Problem: converting a split to a floatwin may not remove the last statusline when needed. (e.g: 'ls' is 1) Solution: call last_status/win_comp_pos in win_new_float, after win_remove. Also fix float_pos formatting for screen snapshots so it doesn't give a nil error for external windows. Not an issue from this PR. --- test/functional/ui/float_spec.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'test/functional/ui/float_spec.lua') diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index a3d4289520..9d3e83352c 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -7758,12 +7758,11 @@ describe('float window', function() screen:expect { grid = [[ ## grid 1 - [2:----------------------------------------]|*5 - {5:[No Name] [+] }| + [2:----------------------------------------]|*6 [3:----------------------------------------]| ## grid 2 x | - {0:~ }|*4 + {0:~ }|*5 ## grid 3 | ## grid 4 -- cgit From 094b297a3b33f6336a825818bfd6a53b25273ac8 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Wed, 11 Mar 2026 19:06:47 +0000 Subject: feat(api): nvim_win_set_config can move split to other tp as floatwin Problem: not possible for nvim_win_set_config to convert a split to a floatwin, then move it to another tabpage in one call. Solution: allow it. --- test/functional/ui/float_spec.lua | 270 +++++++++++++++++++++++++++++++++++++- 1 file changed, 266 insertions(+), 4 deletions(-) (limited to 'test/functional/ui/float_spec.lua') diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 9d3e83352c..e9f608099c 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -3782,10 +3782,7 @@ describe('float window', function() it('API has proper error messages', function() local buf = api.nvim_create_buf(false, false) eq("Invalid key: 'bork'", pcall_err(api.nvim_open_win, buf, false, { width = 20, height = 2, bork = true })) - eq( - "Must specify 'relative' or 'external' when creating a float", - pcall_err(api.nvim_open_win, buf, false, { win = 0 }) - ) + eq("Must specify 'relative' or 'external' when creating a float", pcall_err(api.nvim_open_win, buf, false, { win = 0 })) eq( "floating windows cannot have 'vertical'", pcall_err(api.nvim_open_win, buf, false, { width = 20, height = 2, relative = 'editor', row = 0, col = 0, vertical = true }) @@ -12030,6 +12027,7 @@ describe('float window', function() -- Check tablines are redrawn even when moving floats between two non-current tabpages. command('tabnew') + local tab3_win = api.nvim_get_current_win() if multigrid then screen:expect({ grid = [[ @@ -12094,6 +12092,270 @@ describe('float window', function() | ]]) end + + -- Try converting a split to a float, then moving it to another tabpage in one call. + command('set norightleft | new') + fn.setline(1, 'floaty mcfloatface') + api.nvim_win_set_config(0, { relative = 'editor', win = tab1_win, row = 3, col = 3, width = 15, height = 5 }) + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + {9: }{10:2}{9:+ No Name] }{3: [No Name] }{9: }{10:2}{9:+ No Name] }{5: }{9:X}| + [6:----------------------------------------]|*5 + [3:----------------------------------------]| + ## grid 2 (hidden) + olleh| + {0: ~}|*4 + ## grid 3 + | + ## grid 4 (hidden) + hello | + {0:~ }|*4 + ## grid 5 (hidden) + {1:hello }| + {2:~ }|*4 + ## grid 6 + ^ | + {0:~ }|*4 + ## grid 7 (hidden) + floaty mcfloatface | + {0:~ }| + ]], + }) + else + screen:expect([[ + {9: }{10:2}{9:+ No Name] }{3: [No Name] }{9: }{10:2}{9:+ No Name] }{5: }{9:X}| + ^ | + {0:~ }|*4 + | + ]]) + end + + command('tabfirst') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + {3: }{11:2}{3:+ No Name] }{9: [No Name] }{10:2}{9:+ No Name] }{5: }{9:X}| + [2:----------------------------------------]|*5 + [3:----------------------------------------]| + ## grid 2 + olle^h| + {0: ~}|*4 + ## grid 3 + | + ## grid 4 (hidden) + hello | + {0:~ }|*4 + ## grid 5 (hidden) + {1:hello }| + {2:~ }|*4 + ## grid 6 (hidden) + | + {0:~ }|*4 + ## grid 7 + {1:floaty mcfloatf}| + {1:ace }| + {2:~ }|*3 + ]], + float_pos = { + [7] = { 1004, 'NW', 1, 3, 3, true, 50, 1, 1, 3 }, + }, + }) + else + screen:expect([[ + {3: }{11:2}{3:+ No Name] }{9: [No Name] }{10:2}{9:+ No Name] }{5: }{9:X}| + {1:floaty mcfloatf} olle^h| + {0: }{1:ace }{0: ~}| + {0: }{2:~ }{0: ~}|*3 + | + ]]) + end + + -- Works when doing the same between two non-current tabpages. + local float2 = api.nvim_open_win(0, false, { split = 'below', win = tab3_win }) + api.nvim_win_set_config(float2, { relative = 'win', win = tab2_win, row = 2, col = 7, width = 4, height = 4, border = 'single' }) + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + {3: }{11:2}{3:+ No Name] }{9: [No Name] }{10:3}{9:+ No Name] }{5: }{9:X}| + [2:----------------------------------------]|*5 + [3:----------------------------------------]| + ## grid 2 + olle^h| + {0: ~}|*4 + ## grid 3 + | + ## grid 4 (hidden) + hello | + {0:~ }|*4 + ## grid 5 (hidden) + {1:hello }| + {2:~ }|*4 + ## grid 6 (hidden) + | + {0:~ }|*4 + ## grid 7 + {1:floaty mcfloatf}| + {1:ace }| + {2:~ }|*3 + ]], + float_pos = { + [7] = { 1004, 'NW', 1, 3, 3, true, 50, 1, 1, 3 }, + }, + }) + else + screen:expect([[ + {3: }{11:2}{3:+ No Name] }{9: [No Name] }{10:3}{9:+ No Name] }{5: }{9:X}| + {1:floaty mcfloatf} olle^h| + {0: }{1:ace }{0: ~}| + {0: }{2:~ }{0: ~}|*3 + | + ]]) + end + + command('tabnext') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + {9: }{10:2}{9:+ No Name] }{3: [No Name] }{9: }{10:3}{9:+ No Name] }{5: }{9:X}| + [6:----------------------------------------]|*5 + [3:----------------------------------------]| + ## grid 2 (hidden) + olleh| + {0: ~}|*4 + ## grid 3 + | + ## grid 4 (hidden) + hello | + {0:~ }|*4 + ## grid 5 (hidden) + {1:hello }| + {2:~ }|*4 + ## grid 6 + ^ | + {0:~ }|*4 + ## grid 7 (hidden) + {1:floaty mcfloatf}| + {1:ace }| + {2:~ }|*3 + ]], + }) + else + screen:expect([[ + {9: }{10:2}{9:+ No Name] }{3: [No Name] }{9: }{10:3}{9:+ No Name] }{5: }{9:X}| + ^ | + {0:~ }|*4 + | + ]]) + end + + command('tabnext') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + {9: }{10:2}{9:+ No Name] [No Name] }{3: }{11:3}{3:+ No Name] }{5: }{9:X}| + [4:----------------------------------------]|*5 + [3:----------------------------------------]| + ## grid 2 (hidden) + olleh| + {0: ~}|*4 + ## grid 3 + | + ## grid 4 + ^hello | + {0:~ }|*4 + ## grid 5 + {1:hello }| + {2:~ }|*4 + ## grid 6 (hidden) + | + {0:~ }|*4 + ## grid 7 (hidden) + {1:floaty mcfloatf}| + {1:ace }| + {2:~ }|*3 + ## grid 8 + {5:┌────┐}| + {5:│}{1:lleh}{5:│}| + {5:│}{1: o}{5:│}| + {5:│}{2: ~}{5:│}|*2 + {5:└────┘}| + ]], + float_pos = { + [5] = { 1002, 'NW', 4, 0, 0, true, 50, 1, 1, 0 }, + [8] = { 1005, 'NW', 4, 2, 7, true, 50, 2, 0, 7 }, + }, + }) + else + screen:expect([[ + {9: }{10:2}{9:+ No }{5:┌────┐}{9: [No Name] }{3: }{11:3}{3:+ No Name] }{5: }{9:X}| + {1:^hello }{5:│}{1:lleh}{5:│} | + {2:~ }{5:│}{1: o}{5:│}{0: }| + {2:~ }{5:│}{2: ~}{5:│}{0: }|*2 + {2:~ }{5:└────┘}{0: }| + | + ]]) + end + + -- Used relative=win on two floats relative to this window. + -- Split it to the right to ensure both follow. + command('topleft vsplit') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + {9: }{10:2}{9:+ No Name] [No Name] }{3: }{11:4}{3:+ No Name] }{5: }{9:X}| + [9:--------------------]{5:│}[4:-------------------]|*4 + {4:[No Name] [+] }{5:[No Name] [+] }| + [3:----------------------------------------]| + ## grid 2 (hidden) + olleh| + {0: ~}|*4 + ## grid 3 + | + ## grid 4 + hello | + {0:~ }|*3 + ## grid 5 + {1:hello }| + {2:~ }|*4 + ## grid 6 (hidden) + | + {0:~ }|*4 + ## grid 7 (hidden) + {1:floaty mcfloatf}| + {1:ace }| + {2:~ }|*3 + ## grid 8 + {5:┌────┐}| + {5:│}{1:lleh}{5:│}| + {5:│}{1: o}{5:│}| + {5:│}{2: ~}{5:│}|*2 + {5:└────┘}| + ## grid 9 + ^hello | + {0:~ }|*3 + ]], + float_pos = { + [5] = { 1002, 'NW', 4, 0, 0, true, 50, 1, 1, 21 }, + [8] = { 1005, 'NW', 4, 2, 7, true, 50, 2, 0, 28 }, + }, + }) + else + screen:expect([[ + {9: }{10:2}{9:+ No Name] [No Name] }{3: }{11:4}{3:+ }{5:┌────┐}{3:e] }{5: }{9:X}| + ^hello {5:│}{1:hello }{5:│}{1:lleh}{5:│} | + {0:~ }{5:│}{2:~ }{5:│}{1: o}{5:│}{0: }| + {0:~ }{5:│}{2:~ }{5:│}{2: ~}{5:│}{0: }|*2 + {4:[No Name] [+] }{2:~ }{5:└────┘ }| + | + ]]) + end end) end -- cgit From 3115e3d0d11e0e02ba9040ac1a7bb032afa762bb Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Fri, 13 Mar 2026 11:52:01 +0000 Subject: fix(api): improve external window validation Problem: "win" is allowed in external window configs in some cases. External window converted to normal float can't move tabpages in one nvim_win_set_config call. External window can't be turned into a normal split. Solution: disallow setting "win" for external windows. Allow external window to move tabpages, which turns it non-external. Allow external window to be turned into a (non-external) split. parse_win_config has more validation issues from not considering the window's existing config enough (not from this PR). For example, zindex can be set for an existing split if "split"/"vertical" isn't given, despite intending for that to be an error. Plus the logic is confusing. It could do with a refactor at some point... --- test/functional/ui/float_spec.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'test/functional/ui/float_spec.lua') diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index e9f608099c..c9453e2b32 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -3821,6 +3821,18 @@ describe('float window', function() ) eq("Must specify 'width'", pcall_err(api.nvim_open_win, buf, false, { relative = 'editor', row = 0, col = 0 })) eq("Must specify 'height'", pcall_err(api.nvim_open_win, buf, false, { relative = 'editor', row = 0, col = 0, width = 2 })) + + if multigrid then + eq( + "external window cannot have 'win'", + pcall_err(api.nvim_open_win, buf, false, { external = true, win = 0, width = 10, height = 10 }) + ) + api.nvim_open_win(buf, true, { external = true, width = 10, height = 10 }) + eq("external window cannot have 'win'", pcall_err(api.nvim_win_set_config, 0, { win = 0 })) + -- OK to include "win" if external window is also reconfigured to a normal float. + api.nvim_win_set_config(0, { relative = 'editor', win = 0, row = 0, col = 0, width = 5, height = 5 }) + eq('editor', api.nvim_win_get_config(0).relative) + end end) it('can be placed relative window or cursor', function() -- cgit