From 4ee9e58056a9d17ce921d8cc6dfd6d3305a40f69 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 28 Mar 2024 07:39:36 +0800 Subject: [PATCH] feat(tui): query extended underline support using DECRQSS (#28052) --- runtime/lua/vim/_defaults.lua | 7 +++--- src/nvim/tui/input.c | 7 ++++++ src/nvim/tui/tui.c | 36 +++++++++++++++++++++------ test/functional/terminal/tui_spec.lua | 29 +++++++++++++++++++++ 4 files changed, 69 insertions(+), 10 deletions(-) diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 91baee1a1e..6223082622 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -384,13 +384,13 @@ if tty then -- attributes, so there should be no attributes in the list. local attrs = vim.split(decrqss, ';') if #attrs ~= 1 and (#attrs ~= 2 or attrs[1] ~= '0') then - return true + return false end -- The returned SGR sequence should begin with 48:2 local sgr = attrs[#attrs]:match('^48:2:([%d:]+)$') if not sgr then - return true + return false end -- The remaining elements of the SGR sequence should be the 3 colors @@ -422,7 +422,8 @@ if tty then if os.getenv('TMUX') then decrqss = string.format('\027Ptmux;%s\027\\', decrqss:gsub('\027', '\027\027')) end - io.stdout:write(string.format('\027[48;2;%d;%d;%dm%s', r, g, b, decrqss)) + -- Reset attributes first, as other code may have set attributes. + io.stdout:write(string.format('\027[0m\027[48;2;%d;%d;%dm%s', r, g, b, decrqss)) timer:start(1000, 0, function() -- Delete the autocommand if no response was received diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 840e472a1c..f1594dfcb9 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -553,6 +553,13 @@ static void handle_term_response(TermInput *input, const TermKeyKey *key) if (termkey_interpret_string(input->tk, key, &str) == TERMKEY_RES_KEY) { assert(str != NULL); + // Handle DECRQSS SGR response for the query from tui_query_extended_underline(). + // Some terminals include "0" in the attribute list unconditionally; others don't. + if (key->type == TERMKEY_TYPE_DCS + && (strnequal(str, S_LEN("1$r4:3m")) || strnequal(str, S_LEN("1$r0;4:3m")))) { + tui_enable_extended_underline(input->tui_data); + } + // Send an event to nvim core. This will update the v:termresponse variable // and fire the TermResponse event MAXSIZE_TEMP_ARRAY(args, 2); diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 96054cf5cb..aaa2369e29 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -225,6 +225,26 @@ void tui_handle_term_mode(TUIData *tui, TermMode mode, TermModeState state) } } +/// Query the terminal emulator to see if it supports extended underline. +static void tui_query_extended_underline(TUIData *tui) +{ + // Try to set an undercurl using an SGR sequence, followed by a DECRQSS SGR query. + // Reset attributes first, as other code may have set attributes. + out(tui, S_LEN("\x1b[0m\x1b[4:3m\x1bP$qm\x1b\\")); + tui->print_attr_id = -1; +} + +void tui_enable_extended_underline(TUIData *tui) +{ + if (tui->unibi_ext.set_underline_style == -1) { + tui->unibi_ext.set_underline_style = (int)unibi_add_ext_str(tui->ut, "ext.set_underline_style", + "\x1b[4:%p1%dm"); + } + // Only support colon syntax. #9270 + tui->unibi_ext.set_underline_color = (int)unibi_add_ext_str(tui->ut, "ext.set_underline_color", + "\x1b[58:2::%p1%d:%p2%d:%p3%dm"); +} + /// Query the terminal emulator to see if it supports Kitty's keyboard protocol. /// /// Write CSI ? u followed by a primary device attributes request (CSI c). If @@ -306,6 +326,7 @@ static void terminfo_start(TUIData *tui) tui->unibi_ext.reset_scroll_region = -1; tui->unibi_ext.set_cursor_style = -1; tui->unibi_ext.reset_cursor_style = -1; + tui->unibi_ext.set_underline_style = -1; tui->unibi_ext.set_underline_color = -1; tui->unibi_ext.sync = -1; tui->out_fd = STDOUT_FILENO; @@ -390,6 +411,11 @@ static void terminfo_start(TUIData *tui) // mode 2026 tui_request_term_mode(tui, kTermModeSynchronizedOutput); + if (tui->unibi_ext.set_underline_style == -1) { + // Query the terminal to see if it supports extended underline. + tui_query_extended_underline(tui); + } + // Query the terminal to see if it supports Kitty's keyboard protocol tui_query_kitty_keyboard(tui); @@ -2331,14 +2357,10 @@ static void augment_terminfo(TUIData *tui, const char *term, int vte_version, in if (vte_version >= 5102 || konsolev >= 221170 || (ext_bool_Su != -1 && unibi_get_ext_bool(ut, (size_t)ext_bool_Su)) || (weztermv != NULL && strcmp(weztermv, "20210203-095643") > 0)) { - tui->unibi_ext.set_underline_style = (int)unibi_add_ext_str(ut, "ext.set_underline_style", - "\x1b[4:%p1%dm"); + tui_enable_extended_underline(tui); } - } - if (tui->unibi_ext.set_underline_style != -1) { - // Only support colon syntax. #9270 - tui->unibi_ext.set_underline_color = (int)unibi_add_ext_str(ut, "ext.set_underline_color", - "\x1b[58:2::%p1%d:%p2%d:%p3%dm"); + } else { + tui_enable_extended_underline(tui); } if (!kitty && (vte_version == 0 || vte_version >= 5400)) { diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 0300672a08..10d9099431 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -1588,6 +1588,35 @@ describe('TUI', function() } end) + -- Note: libvterm doesn't support colored underline or undercurl. + it('supports undercurl and underdouble when run in :terminal', function() + screen:set_default_attr_ids({ + [1] = { reverse = true }, + [2] = { bold = true, reverse = true }, + [3] = { bold = true }, + [4] = { foreground = 12 }, + [5] = { undercurl = true }, + [6] = { underdouble = true }, + }) + child_session:request('nvim_set_hl', 0, 'Visual', { undercurl = true }) + feed_data('ifoobar\027V') + screen:expect([[ + {5:fooba}{1:r} | + {4:~ }|*3 + {2:[No Name] [+] }| + {3:-- VISUAL LINE --} | + {3:-- TERMINAL --} | + ]]) + child_session:request('nvim_set_hl', 0, 'Visual', { underdouble = true }) + screen:expect([[ + {6:fooba}{1:r} | + {4:~ }|*3 + {2:[No Name] [+] }| + {3:-- VISUAL LINE --} | + {3:-- TERMINAL --} | + ]]) + end) + it('in nvim_list_uis()', function() -- $TERM in :terminal. local exp_term = is_os('bsd') and 'builtin_xterm' or 'xterm-256color'