feat(terminal): respond to OSC background and foreground request (#17197)

The motivation for this update is Issue #15365, where background=light
is not properly set for Nvim running from an Nvim :terminal. This can be
encountered when e.g., opening a terminal to make git commits, which
opens EDITOR=nvim in the nested terminal.

Under the implementation of this commit, the OSC response always
indicates a black or white foreground/background. While this may not
reflect the actual foreground/background color, it permits 'background'
to be retained for a nested Nvim instance running in the terminal
emulator. The behaviour matches Vim.
This commit is contained in:
Daniel Steinberg 2024-01-15 11:12:07 -05:00 committed by GitHub
parent 9c202b9392
commit 7589336120
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 137 additions and 106 deletions

View File

@ -287,6 +287,8 @@ The following new APIs and features were added.
• Terminal buffers emit a |TermRequest| autocommand event when the child
process emits an OSC or DCS control sequence.
• Terminal buffers respond to OSC background and foreground requests. |default-autocmds|
==============================================================================
CHANGED FEATURES *news-changed*

View File

@ -145,6 +145,12 @@ nvim_terminal:
- BufReadCmd: Treats "term://" buffers as |terminal| buffers. |terminal-start|
- TermClose: A |terminal| buffer started with no arguments (which thus uses
'shell') and which exits with no error is closed automatically.
- TermRequest: The terminal emulator responds to OSC background and foreground
requests, indicating (1) a black background and white foreground when Nvim
option 'background' is "dark" or (2) a white background and black foreground
when 'background' is "light". While this may not reflect the actual
foreground/background color, it permits 'background' to be retained for a
nested Nvim instance running in the terminal emulator.
nvim_cmdwin:
- CmdwinEnter: Limits syntax sync to maxlines=1 in the |cmdwin|.

View File

@ -155,6 +155,30 @@ do
end,
})
vim.api.nvim_create_autocmd('TermRequest', {
group = nvim_terminal_augroup,
desc = 'Respond to OSC foreground/background color requests',
callback = function(args)
local fg_request = args.data == '\027]10;?'
local bg_request = args.data == '\027]11;?'
if fg_request or bg_request then
-- WARN: This does not return the actual foreground/background color,
-- but rather returns:
-- - fg=white/bg=black when Nvim option 'background' is 'dark'
-- - fg=black/bg=white when Nvim option 'background' is 'light'
local red, green, blue = 0, 0, 0
local bg_option_dark = vim.o.background == 'dark'
if (fg_request and bg_option_dark) or (bg_request and not bg_option_dark) then
red, green, blue = 65535, 65535, 65535
end
local command = fg_request and 10 or 11
local data = string.format('\027]%d;rgb:%04x/%04x/%04x\007', command, red, green, blue)
local channel = vim.bo[args.buf].channel
vim.api.nvim_chan_send(channel, data)
end
end,
})
vim.api.nvim_create_autocmd('CmdwinEnter', {
pattern = '[:>]',
desc = 'Limit syntax sync to maxlines=1 in the command window',

View File

@ -1369,12 +1369,19 @@ describe('inccommand on ex mode', function()
local screen
screen = Screen.new(60, 10)
screen:attach()
local id = fn.termopen(
{ nvim_prog, '-u', 'NONE', '-c', 'set termguicolors', '-E', 'test/README.md' },
{
env = { VIMRUNTIME = os.getenv('VIMRUNTIME') },
}
)
local id = fn.termopen({
nvim_prog,
'-u',
'NONE',
'-i',
'NONE',
'-c',
'set termguicolors background=dark',
'-E',
'test/README.md',
}, {
env = { VIMRUNTIME = os.getenv('VIMRUNTIME') },
})
fn.chansend(id, '%s/N')
screen:expect {
grid = [[

View File

@ -8,6 +8,7 @@ local helpers = require('test.functional.helpers')(after_each)
local thelpers = require('test.functional.terminal.helpers')
local Screen = require('test.functional.ui.screen')
local eq = helpers.eq
local feed_command = helpers.feed_command
local feed_data = thelpers.feed_data
local clear = helpers.clear
local command = helpers.command
@ -2126,7 +2127,7 @@ describe('TUI FocusGained/FocusLost', function()
'--cmd',
'colorscheme vim',
'--cmd',
'set noswapfile noshowcmd noruler notermguicolors',
'set noswapfile noshowcmd noruler notermguicolors background=dark',
})
screen:expect([[
@ -2776,10 +2777,73 @@ describe('TUI', function()
end)
describe('TUI bg color', function()
local screen
before_each(clear)
local function setup_bg_test()
clear()
local attr_ids = {
[1] = { reverse = true },
[2] = { bold = true },
[3] = { reverse = true, bold = true },
[4] = { foreground = tonumber('0x00000a') },
}
it('is properly set in a nested Nvim instance when background=dark', function()
command('highlight clear Normal')
command('set background=dark') -- set outer Nvim background
local screen = thelpers.setup_child_nvim({
'-u',
'NONE',
'-i',
'NONE',
'--cmd',
'colorscheme vim',
'--cmd',
'set noswapfile',
})
screen:set_default_attr_ids(attr_ids)
retry(nil, 30000, function() -- wait for automatic background processing
screen:sleep(20)
feed_command('set background?') -- check nested Nvim background
screen:expect([[
{1: } |
{2:~} |
{2:~} |
{2:~} |
{3:[No Name] 0,0-1 All}|
background=dark |
{4:-- TERMINAL --} |
]])
end)
end)
it('is properly set in a nested Nvim instance when background=light', function()
command('highlight clear Normal')
command('set background=light') -- set outer Nvim background
local screen = thelpers.setup_child_nvim({
'-u',
'NONE',
'-i',
'NONE',
'--cmd',
'colorscheme vim',
'--cmd',
'set noswapfile',
})
retry(nil, 30000, function() -- wait for automatic background processing
screen:sleep(20)
feed_command('set background?') -- check nested Nvim background
screen:expect([[
{1: } |
{3:~} |
{3:~} |
{3:~} |
{5:[No Name] 0,0-1 All}|
background=light |
{3:-- TERMINAL --} |
]])
end)
end)
it('queries the terminal for background color', function()
exec_lua([[
vim.api.nvim_create_autocmd('TermRequest', {
callback = function(args)
@ -2791,8 +2855,7 @@ describe('TUI bg color', function()
end,
})
]])
screen = thelpers.setup_child_nvim({
thelpers.setup_child_nvim({
'-u',
'NONE',
'-i',
@ -2800,109 +2863,38 @@ describe('TUI bg color', function()
'--cmd',
'colorscheme vim',
'--cmd',
'set noswapfile notermguicolors',
'-c',
'autocmd OptionSet background echo "did OptionSet, yay!"',
'set noswapfile',
})
end
before_each(setup_bg_test)
it('queries the terminal for background color', function()
retry(nil, 1000, function()
eq(true, eval("get(g:, 'oscrequest', v:false)"))
end)
end)
it('triggers OptionSet event on unsplit terminal-response', function()
screen:expect([[
it('triggers OptionSet from automatic background processing', function()
local screen = thelpers.setup_child_nvim({
'-u',
'NONE',
'-i',
'NONE',
'--cmd',
'colorscheme vim',
'--cmd',
'set noswapfile',
'-c',
'autocmd OptionSet background echo "did OptionSet, yay!"',
})
retry(nil, 30000, function() -- wait for automatic background processing
screen:sleep(20)
screen:expect([[
{1: } |
{4:~ }|*3
{3:~} |
{3:~} |
{3:~} |
{5:[No Name] 0,0-1 All}|
|
did OptionSet, yay! |
{3:-- TERMINAL --} |
]])
feed_data('\027]11;rgb:ffff/ffff/ffff\027\\')
screen:expect { any = 'did OptionSet, yay!' }
feed_data(':echo "new_bg=".&background\n')
screen:expect { any = 'new_bg=light' }
setup_bg_test()
screen:expect([[
{1: } |
{4:~ }|*3
{5:[No Name] 0,0-1 All}|
|
{3:-- TERMINAL --} |
]])
feed_data('\027]11;rgba:ffff/ffff/ffff/8000\027\\')
screen:expect { any = 'did OptionSet, yay!' }
feed_data(':echo "new_bg=".&background\n')
screen:expect { any = 'new_bg=light' }
end)
it('triggers OptionSet event with split terminal-response', function()
screen:expect([[
{1: } |
{4:~ }|*3
{5:[No Name] 0,0-1 All}|
|
{3:-- TERMINAL --} |
]])
-- Send a background response with the OSC command part split.
feed_data('\027]11;rgb')
feed_data(':ffff/ffff/ffff\027\\')
screen:expect { any = 'did OptionSet, yay!' }
feed_data(':echo "new_bg=".&background\n')
screen:expect { any = 'new_bg=light' }
setup_bg_test()
screen:expect([[
{1: } |
{4:~ }|*3
{5:[No Name] 0,0-1 All}|
|
{3:-- TERMINAL --} |
]])
-- Send a background response with the Pt portion split.
feed_data('\027]11;rgba:ffff/fff')
feed_data('f/ffff/8000\027\\')
screen:expect { any = 'did OptionSet, yay!' }
feed_data(':echo "new_bg=".&background\n')
screen:expect { any = 'new_bg=light' }
end)
it('not triggers OptionSet event with invalid terminal-response', function()
screen:expect([[
{1: } |
{4:~ }|*3
{5:[No Name] 0,0-1 All}|
|
{3:-- TERMINAL --} |
]])
feed_data('\027]11;rgb:ffff/ffff/ffff/8000\027\\')
screen:expect_unchanged()
feed_data(':echo "new_bg=".&background\n')
screen:expect { any = 'new_bg=dark' }
setup_bg_test()
screen:expect([[
{1: } |
{4:~ }|*3
{5:[No Name] 0,0-1 All}|
|
{3:-- TERMINAL --} |
]])
feed_data('\027]11;rgba:ffff/foo/ffff/8000\027\\')
screen:expect_unchanged()
feed_data(':echo "new_bg=".&background\n')
screen:expect { any = 'new_bg=dark' }
]])
end)
end)
end)