From 7589336120a258cf75134a5243b2f6b1926ac85b Mon Sep 17 00:00:00 2001 From: Daniel Steinberg Date: Mon, 15 Jan 2024 11:12:07 -0500 Subject: [PATCH] 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. --- runtime/doc/news.txt | 2 + runtime/doc/vim_diff.txt | 6 + runtime/lua/vim/_defaults.lua | 24 ++++ test/functional/core/startup_spec.lua | 19 ++- test/functional/terminal/tui_spec.lua | 192 ++++++++++++-------------- 5 files changed, 137 insertions(+), 106 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index cb70c81191..7743f5981a 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -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* diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 279fdd646f..46550f42b7 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -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|. diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 64eb638fd7..07850a5a47 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -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', diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index fd3748a985..f4a9c0c8d7 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -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 = [[ diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 54fbfd191a..46d3c98e07 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -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)