mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
feat(tui): update 'background' on theme change events (#31350)
Enabling private DEC mode 2031 tells the terminal to notify Nvim whenever the OS theme changes (i.e. light mode to dark mode or vice versa) or the terminal emulator's palette changes. When we receive one of these notifications we query the terminal color's background color again to see if it has changed and update the value of 'background' if it has. We only do this though if the user has not explicitly set the value of 'bg' themselves. The help text is updated slightly to hint to users that they probably shouldn't set this value: on modern terminal emulators Nvim is able to completely determine this automatically.
This commit is contained in:
parent
99b5ffd688
commit
d460928263
@ -284,6 +284,8 @@ TUI
|
||||
:lua =vim.api.nvim_get_chan_info(vim.api.nvim_list_uis()[1].chan)
|
||||
• |log| messages written by the builtin UI client (TUI, |--remote-ui|) are
|
||||
now prefixed with "ui" instead of "?".
|
||||
• The TUI will re-query the terminal's background color when a theme update
|
||||
notification is received and Nvim will update 'background' accordingly.
|
||||
|
||||
UI
|
||||
|
||||
|
@ -546,8 +546,9 @@ do
|
||||
---
|
||||
--- @param option string Option name
|
||||
--- @param value any Option value
|
||||
local function setoption(option, value)
|
||||
if vim.api.nvim_get_option_info2(option, {}).was_set then
|
||||
--- @param force boolean? Always set the value, even if already set
|
||||
local function setoption(option, value, force)
|
||||
if not force and vim.api.nvim_get_option_info2(option, {}).was_set then
|
||||
-- Don't do anything if option is already set
|
||||
return
|
||||
end
|
||||
@ -563,7 +564,7 @@ do
|
||||
once = true,
|
||||
nested = true,
|
||||
callback = function()
|
||||
setoption(option, value)
|
||||
setoption(option, value, force)
|
||||
end,
|
||||
})
|
||||
end
|
||||
@ -645,11 +646,15 @@ do
|
||||
return nil, nil, nil
|
||||
end
|
||||
|
||||
local timer = assert(vim.uv.new_timer())
|
||||
|
||||
-- This autocommand updates the value of 'background' anytime we receive
|
||||
-- an OSC 11 response from the terminal emulator. If the user has set
|
||||
-- 'background' explictly then we will delete this autocommand,
|
||||
-- effectively disabling automatic background setting.
|
||||
local force = false
|
||||
local id = vim.api.nvim_create_autocmd('TermResponse', {
|
||||
group = group,
|
||||
nested = true,
|
||||
desc = "Update the value of 'background' automatically based on the terminal emulator's background color",
|
||||
callback = function(args)
|
||||
local resp = args.data ---@type string
|
||||
local r, g, b = parseosc11(resp)
|
||||
@ -661,27 +666,33 @@ do
|
||||
if rr and gg and bb then
|
||||
local luminance = (0.299 * rr) + (0.587 * gg) + (0.114 * bb)
|
||||
local bg = luminance < 0.5 and 'dark' or 'light'
|
||||
setoption('background', bg)
|
||||
end
|
||||
setoption('background', bg, force)
|
||||
|
||||
return true
|
||||
-- On the first query response, don't force setting the option in
|
||||
-- case the user has already set it manually. If they have, then
|
||||
-- this autocommand will be deleted. If they haven't, then we do
|
||||
-- want to force setting the option to override the value set by
|
||||
-- this autocommand.
|
||||
if not force then
|
||||
force = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd('VimEnter', {
|
||||
group = group,
|
||||
nested = true,
|
||||
once = true,
|
||||
callback = function()
|
||||
if vim.api.nvim_get_option_info2('background', {}).was_set then
|
||||
vim.api.nvim_del_autocmd(id)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
io.stdout:write('\027]11;?\007')
|
||||
|
||||
timer:start(1000, 0, function()
|
||||
-- Delete the autocommand if no response was received
|
||||
vim.schedule(function()
|
||||
-- Suppress error if autocommand has already been deleted
|
||||
pcall(vim.api.nvim_del_autocmd, id)
|
||||
end)
|
||||
|
||||
if not timer:is_closing() then
|
||||
timer:close()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
--- If the TUI (term_has_truecolor) was able to determine that the host
|
||||
|
@ -160,12 +160,16 @@ void tinput_init(TermInput *input, Loop *loop)
|
||||
// initialize a timer handle for handling ESC with libtermkey
|
||||
uv_timer_init(&loop->uv, &input->timer_handle);
|
||||
input->timer_handle.data = input;
|
||||
|
||||
uv_timer_init(&loop->uv, &input->bg_query_timer);
|
||||
input->bg_query_timer.data = input;
|
||||
}
|
||||
|
||||
void tinput_destroy(TermInput *input)
|
||||
{
|
||||
map_destroy(int, &kitty_key_map);
|
||||
uv_close((uv_handle_t *)&input->timer_handle, NULL);
|
||||
uv_close((uv_handle_t *)&input->bg_query_timer, NULL);
|
||||
rstream_may_close(&input->read_stream);
|
||||
termkey_destroy(input->tk);
|
||||
}
|
||||
@ -179,6 +183,7 @@ void tinput_stop(TermInput *input)
|
||||
{
|
||||
rstream_stop(&input->read_stream);
|
||||
uv_timer_stop(&input->timer_handle);
|
||||
uv_timer_stop(&input->bg_query_timer);
|
||||
}
|
||||
|
||||
static void tinput_done_event(void **argv)
|
||||
@ -474,6 +479,13 @@ static void tinput_timer_cb(uv_timer_t *handle)
|
||||
tinput_flush(input);
|
||||
}
|
||||
|
||||
static void bg_query_timer_cb(uv_timer_t *handle)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
TermInput *input = handle->data;
|
||||
tui_query_bg_color(input->tui_data);
|
||||
}
|
||||
|
||||
/// Handle focus events.
|
||||
///
|
||||
/// If the upcoming sequence of bytes in the input stream matches the termcode
|
||||
@ -660,6 +672,33 @@ static void handle_unknown_csi(TermInput *input, const TermKeyKey *key)
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'n':
|
||||
// Device Status Report (DSR)
|
||||
if (nparams == 2) {
|
||||
int args[2];
|
||||
for (size_t i = 0; i < ARRAY_SIZE(args); i++) {
|
||||
if (termkey_interpret_csi_param(params[i], &args[i], NULL, NULL) != TERMKEY_RES_KEY) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (args[0] == 997) {
|
||||
// Theme update notification
|
||||
// https://github.com/contour-terminal/contour/blob/master/docs/vt-extensions/color-palette-update-notifications.md
|
||||
// The second argument tells us whether the OS theme is set to light
|
||||
// mode or dark mode, but all we care about is the background color of
|
||||
// the terminal emulator. We query for that with OSC 11 and the response
|
||||
// is handled by the autocommand created in _defaults.lua. The terminal
|
||||
// may send us multiple notifications all at once so we use a timer to
|
||||
// coalesce the queries.
|
||||
if (uv_timer_get_due_in(&input->bg_query_timer) > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
uv_timer_start(&input->bg_query_timer, bg_query_timer_cb, 100, 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ typedef struct {
|
||||
TermKey *tk;
|
||||
TermKey_Terminfo_Getstr_Hook *tk_ti_hook_fn; ///< libtermkey terminfo hook
|
||||
uv_timer_t timer_handle;
|
||||
uv_timer_t bg_query_timer; ///< timer used to batch background color queries
|
||||
Loop *loop;
|
||||
RStream read_stream;
|
||||
TUIData *tui_data;
|
||||
|
@ -241,16 +241,19 @@ void tui_handle_term_mode(TUIData *tui, TermMode mode, TermModeState state)
|
||||
tui->unibi_ext.sync = (int)unibi_add_ext_str(tui->ut, "Sync",
|
||||
"\x1b[?2026%?%p1%{1}%-%tl%eh%;");
|
||||
break;
|
||||
case kTermModeResizeEvents:
|
||||
signal_watcher_stop(&tui->winch_handle);
|
||||
tui_set_term_mode(tui, mode, true);
|
||||
break;
|
||||
case kTermModeGraphemeClusters:
|
||||
if (!is_set) {
|
||||
tui_set_term_mode(tui, mode, true);
|
||||
tui->did_set_grapheme_cluster_mode = true;
|
||||
}
|
||||
break;
|
||||
case kTermModeThemeUpdates:
|
||||
tui_set_term_mode(tui, mode, true);
|
||||
break;
|
||||
case kTermModeResizeEvents:
|
||||
signal_watcher_stop(&tui->winch_handle);
|
||||
tui_set_term_mode(tui, mode, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -320,6 +323,18 @@ static void tui_reset_key_encoding(TUIData *tui)
|
||||
}
|
||||
}
|
||||
|
||||
/// Write the OSC 11 sequence to the terminal emulator to query the current
|
||||
/// background color.
|
||||
///
|
||||
/// The response will be handled by the TermResponse autocommand created in
|
||||
/// _defaults.lua.
|
||||
void tui_query_bg_color(TUIData *tui)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
out(tui, S_LEN("\x1b]11;?\x07"));
|
||||
flush_buf(tui);
|
||||
}
|
||||
|
||||
/// Enable the alternate screen and emit other control sequences to start the TUI.
|
||||
///
|
||||
/// This is also called when the TUI is resumed after being suspended. We reinitialize all state
|
||||
@ -438,14 +453,13 @@ static void terminfo_start(TUIData *tui)
|
||||
// Enable bracketed paste
|
||||
unibi_out_ext(tui, tui->unibi_ext.enable_bracketed_paste);
|
||||
|
||||
// Query support for mode 2026 (Synchronized Output). Some terminals also
|
||||
// support an older DCS sequence for synchronized output, but we will only use
|
||||
// mode 2026.
|
||||
// Query support for private DEC modes that Nvim can take advantage of.
|
||||
// Some terminals (such as Terminal.app) do not support DECRQM, so skip the query.
|
||||
if (!nsterm) {
|
||||
tui_request_term_mode(tui, kTermModeSynchronizedOutput);
|
||||
tui_request_term_mode(tui, kTermModeResizeEvents);
|
||||
tui_request_term_mode(tui, kTermModeGraphemeClusters);
|
||||
tui_request_term_mode(tui, kTermModeThemeUpdates);
|
||||
tui_request_term_mode(tui, kTermModeResizeEvents);
|
||||
}
|
||||
|
||||
// Don't use DECRQSS in screen or tmux, as they behave strangely when receiving it.
|
||||
@ -493,6 +507,10 @@ static void terminfo_start(TUIData *tui)
|
||||
/// Disable the alternate screen and prepare for the TUI to close.
|
||||
static void terminfo_stop(TUIData *tui)
|
||||
{
|
||||
// Disable theme update notifications. We do this first to avoid getting any
|
||||
// more notifications after we reset the cursor and any color palette changes.
|
||||
tui_set_term_mode(tui, kTermModeThemeUpdates, false);
|
||||
|
||||
// Destroy output stuff
|
||||
tui_mode_change(tui, NULL_STRING, SHAPE_IDX_N);
|
||||
tui_mouse_off(tui);
|
||||
@ -509,6 +527,7 @@ static void terminfo_stop(TUIData *tui)
|
||||
if (tui->did_set_grapheme_cluster_mode) {
|
||||
tui_set_term_mode(tui, kTermModeGraphemeClusters, false);
|
||||
}
|
||||
|
||||
// May restore old title before exiting alternate screen.
|
||||
tui_set_title(tui, NULL_STRING);
|
||||
if (ui_client_exit_status == 0) {
|
||||
|
@ -5,6 +5,7 @@ typedef struct TUIData TUIData;
|
||||
typedef enum {
|
||||
kTermModeSynchronizedOutput = 2026,
|
||||
kTermModeGraphemeClusters = 2027,
|
||||
kTermModeThemeUpdates = 2031,
|
||||
kTermModeResizeEvents = 2048,
|
||||
} TermMode;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user