aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGregory Anders <greg@gpanders.com>2025-01-14 08:18:59 -0600
committerGitHub <noreply@github.com>2025-01-14 08:18:59 -0600
commitf1c45fc7a4a595e460cd245172a5767bddeb09e9 (patch)
treebf9dd2ffe6acea41c7639828fffef9a8393015fe
parent7eabc8899af8b2fed1472165b74f43965282974f (diff)
downloadrneovim-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.txt2
-rw-r--r--src/nvim/optionstr.c10
-rw-r--r--src/nvim/terminal.c29
-rw-r--r--src/nvim/vterm/screen.c12
-rw-r--r--src/nvim/vterm/state.c19
-rw-r--r--src/nvim/vterm/vterm_defs.h3
-rw-r--r--src/nvim/vterm/vterm_internal_defs.h1
-rw-r--r--test/functional/terminal/tui_spec.lua26
-rw-r--r--test/unit/fixtures/vterm_test.c2
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;