diff options
author | Gregory Anders <greg@gpanders.com> | 2025-01-14 08:18:59 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-01-14 08:18:59 -0600 |
commit | f1c45fc7a4a595e460cd245172a5767bddeb09e9 (patch) | |
tree | bf9dd2ffe6acea41c7639828fffef9a8393015fe | |
parent | 7eabc8899af8b2fed1472165b74f43965282974f (diff) | |
download | rneovim-f1c45fc7a4a595e460cd245172a5767bddeb09e9.tar.gz rneovim-f1c45fc7a4a595e460cd245172a5767bddeb09e9.tar.bz2 rneovim-f1c45fc7a4a595e460cd245172a5767bddeb09e9.zip |
feat(terminal): support theme update notifications (DEC mode 2031) (#31999)
-rw-r--r-- | runtime/doc/news.txt | 2 | ||||
-rw-r--r-- | src/nvim/optionstr.c | 10 | ||||
-rw-r--r-- | src/nvim/terminal.c | 29 | ||||
-rw-r--r-- | src/nvim/vterm/screen.c | 12 | ||||
-rw-r--r-- | src/nvim/vterm/state.c | 19 | ||||
-rw-r--r-- | src/nvim/vterm/vterm_defs.h | 3 | ||||
-rw-r--r-- | src/nvim/vterm/vterm_internal_defs.h | 1 | ||||
-rw-r--r-- | test/functional/terminal/tui_spec.lua | 26 | ||||
-rw-r--r-- | test/unit/fixtures/vterm_test.c | 2 |
9 files changed, 104 insertions, 0 deletions
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index e6a1adf15b..23266d536f 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -337,6 +337,8 @@ TERMINAL unfocused terminal window will have no cursor at all (so there is nothing to highlight). • |jobstart()| gained the "term" flag. +• The |terminal| will send theme update notifications when 'background' is + changed and DEC mode 2031 is enabled. TREESITTER diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index eac9ea02e0..93871905db 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -44,6 +44,7 @@ #include "nvim/spellfile.h" #include "nvim/spellsuggest.h" #include "nvim/strings.h" +#include "nvim/terminal.h" #include "nvim/types_defs.h" #include "nvim/vim_defs.h" #include "nvim/window.h" @@ -532,6 +533,15 @@ const char *did_set_background(optset_T *args) check_string_option(&p_bg); init_highlight(false, false); } + + // Notify all terminal buffers that the background color changed so they can + // send a theme update notification + FOR_ALL_BUFFERS(buf) { + if (buf->terminal) { + terminal_notify_theme(buf->terminal, dark); + } + } + return NULL; } diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index ad343bad67..2ad5ac49ca 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -179,6 +179,8 @@ struct terminal { StringBuilder *send; ///< When there is a pending TermRequest autocommand, block and store input. } pending; + bool theme_updates; ///< Send a theme update notification when 'bg' changes + bool color_set[16]; char *selection_buffer; /// libvterm selection buffer @@ -193,6 +195,7 @@ static VTermScreenCallbacks vterm_screen_callbacks = { .movecursor = term_movecursor, .settermprop = term_settermprop, .bell = term_bell, + .theme = term_theme, .sb_pushline = term_sb_push, // Called before a line goes offscreen. .sb_popline = term_sb_pop, }; @@ -1141,6 +1144,20 @@ bool terminal_running(const Terminal *term) return !term->closed; } +void terminal_notify_theme(Terminal *term, bool dark) + FUNC_ATTR_NONNULL_ALL +{ + if (!term->theme_updates) { + return; + } + + char buf[10]; + ssize_t ret = snprintf(buf, sizeof(buf), "\x1b[997;%cn", dark ? '1' : '2'); + assert(ret > 0); + assert((size_t)ret <= sizeof(buf)); + terminal_send(term, buf, (size_t)ret); +} + static void terminal_focus(const Terminal *term, bool focus) FUNC_ATTR_NONNULL_ALL { @@ -1259,6 +1276,10 @@ static int term_settermprop(VTermProp prop, VTermValue *val, void *data) invalidate_terminal(term, -1, -1); break; + case VTERM_PROP_THEMEUPDATES: + term->theme_updates = val->boolean; + break; + default: return 0; } @@ -1273,6 +1294,14 @@ static int term_bell(void *data) return 1; } +/// Called when the terminal wants to query the system theme. +static int term_theme(bool *dark, void *data) + FUNC_ATTR_NONNULL_ALL +{ + *dark = (*p_bg == 'd'); + return 1; +} + /// Scrollback push handler: called just before a line goes offscreen (and libvterm will forget it), /// giving us a chance to store it. /// diff --git a/src/nvim/vterm/screen.c b/src/nvim/vterm/screen.c index c91c6fb84f..45bd5e2a27 100644 --- a/src/nvim/vterm/screen.c +++ b/src/nvim/vterm/screen.c @@ -784,6 +784,17 @@ static int resize(int new_rows, int new_cols, VTermStateFields *fields, void *us return 1; } +static int theme(bool *dark, void *user) +{ + VTermScreen *screen = user; + + if (screen->callbacks && screen->callbacks->theme) { + return (*screen->callbacks->theme)(dark, screen->cbdata); + } + + return 1; +} + static int setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user) { @@ -838,6 +849,7 @@ static VTermStateCallbacks state_cbs = { .settermprop = &settermprop, .bell = &bell, .resize = &resize, + .theme = &theme, .setlineinfo = &setlineinfo, .sb_clear = &sb_clear, }; diff --git a/src/nvim/vterm/state.c b/src/nvim/vterm/state.c index 9e787acd9b..4ad07377de 100644 --- a/src/nvim/vterm/state.c +++ b/src/nvim/vterm/state.c @@ -819,6 +819,10 @@ static void set_dec_mode(VTermState *state, int num, int val) state->mode.bracketpaste = (unsigned)val; break; + case 2031: + settermprop_bool(state, VTERM_PROP_THEMEUPDATES, val); + break; + default: DEBUG_LOG("libvterm: Unknown DEC mode %d\n", num); return; @@ -894,6 +898,10 @@ static void request_dec_mode(VTermState *state, int num) reply = state->mode.bracketpaste; break; + case 2031: + reply = state->mode.theme_updates; + break; + default: vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, 0); return; @@ -1387,6 +1395,7 @@ static int on_csi(const char *leader, const long args[], int argcount, const cha { char *qmark = (leader_byte == '?') ? "?" : ""; + bool dark = false; switch (val) { case 0: @@ -1403,6 +1412,13 @@ static int on_csi(const char *leader, const long args[], int argcount, const cha vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s%d;%dR", qmark, state->pos.row + 1, state->pos.col + 1); break; + case 996: + if (state->callbacks && state->callbacks->theme) { + if (state->callbacks->theme(&dark, state->cbdata)) { + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?997;%cn", dark ? '1' : '2'); + } + } + break; } } break; @@ -2268,6 +2284,9 @@ int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val) case VTERM_PROP_FOCUSREPORT: state->mode.report_focus = (unsigned)val->boolean; return 1; + case VTERM_PROP_THEMEUPDATES: + state->mode.theme_updates = (unsigned)val->boolean; + return 1; case VTERM_N_PROPS: return 0; diff --git a/src/nvim/vterm/vterm_defs.h b/src/nvim/vterm/vterm_defs.h index d0a8ba8814..9aa933bef0 100644 --- a/src/nvim/vterm/vterm_defs.h +++ b/src/nvim/vterm/vterm_defs.h @@ -86,6 +86,7 @@ typedef enum { VTERM_PROP_CURSORSHAPE, // number VTERM_PROP_MOUSE, // number VTERM_PROP_FOCUSREPORT, // bool + VTERM_PROP_THEMEUPDATES, // bool VTERM_N_PROPS, } VTermProp; @@ -111,6 +112,7 @@ typedef struct { int (*settermprop)(VTermProp prop, VTermValue *val, void *user); int (*bell)(void *user); int (*resize)(int rows, int cols, void *user); + int (*theme)(bool *dark, void *user); int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user); int (*sb_popline)(int cols, VTermScreenCell *cells, void *user); int (*sb_clear)(void *user); @@ -263,6 +265,7 @@ typedef struct { int (*settermprop)(VTermProp prop, VTermValue *val, void *user); int (*bell)(void *user); int (*resize)(int rows, int cols, VTermStateFields *fields, void *user); + int (*theme)(bool *dark, void *user); int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user); int (*sb_clear)(void *user); diff --git a/src/nvim/vterm/vterm_internal_defs.h b/src/nvim/vterm/vterm_internal_defs.h index 770f862ce3..d4d59867bf 100644 --- a/src/nvim/vterm/vterm_internal_defs.h +++ b/src/nvim/vterm/vterm_internal_defs.h @@ -119,6 +119,7 @@ struct VTermState { unsigned leftrightmargin:1; unsigned bracketpaste:1; unsigned report_focus:1; + unsigned theme_updates:1; } mode; VTermEncodingInstance encoding[4], encoding_utf8; diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 3624a7bc2b..0a7dca8e6e 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -3309,6 +3309,32 @@ describe('TUI bg color', function() {3:-- TERMINAL --} | ]]) end) + + it('sends theme update notifications when background changes #31652', function() + command('set background=dark') -- set outer Nvim background + local child_server = new_pipename() + local screen = tt.setup_child_nvim({ + '--listen', + child_server, + '-u', + 'NONE', + '-i', + 'NONE', + '--cmd', + 'colorscheme vim', + '--cmd', + 'set noswapfile', + }) + screen:expect({ any = '%[No Name%]' }) + local child_session = n.connect(child_server) + retry(nil, nil, function() + eq({ true, 'dark' }, { child_session:request('nvim_eval', '&background') }) + end) + command('set background=light') -- set outer Nvim background + retry(nil, nil, function() + eq({ true, 'light' }, { child_session:request('nvim_eval', '&background') }) + end) + end) end) -- These tests require `tt` because --headless/--embed diff --git a/test/unit/fixtures/vterm_test.c b/test/unit/fixtures/vterm_test.c index 7522962a05..6744305960 100644 --- a/test/unit/fixtures/vterm_test.c +++ b/test/unit/fixtures/vterm_test.c @@ -345,6 +345,8 @@ static VTermValueType vterm_get_prop_type(VTermProp prop) return VTERM_VALUETYPE_INT; case VTERM_PROP_FOCUSREPORT: return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_THEMEUPDATES: + return VTERM_VALUETYPE_BOOL; case VTERM_N_PROPS: return 0; |