From 2613ba5000d4c0d9b15e2eec2d2b97615575925e Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Tue, 5 Dec 2023 10:01:32 -0800 Subject: feat(defaults): enable 'termguicolors' by default when supported by terminal Enable 'termguicolors' automatically when Nvim can detect that truecolor is supported by the host terminal. If $COLORTERM is set to "truecolor" or "24bit", or the terminal's terminfo entry contains capabilities for Tc, RGB, or setrgbf and setrgbb, then we assume that the terminal supports truecolor. Otherwise, the terminal is queried (using both XTGETTCAP and SGR + DECRQSS). If the terminal's response to these queries (if any) indicates that it supports truecolor, then 'termguicolors' is enabled. --- runtime/doc/news.txt | 3 + runtime/doc/options.txt | 4 + runtime/doc/vim_diff.txt | 2 + runtime/lua/vim/_defaults.lua | 255 +++++++++++++++++++++++++++----------- runtime/lua/vim/_meta.lua | 1 + runtime/lua/vim/_meta/options.lua | 4 + runtime/lua/vim/termcap.lua | 52 ++++++-- runtime/plugin/osc52.lua | 8 +- src/nvim/options.lua | 4 + src/nvim/tui/tui.c | 47 ++++++- src/nvim/ui_client.c | 9 +- 11 files changed, 296 insertions(+), 93 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index e38707fa76..3006287e62 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -98,6 +98,9 @@ The following changes may require adaptations in user config or plugins. • Default color scheme has been updated to be "Neovim branded" and accessible. Use `:colorscheme vim` to revert to the old legacy color scheme. +• 'termguicolors' is enabled by default when Nvim is able to determine that + the host terminal emulator supports 24-bit color. + ============================================================================== BREAKING CHANGES IN HEAD *news-breaking-dev* diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index f47093782c..355c8cc99a 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -6495,6 +6495,10 @@ A jump table for the options with a short description can be found at |Q_op|. attributes instead of "cterm" attributes. |guifg| Requires an ISO-8613-3 compatible terminal. + Nvim will automatically attempt to determine if the host terminal + supports 24-bit color and will enable this option if it does + (unless explicitly disabled by the user). + *'termpastefilter'* *'tpf'* 'termpastefilter' 'tpf' string (default "BS,HT,ESC,DEL") global diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index f6dfe3b14a..220505f573 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -75,6 +75,8 @@ Defaults *nvim-defaults* - 'switchbuf' defaults to "uselast" - 'tabpagemax' defaults to 50 - 'tags' defaults to "./tags;,tags" +- 'termguicolors' is enabled by default if Nvim can detect support from the + host terminal - 'ttimeoutlen' defaults to 50 - 'ttyfast' is always set - 'undodir' defaults to ~/.local/state/nvim/undo// (|xdg|), auto-created diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index c3bb36fc36..b73681be04 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -165,91 +165,92 @@ do }) end ---- Guess value of 'background' based on terminal color. ---- ---- We write Operating System Command (OSC) 11 to the terminal to request the ---- terminal's background color. We then wait for a response. If the response ---- matches `rgba:RRRR/GGGG/BBBB/AAAA` where R, G, B, and A are hex digits, then ---- compute the luminance[1] of the RGB color and classify it as light/dark ---- accordingly. Note that the color components may have anywhere from one to ---- four hex digits, and require scaling accordingly as values out of 4, 8, 12, ---- or 16 bits. Also note the A(lpha) component is optional, and is parsed but ---- ignored in the calculations. ---- ---- [1] https://en.wikipedia.org/wiki/Luma_%28video%29 -do - --- Parse a string of hex characters as a color. - --- - --- The string can contain 1 to 4 hex characters. The returned value is - --- between 0.0 and 1.0 (inclusive) representing the intensity of the color. - --- - --- For instance, if only a single hex char "a" is used, then this function - --- returns 0.625 (10 / 16), while a value of "aa" would return 0.664 (170 / - --- 256). - --- - --- @param c string Color as a string of hex chars - --- @return number? Intensity of the color - local function parsecolor(c) - if #c == 0 or #c > 4 then - return nil - end - - local val = tonumber(c, 16) - if not val then - return nil - end - - local max = tonumber(string.rep('f', #c), 16) - return val / max +-- Only do the following when the TUI is attached +local tty = nil +for _, ui in ipairs(vim.api.nvim_list_uis()) do + if ui.chan == 1 and ui.stdout_tty then + tty = ui + break end +end - --- Parse an OSC 11 response - --- - --- Either of the two formats below are accepted: - --- - --- OSC 11 ; rgb:// - --- - --- or - --- - --- OSC 11 ; rgba:/// +if tty then + --- Guess value of 'background' based on terminal color. --- - --- where + --- We write Operating System Command (OSC) 11 to the terminal to request the + --- terminal's background color. We then wait for a response. If the response + --- matches `rgba:RRRR/GGGG/BBBB/AAAA` where R, G, B, and A are hex digits, then + --- compute the luminance[1] of the RGB color and classify it as light/dark + --- accordingly. Note that the color components may have anywhere from one to + --- four hex digits, and require scaling accordingly as values out of 4, 8, 12, + --- or 16 bits. Also note the A(lpha) component is optional, and is parsed but + --- ignored in the calculations. --- - --- , , , := h | hh | hhh | hhhh - --- - --- The alpha component is ignored, if present. - --- - --- @param resp string OSC 11 response - --- @return string? Red component - --- @return string? Green component - --- @return string? Blue component - local function parseosc11(resp) - local r, g, b - r, g, b = resp:match('^\027%]11;rgb:(%x+)/(%x+)/(%x+)$') - if not r and not g and not b then - local a - r, g, b, a = resp:match('^\027%]11;rgba:(%x+)/(%x+)/(%x+)/(%x+)$') - if not a or #a > 4 then - return nil, nil, nil + --- [1] https://en.wikipedia.org/wiki/Luma_%28video%29 + do + --- Parse a string of hex characters as a color. + --- + --- The string can contain 1 to 4 hex characters. The returned value is + --- between 0.0 and 1.0 (inclusive) representing the intensity of the color. + --- + --- For instance, if only a single hex char "a" is used, then this function + --- returns 0.625 (10 / 16), while a value of "aa" would return 0.664 (170 / + --- 256). + --- + --- @param c string Color as a string of hex chars + --- @return number? Intensity of the color + local function parsecolor(c) + if #c == 0 or #c > 4 then + return nil end - end - if r and g and b and #r <= 4 and #g <= 4 and #b <= 4 then - return r, g, b + local val = tonumber(c, 16) + if not val then + return nil + end + + local max = tonumber(string.rep('f', #c), 16) + return val / max end - return nil, nil, nil - end + --- Parse an OSC 11 response + --- + --- Either of the two formats below are accepted: + --- + --- OSC 11 ; rgb:// + --- + --- or + --- + --- OSC 11 ; rgba:/// + --- + --- where + --- + --- , , , := h | hh | hhh | hhhh + --- + --- The alpha component is ignored, if present. + --- + --- @param resp string OSC 11 response + --- @return string? Red component + --- @return string? Green component + --- @return string? Blue component + local function parseosc11(resp) + local r, g, b + r, g, b = resp:match('^\027%]11;rgb:(%x+)/(%x+)/(%x+)$') + if not r and not g and not b then + local a + r, g, b, a = resp:match('^\027%]11;rgba:(%x+)/(%x+)/(%x+)/(%x+)$') + if not a or #a > 4 then + return nil, nil, nil + end + end + + if r and g and b and #r <= 4 and #g <= 4 and #b <= 4 then + return r, g, b + end - local tty = false - for _, ui in ipairs(vim.api.nvim_list_uis()) do - if ui.chan == 1 and ui.stdout_tty then - tty = true - break + return nil, nil, nil end - end - if tty then local timer = assert(vim.uv.new_timer()) ---@param bg string New value of the 'background' option @@ -300,7 +301,7 @@ do io.stdout:write('\027]11;?\007') timer:start(1000, 0, function() - -- No response received. Delete the autocommand + -- 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) @@ -311,4 +312,108 @@ do end end) end + + --- If the TUI (term_has_truecolor) was able to determine that the host + --- terminal supports truecolor, enable 'termguicolors'. Otherwise, query the + --- terminal (using both XTGETTCAP and SGR + DECRQSS). If the terminal's + --- response indicates that it does support truecolor enable 'termguicolors', + --- but only if the user has not already disabled it. + do + if tty.rgb then + -- The TUI was able to determine truecolor support + vim.o.termguicolors = true + else + --- Enable 'termguicolors', but only if it was not already set by the user. + local function settgc() + if not vim.api.nvim_get_option_info2('termguicolors', {}).was_set then + vim.o.termguicolors = true + end + end + + local caps = {} ---@type table + require('vim.termcap').query({ 'Tc', 'RGB', 'setrgbf', 'setrgbb' }, function(cap, found) + if not found then + return + end + + caps[cap] = true + if caps.Tc or caps.RGB or (caps.setrgbf and caps.setrgbb) then + settgc() + end + end) + + local timer = assert(vim.uv.new_timer()) + + -- Arbitrary colors to set in the SGR sequence + local r = 1 + local g = 2 + local b = 3 + + local id = vim.api.nvim_create_autocmd('TermResponse', { + nested = true, + callback = function(args) + local resp = args.data ---@type string + local decrqss = resp:match('^\027P1%$r([%d;:]+)m$') + + if decrqss then + -- The DECRQSS SGR response first contains attributes separated by + -- semicolons, followed by the SGR itself with parameters separated + -- by colons. Some terminals include "0" in the attribute list + -- unconditionally; others do not. Our SGR sequence did not set any + -- 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 + end + + -- The returned SGR sequence should begin with 48:2 + local sgr = attrs[#attrs]:match('^48:2:([%d:]+)$') + if not sgr then + return true + end + + -- The remaining elements of the SGR sequence should be the 3 colors + -- we set. Some terminals also include an additional parameter + -- (which can even be empty!), so handle those cases as well + local params = vim.split(sgr, ':') + if #params ~= 3 and (#params ~= 4 or (params[1] ~= '' and params[1] ~= '1')) then + return true + end + + if + tonumber(params[#params - 2]) == r + and tonumber(params[#params - 1]) == g + and tonumber(params[#params]) == b + then + settgc() + end + + return true + end + end, + }) + + -- Write SGR followed by DECRQSS. This sets the background color then + -- immediately asks the terminal what the background color is. If the + -- terminal responds to the DECRQSS with the same SGR sequence that we + -- sent then the terminal supports truecolor. + local decrqss = '\027P$qm\027\\' + 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)) + + 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 + end end diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index e3b99f6b3d..bb9ed722e2 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -20,6 +20,7 @@ vim.lsp = require('vim.lsp') vim.re = require('vim.re') vim.secure = require('vim.secure') vim.snippet = require('vim.snippet') +vim.text = require('vim.text') vim.treesitter = require('vim.treesitter') vim.ui = require('vim.ui') vim.version = require('vim.version') diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index d2bdab4d28..5e65ca6b1b 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -6941,6 +6941,10 @@ vim.go.tbidi = vim.go.termbidi --- attributes instead of "cterm" attributes. `guifg` --- Requires an ISO-8613-3 compatible terminal. --- +--- Nvim will automatically attempt to determine if the host terminal +--- supports 24-bit color and will enable this option if it does +--- (unless explicitly disabled by the user). +--- --- @type boolean vim.o.termguicolors = false vim.o.tgc = vim.o.termguicolors diff --git a/runtime/lua/vim/termcap.lua b/runtime/lua/vim/termcap.lua index 862cc52149..b88d9ac9ba 100644 --- a/runtime/lua/vim/termcap.lua +++ b/runtime/lua/vim/termcap.lua @@ -12,7 +12,10 @@ local M = {} --- emulator supports the XTGETTCAP sequence. --- --- @param caps string|table A terminal capability or list of capabilities to query ---- @param cb function(cap:string, seq:string) Function to call when a response is received +--- @param cb function(cap:string, found:bool, seq:string?) Callback function which is called for +--- each capability in {caps}. {found} is set to true if the capability was found or false +--- otherwise. {seq} is the control sequence for the capability if found, or nil for +--- boolean capabilities. function M.query(caps, cb) vim.validate({ caps = { caps, { 'string', 'table' } }, @@ -23,21 +26,33 @@ function M.query(caps, cb) caps = { caps } end - local count = #caps + local pending = {} ---@type table + for _, v in ipairs(caps) do + pending[v] = true + end + + local timer = assert(vim.uv.new_timer()) - vim.api.nvim_create_autocmd('TermResponse', { + local id = vim.api.nvim_create_autocmd('TermResponse', { callback = function(args) local resp = args.data ---@type string - local k, v = resp:match('^\027P1%+r(%x+)=(%x+)$') - if k and v then + local k, rest = resp:match('^\027P1%+r(%x+)(.*)$') + if k and rest then local cap = vim.text.hexdecode(k) - local seq = - vim.text.hexdecode(v):gsub('\\E', '\027'):gsub('%%p%d', ''):gsub('\\(%d+)', string.char) + local seq ---@type string? + if rest:match('^=%x+$') then + seq = vim.text + .hexdecode(rest:sub(2)) + :gsub('\\E', '\027') + :gsub('%%p%d', '') + :gsub('\\(%d+)', string.char) + end + + cb(cap, true, seq) - cb(cap, seq) + pending[cap] = nil - count = count - 1 - if count == 0 then + if next(pending) == nil then return true end end @@ -57,6 +72,23 @@ function M.query(caps, cb) end io.stdout:write(query) + + 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) + + -- Call the callback for all capabilities that were not found + for k in pairs(pending) do + cb(k, false, nil) + end + end) + + if not timer:is_closing() then + timer:close() + end + end) end return M diff --git a/runtime/plugin/osc52.lua b/runtime/plugin/osc52.lua index 374b70066f..7a90518966 100644 --- a/runtime/plugin/osc52.lua +++ b/runtime/plugin/osc52.lua @@ -6,7 +6,11 @@ if not tty or vim.g.clipboard ~= nil or vim.o.clipboard ~= '' or not os.getenv(' return end -require('vim.termcap').query('Ms', function(cap, seq) +require('vim.termcap').query('Ms', function(cap, found, seq) + if not found then + return + end + assert(cap == 'Ms') -- Check 'clipboard' and g:clipboard again to avoid a race condition @@ -16,7 +20,7 @@ require('vim.termcap').query('Ms', function(cap, seq) -- If the terminal reports a sequence other than OSC 52 for the Ms capability -- then ignore it. We only support OSC 52 (for now) - if not seq:match('^\027%]52') then + if not seq or not seq:match('^\027%]52') then return end diff --git a/src/nvim/options.lua b/src/nvim/options.lua index daaf73d241..50371b8bf3 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -8764,6 +8764,10 @@ return { Enables 24-bit RGB color in the |TUI|. Uses "gui" |:highlight| attributes instead of "cterm" attributes. |guifg| Requires an ISO-8613-3 compatible terminal. + + Nvim will automatically attempt to determine if the host terminal + supports 24-bit color and will enable this option if it does + (unless explicitly disabled by the user). ]=], full_name = 'termguicolors', redraw = { 'ui_option' }, diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index c71eb633e9..d625c22c76 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -148,7 +148,8 @@ static bool cursor_style_enabled = false; # include "tui/tui.c.generated.h" #endif -void tui_start(TUIData **tui_p, int *width, int *height, char **term) +void tui_start(TUIData **tui_p, int *width, int *height, char **term, bool *rgb) + FUNC_ATTR_NONNULL_ALL { TUIData *tui = xcalloc(1, sizeof(TUIData)); tui->is_starting = true; @@ -177,6 +178,7 @@ void tui_start(TUIData **tui_p, int *width, int *height, char **term) *width = tui->width; *height = tui->height; *term = tui->term; + *rgb = tui->rgb; } void tui_set_key_encoding(TUIData *tui) @@ -334,6 +336,9 @@ static void terminfo_start(TUIData *tui) int konsolev = konsolev_env ? (int)strtol(konsolev_env, NULL, 10) : (konsole ? 1 : 0); + // truecolor support must be checked before patching/augmenting terminfo + tui->rgb = term_has_truecolor(tui, colorterm); + patch_terminfo_bugs(tui, term, colorterm, vtev, konsolev, iterm_env, nsterm); augment_terminfo(tui, term, vtev, konsolev, iterm_env, nsterm); tui->can_change_scroll_region = @@ -1439,7 +1444,7 @@ void tui_suspend(TUIData *tui) tui_mouse_on(tui); } stream_set_blocking(tui->input.in_fd, false); // libuv expects this - ui_client_attach(tui->width, tui->height, tui->term); + ui_client_attach(tui->width, tui->height, tui->term, tui->rgb); #endif } @@ -1752,6 +1757,44 @@ static int unibi_find_ext_bool(unibi_term *ut, const char *name) return -1; } +/// Determine if the terminal supports truecolor or not: +/// +/// 1. If $COLORTERM is "24bit" or "truecolor", return true +/// 2. Else, check terminfo for Tc, RGB, setrgbf, or setrgbb capabilities. If +/// found, return true +/// 3. Else, return false +static bool term_has_truecolor(TUIData *tui, const char *colorterm) +{ + // Check $COLORTERM + if (strequal(colorterm, "truecolor") || strequal(colorterm, "24bit")) { + return true; + } + + // Check for Tc and RGB + for (size_t i = 0; i < unibi_count_ext_bool(tui->ut); i++) { + const char *n = unibi_get_ext_bool_name(tui->ut, i); + if (n && (!strcmp(n, "Tc") || !strcmp(n, "RGB"))) { + return true; + } + } + + // Check for setrgbf and setrgbb + bool setrgbf = false; + bool setrgbb = false; + for (size_t i = 0; i < unibi_count_ext_str(tui->ut) && (!setrgbf || !setrgbb); i++) { + const char *n = unibi_get_ext_str_name(tui->ut, i); + if (n) { + if (!setrgbf && !strcmp(n, "setrgbf")) { + setrgbf = true; + } else if (!setrgbb && !strcmp(n, "setrgbb")) { + setrgbb = true; + } + } + } + + return setrgbf && setrgbb; +} + /// Patches the terminfo records after loading from system or built-in db. /// Several entries in terminfo are known to be deficient or outright wrong; /// and several terminal emulators falsely announce incorrect terminal types. diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c index eb32c16881..d744560a86 100644 --- a/src/nvim/ui_client.c +++ b/src/nvim/ui_client.c @@ -70,14 +70,14 @@ uint64_t ui_client_start_server(int argc, char **argv) return channel->id; } -void ui_client_attach(int width, int height, char *term) +void ui_client_attach(int width, int height, char *term, bool rgb) { MAXSIZE_TEMP_ARRAY(args, 3); ADD_C(args, INTEGER_OBJ(width)); ADD_C(args, INTEGER_OBJ(height)); MAXSIZE_TEMP_DICT(opts, 9); - PUT_C(opts, "rgb", BOOLEAN_OBJ(true)); + PUT_C(opts, "rgb", BOOLEAN_OBJ(rgb)); PUT_C(opts, "ext_linegrid", BOOLEAN_OBJ(true)); PUT_C(opts, "ext_termcolors", BOOLEAN_OBJ(true)); if (term) { @@ -111,9 +111,10 @@ void ui_client_run(bool remote_ui) ui_client_is_remote = remote_ui; int width, height; char *term; - tui_start(&tui, &width, &height, &term); + bool rgb; + tui_start(&tui, &width, &height, &term, &rgb); - ui_client_attach(width, height, term); + ui_client_attach(width, height, term, rgb); // os_exit() will be invoked when the client channel detaches while (true) { -- cgit From a5a346678a8211ea07f318de42e557ad3909f65e Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Tue, 5 Dec 2023 14:26:46 -0800 Subject: test: set notermguicolors in tests Set 'notermguicolors' in tests which spawn a child Nvim process to force existing tests to use 16 colors. Also refactor the child process invocation to make things a little bit less messy. --- test/functional/autocmd/focus_spec.lua | 11 +- test/functional/core/main_spec.lua | 2 +- test/functional/helpers.lua | 3 +- test/functional/terminal/api_spec.lua | 8 +- test/functional/terminal/buffer_spec.lua | 12 +- test/functional/terminal/cursor_spec.lua | 29 +++- test/functional/terminal/helpers.lua | 24 +++- test/functional/terminal/tui_spec.lua | 225 +++++++++++++++++-------------- test/functional/ui/output_spec.lua | 8 +- 9 files changed, 198 insertions(+), 124 deletions(-) diff --git a/test/functional/autocmd/focus_spec.lua b/test/functional/autocmd/focus_spec.lua index c72842f14b..204bfcaa12 100644 --- a/test/functional/autocmd/focus_spec.lua +++ b/test/functional/autocmd/focus_spec.lua @@ -2,7 +2,6 @@ local helpers = require('test.functional.helpers')(after_each) local thelpers = require('test.functional.terminal.helpers') local luv = require('luv') local clear = helpers.clear -local nvim_prog = helpers.nvim_prog local feed_command = helpers.feed_command local feed_data = thelpers.feed_data @@ -14,10 +13,12 @@ describe('autoread TUI FocusGained/FocusLost', function() before_each(function() clear() - screen = thelpers.screen_setup( - 0, - '["'..nvim_prog..'", "-u", "NONE", "-i", "NONE", "--cmd", "colorscheme vim", "--cmd", "set noswapfile noshowcmd noruler"]' - ) + screen = thelpers.setup_child_nvim({ + '-u', 'NONE', + '-i', 'NONE', + '--cmd', 'colorscheme vim', + '--cmd', 'set noswapfile noshowcmd noruler notermguicolors', + }) end) teardown(function() diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua index d705d56575..c8d800f89a 100644 --- a/test/functional/core/main_spec.lua +++ b/test/functional/core/main_spec.lua @@ -62,7 +62,7 @@ describe('command-line option', function() screen:attach() local args = { nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE', - '--cmd', '"set noswapfile shortmess+=IFW fileformats=unix"', + '--cmd', '"set noswapfile shortmess+=IFW fileformats=unix notermguicolors"', '-s', '-' } diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 2bff1d16f8..93eadb64ca 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -30,7 +30,8 @@ module.nvim_prog = ( module.nvim_set = ( 'set shortmess+=IS background=light noswapfile noautoindent startofline' ..' laststatus=1 undodir=. directory=. viewdir=. backupdir=.' - ..' belloff= wildoptions-=pum joinspaces noshowcmd noruler nomore redrawdebug=invalid') + ..' belloff= wildoptions-=pum joinspaces noshowcmd noruler nomore redrawdebug=invalid' + ..' notermguicolors') module.nvim_argv = { module.nvim_prog, '-u', 'NONE', '-i', 'NONE', -- XXX: find treesitter parsers. diff --git a/test/functional/terminal/api_spec.lua b/test/functional/terminal/api_spec.lua index c278b2ad0e..68082ba4fa 100644 --- a/test/functional/terminal/api_spec.lua +++ b/test/functional/terminal/api_spec.lua @@ -11,8 +11,12 @@ describe('api', function() before_each(function() helpers.clear() os.remove(socket_name) - screen = child_session.screen_setup(0, '["'..helpers.nvim_prog - ..'", "-u", "NONE", "-i", "NONE", "--cmd", "colorscheme vim", "--cmd", "'..helpers.nvim_set..'"]') + screen = child_session.setup_child_nvim({ + '-u', 'NONE', + '-i', 'NONE', + '--cmd', 'colorscheme vim', + '--cmd', helpers.nvim_set, + }) end) after_each(function() os.remove(socket_name) diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index 423e7bdf21..b92f1b1592 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -17,7 +17,6 @@ local sleep = helpers.sleep local funcs = helpers.funcs local is_os = helpers.is_os local skip = helpers.skip -local nvim_prog = helpers.nvim_prog describe(':terminal buffer', function() local screen @@ -446,10 +445,13 @@ end) describe('terminal input', function() it('sends various special keys with modifiers', function() clear() - local screen = thelpers.screen_setup( - 0, - string.format([=[["%s", "-u", "NONE", "-i", "NONE", "--cmd", 'colorscheme vim', "--cmd", "startinsert"]]=], nvim_prog) - ) + local screen = thelpers.setup_child_nvim({ + '-u', 'NONE', + '-i', 'NONE', + '--cmd', 'colorscheme vim', + '--cmd', 'set notermguicolors', + '--cmd', 'startinsert', + }) screen:expect{grid=[[ {1: } | {4:~ }| diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua index 95081b7189..37bb0ee817 100644 --- a/test/functional/terminal/cursor_spec.lua +++ b/test/functional/terminal/cursor_spec.lua @@ -3,7 +3,6 @@ local Screen = require('test.functional.ui.screen') local thelpers = require('test.functional.terminal.helpers') local feed, clear, nvim = helpers.feed, helpers.clear, helpers.nvim local testprg, command = helpers.testprg, helpers.command -local nvim_prog = helpers.nvim_prog local eq, eval = helpers.eq, helpers.eval local matches = helpers.matches local poke_eventloop = helpers.poke_eventloop @@ -184,10 +183,18 @@ describe('buffer cursor position is correct in terminal without number column', local screen local function setup_ex_register(str) - screen = thelpers.screen_setup(0, '["'..nvim_prog - ..[[", "-u", "NONE", "-i", "NONE", "-E", "--cmd", "let @r = ']]..str..[['", ]] + screen = thelpers.setup_child_nvim({ + '-u', 'NONE', + '-i', 'NONE', + '-E', + '--cmd', string.format('let @r = "%s"', str), -- and don't always work - ..[["--cmd", "cnoremap ", "--cmd", "cnoremap "]]..']', 70) + '--cmd', 'cnoremap ', + '--cmd', 'cnoremap ', + '--cmd', 'set notermguicolors', + }, { + cols = 70, + }) screen:set_default_attr_ids({ [1] = {foreground = 253, background = 11}; [3] = {bold = true}, @@ -570,10 +577,18 @@ describe('buffer cursor position is correct in terminal with number column', fun local screen local function setup_ex_register(str) - screen = thelpers.screen_setup(0, '["'..nvim_prog - ..[[", "-u", "NONE", "-i", "NONE", "-E", "--cmd", "let @r = ']]..str..[['", ]] + screen = thelpers.setup_child_nvim({ + '-u', 'NONE', + '-i', 'NONE', + '-E', + '--cmd', string.format('let @r = "%s"', str), -- and don't always work - ..[["--cmd", "cnoremap ", "--cmd", "cnoremap "]]..']', 70) + '--cmd', 'cnoremap ', + '--cmd', 'cnoremap ', + '--cmd', 'set notermguicolors', + }, { + cols = 70, + }) screen:set_default_attr_ids({ [1] = {foreground = 253, background = 11}; [3] = {bold = true}, diff --git a/test/functional/terminal/helpers.lua b/test/functional/terminal/helpers.lua index 62d3dd67a3..4ae054daa8 100644 --- a/test/functional/terminal/helpers.lua +++ b/test/functional/terminal/helpers.lua @@ -6,6 +6,7 @@ local Screen = require('test.functional.ui.screen') local testprg = helpers.testprg local exec_lua = helpers.exec_lua local nvim = helpers.nvim +local nvim_prog = helpers.nvim_prog local function feed_data(data) if type(data) == 'table' then @@ -122,6 +123,26 @@ local function screen_setup(extra_rows, command, cols, opts) return screen end +local function setup_child_nvim(args, opts) + opts = opts or {} + + local argv = { nvim_prog, unpack(args) } + local cmd = string.format('[%s]', vim.iter(argv):map(function(s) + return string.format('\'%s\'', s) + end):join(', ')) + + if opts.env then + local s = {} + for k, v in pairs(opts.env) do + table.insert(s, string.format('%s: \'%s\'', k, v)) + end + + cmd = string.format('%s, #{env: #{%s}}', cmd, table.concat(s, ', ')) + end + + return screen_setup(0, cmd, opts.cols) +end + return { feed_data = feed_data, feed_termcode = feed_termcode, @@ -141,5 +162,6 @@ return { clear_attrs = clear_attrs, enable_mouse = enable_mouse, disable_mouse = disable_mouse, - screen_setup = screen_setup + screen_setup = screen_setup, + setup_child_nvim = setup_child_nvim, } diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 3748f820d4..0c984069b3 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -39,13 +39,13 @@ describe('TUI', function() before_each(function() clear() local child_server = new_pipename() - screen = thelpers.screen_setup(0, - string.format( - [=[["%s", "--listen", "%s", "-u", "NONE", "-i", "NONE", "--cmd", "%s laststatus=2 background=dark", "--cmd", "colorscheme vim"]]=], - nvim_prog, - child_server, - nvim_set - )) + screen = thelpers.setup_child_nvim({ + '--listen', child_server, + '-u', 'NONE', + '-i', 'NONE', + '--cmd', string.format('%s laststatus=2 background=dark', nvim_set), + '--cmd', 'colorscheme vim' + }) screen:expect([[ {1: } | {4:~ }| @@ -1830,7 +1830,7 @@ describe('TUI', function() }) screen:attach() exec([[ - call termopen([v:progpath, '--clean', '--cmd', 'colorscheme vim', '--cmd', 'let start = reltime() | while v:true | if reltimefloat(reltime(start)) > 2 | break | endif | endwhile']) + call termopen([v:progpath, '--clean', '--cmd', 'set notermguicolors', '--cmd', 'colorscheme vim', '--cmd', 'let start = reltime() | while v:true | if reltimefloat(reltime(start)) > 2 | break | endif | endwhile']) sleep 500m vs new ]]) @@ -1856,30 +1856,29 @@ describe('TUI', function() write_file(script_file, [=[ local ffi = require('ffi') ffi.cdef([[int execl(const char *, const char *, ...);]]) - ffi.C.execl(vim.v.progpath, 'Xargv0nvim', '--clean', '--cmd', 'colorscheme vim') + ffi.C.execl(vim.v.progpath, 'Xargv0nvim', '--clean') ]=]) finally(function() os.remove(script_file) end) - local screen = thelpers.screen_setup(0, string.format([=[["%s", "--clean", "-l", "%s"]]=], - nvim_prog, script_file)) + local screen = thelpers.setup_child_nvim({'--clean', '-l', script_file}) screen:expect{grid=[[ {1: } | - {4:~ }| - {4:~ }| - {4:~ }| - {5:[No Name] 0,0-1 All}| + ~ | + ~ | + ~ | + [No Name] 0,0-1 All| | {3:-- TERMINAL --} | ]]} feed_data(':put =v:argv + [v:progname]\n') screen:expect{grid=[[ + Xargv0nvim | + --embed | --clean | - --cmd | - colorscheme vim | {1:X}argv0nvim | - {5:[No Name] [+] 7,1 Bot}| - 6 more lines | + [No Name] [+] 5,1 Bot| + 4 more lines | {3:-- TERMINAL --} | ]]} end) @@ -1903,11 +1902,13 @@ describe('TUI', function() end) it(' #10134', function() - local screen = thelpers.screen_setup( - 0, - '["'..nvim_prog..[[", "-u", "NONE", "-i", "NONE", "--cmd", "colorscheme vim", ]] - ..[["--cmd", "set noruler", "--cmd", ':nnoremap :echomsg "\"']]..']' - ) + local screen = thelpers.setup_child_nvim({ + '-u', 'NONE', + '-i', 'NONE', + '--cmd', 'colorscheme vim', + '--cmd', 'set noruler notermguicolors', + '--cmd', ':nnoremap :echomsg "\\"', + }) screen:expect{grid=[[ {1: } | {4:~ }| @@ -1931,12 +1932,13 @@ describe('TUI', function() end) it('draws line with many trailing spaces correctly #24955', function() - local screen = thelpers.screen_setup( - 0, - '["'..nvim_prog..[[", "-u", "NONE", "-i", "NONE", "--cmd", "colorscheme vim"]] - ..[[, "--cmd", "call setline(1, ['1st line' .. repeat(' ', 153), '2nd line'])"]]..']', - 80 - ) + local screen = thelpers.setup_child_nvim({ + '-u', 'NONE', + '-i', 'NONE', + '--cmd', 'set notermguicolors', + '--cmd', 'colorscheme vim', + '--cmd', 'call setline(1, ["1st line" .. repeat(" ", 153), "2nd line"])', + }, { cols = 80 }) screen:expect{grid=[[ {1:1}st line | | @@ -1962,16 +1964,16 @@ end) describe('TUI UIEnter/UILeave', function() it('fires exactly once, after VimEnter', function() clear() - local screen = thelpers.screen_setup(0, - '["'..nvim_prog..'", "-u", "NONE", "-i", "NONE"' - ..[[, "--cmd", "colorscheme vim"]] - ..[[, "--cmd", "set noswapfile noshowcmd noruler"]] - ..[[, "--cmd", "let g:evs = []"]] - ..[[, "--cmd", "autocmd UIEnter * :call add(g:evs, 'UIEnter')"]] - ..[[, "--cmd", "autocmd UILeave * :call add(g:evs, 'UILeave')"]] - ..[[, "--cmd", "autocmd VimEnter * :call add(g:evs, 'VimEnter')"]] - ..']' - ) + local screen = thelpers.setup_child_nvim({ + '-u', 'NONE', + '-i', 'NONE', + '--cmd', 'colorscheme vim', + '--cmd', 'set noswapfile noshowcmd noruler notermguicolors', + '--cmd', 'let g:evs = []', + '--cmd', 'autocmd UIEnter * :call add(g:evs, "UIEnter")', + '--cmd', 'autocmd UILeave * :call add(g:evs, "UILeave")', + '--cmd', 'autocmd VimEnter * :call add(g:evs, "VimEnter")', + }) screen:expect{grid=[[ {1: } | {4:~ }| @@ -2001,11 +2003,14 @@ describe('TUI FocusGained/FocusLost', function() before_each(function() clear() local child_server = new_pipename() - screen = thelpers.screen_setup(0, - string.format( - '["%s", "--listen", "%s", "-u", "NONE", "-i", "NONE", "--cmd", "colorscheme vim", ' - ..'"--cmd", "set noswapfile noshowcmd noruler"]', - nvim_prog, child_server)) + screen = thelpers.setup_child_nvim({ + '--listen', child_server, + '-u', 'NONE', + '-i', 'NONE', + '--cmd', 'colorscheme vim', + '--cmd', 'set noswapfile noshowcmd noruler notermguicolors', + }) + screen:expect([[ {1: } | {4:~ }| @@ -2208,14 +2213,18 @@ describe("TUI 't_Co' (terminal colors)", function() local function assert_term_colors(term, colorterm, maxcolors) clear({env={TERM=term}, args={}}) - -- This is ugly because :term/termopen() forces TERM=xterm-256color. - -- TODO: Revisit this after jobstart/termopen accept `env` dict. - screen = thelpers.screen_setup(0, string.format( - [=[['sh', '-c', 'LANG=C TERM=%s %s %s -u NONE -i NONE --cmd "colorscheme vim" --cmd "%s"']]=], - term or "", - (colorterm ~= nil and "COLORTERM="..colorterm or ""), - nvim_prog, - nvim_set)) + screen = thelpers.setup_child_nvim({ + '-u', 'NONE', + '-i', 'NONE', + '--cmd', 'colorscheme vim', + '--cmd', nvim_set, + }, { + env = { + LANG = 'C', + TERM = term or '', + COLORTERM = colorterm or '', + }, + }) local tline if maxcolors == 8 then @@ -2483,14 +2492,16 @@ describe("TUI 'term' option", function() local function assert_term(term_envvar, term_expected) clear() - -- This is ugly because :term/termopen() forces TERM=xterm-256color. - -- TODO: Revisit this after jobstart/termopen accept `env` dict. - local cmd = string.format( - [=[['sh', '-c', 'LANG=C TERM=%s %s -u NONE -i NONE --cmd "%s"']]=], - term_envvar or "", - nvim_prog, - nvim_set) - screen = thelpers.screen_setup(0, cmd) + screen = thelpers.setup_child_nvim({ + '-u', 'NONE', + '-i', 'NONE', + '--cmd', nvim_set, + }, { + env = { + LANG = 'C', + TERM = term_envvar or '', + }, + }) local full_timeout = screen.timeout screen.timeout = 250 -- We want screen:expect() to fail quickly. @@ -2540,14 +2551,17 @@ describe("TUI", function() -- Runs (child) `nvim` in a TTY (:terminal), to start the builtin TUI. local function nvim_tui(extra_args) clear() - -- This is ugly because :term/termopen() forces TERM=xterm-256color. - -- TODO: Revisit this after jobstart/termopen accept `env` dict. - local cmd = string.format( - [=[['sh', '-c', 'LANG=C %s -u NONE -i NONE %s --cmd "colorscheme vim" --cmd "%s"']]=], - nvim_prog, - extra_args or "", - nvim_set) - screen = thelpers.screen_setup(0, cmd) + screen = thelpers.setup_child_nvim({ + '-u', 'NONE', + '-i', 'NONE', + '--cmd', 'colorscheme vim', + '--cmd', nvim_set, + extra_args, + }, { + env = { + LANG = 'C', + }, + }) end it('-V3log logs terminfo values', function() @@ -2604,9 +2618,18 @@ describe('TUI bg color', function() local function setup_bg_test() clear() - screen = thelpers.screen_setup(0, '["'..nvim_prog - ..'", "-u", "NONE", "-i", "NONE", "--cmd", "colorscheme vim", "--cmd", "set noswapfile", ' - ..'"-c", "autocmd OptionSet background echo \\"did OptionSet, yay!\\""]') + screen = thelpers.setup_child_nvim({ + '-u', + 'NONE', + '-i', + 'NONE', + '--cmd', + 'colorscheme vim', + '--cmd', + 'set noswapfile notermguicolors', + '-c', + 'autocmd OptionSet background echo "did OptionSet, yay!"', + }) end before_each(setup_bg_test) @@ -2725,14 +2748,13 @@ describe("TUI as a client", function() set_session(server_super) local server_pipe = new_pipename() - local screen_server = thelpers.screen_setup(0, - string.format( - '["%s", "--listen", "%s", "-u", "NONE", "-i", "NONE", "--cmd", "colorscheme vim", ' - ..'"--cmd", "%s laststatus=2 background=dark"]', - nvim_prog, - server_pipe, - nvim_set - )) + local screen_server = thelpers.setup_child_nvim({ + '--listen', server_pipe, + '-u', 'NONE', + '-i', 'NONE', + '--cmd', 'colorscheme vim', + '--cmd', string.format('%s laststatus=2 background=dark', nvim_set), + }) feed_data("iHello, World") screen_server:expect{grid=[[ @@ -2756,9 +2778,10 @@ describe("TUI as a client", function() ]]} set_session(client_super) - local screen_client = thelpers.screen_setup(0, - string.format([=[["%s", "--server", "%s", "--remote-ui"]]=], - nvim_prog, server_pipe)) + local screen_client = thelpers.setup_child_nvim({ + '--server', server_pipe, + '--remote-ui', + }) screen_client:expect{grid=[[ Hello, Worl{1:d} | @@ -2798,9 +2821,10 @@ describe("TUI as a client", function() server:request('nvim_input', 'iHalloj!') set_session(client_super) - local screen_client = thelpers.screen_setup(0, - string.format([=[["%s", "--server", "%s", "--remote-ui"]]=], - nvim_prog, server_pipe)) + local screen_client = thelpers.setup_child_nvim({ + '--server', server_pipe, + '--remote-ui', + }) screen_client:expect{grid=[[ Halloj{1:!} | @@ -2836,9 +2860,10 @@ describe("TUI as a client", function() it("throws error when no server exists", function() clear() - local screen = thelpers.screen_setup(0, - string.format([=[["%s", "--server", "127.0.0.1:2436546", "--remote-ui"]]=], - nvim_prog), 60) + local screen = thelpers.setup_child_nvim({ + '--server', '127.0.0.1:2436546', + '--remote-ui', + }, { cols = 60 }) screen:expect([[ Remote ui failed to start: {MATCH:.*}| @@ -2857,14 +2882,13 @@ describe("TUI as a client", function() set_session(server_super) local server_pipe = new_pipename() - local screen_server = thelpers.screen_setup(0, - string.format( - '["%s", "--listen", "%s", "-u", "NONE", "-i", "NONE", "--cmd", "colorscheme vim", ' - ..'"--cmd", "%s laststatus=2 background=dark"]', - nvim_prog, - server_pipe, - nvim_set - )) + local screen_server = thelpers.setup_child_nvim({ + '--listen', server_pipe, + '-u', 'NONE', + '-i', 'NONE', + '--cmd', 'colorscheme vim', + '--cmd', string.format('%s laststatus=2 background=dark', nvim_set), + }) screen_server:expect{grid=[[ {1: } | {4:~ }| @@ -2897,9 +2921,10 @@ describe("TUI as a client", function() ]]} set_session(client_super) - local screen_client = thelpers.screen_setup(0, - string.format([=[["%s", "--server", "%s", "--remote-ui"]]=], - nvim_prog, server_pipe)) + local screen_client = thelpers.setup_child_nvim({ + '--server', server_pipe, + '--remote-ui', + }) screen_client:expect{grid=[[ Hello, Worl{1:d} | diff --git a/test/functional/ui/output_spec.lua b/test/functional/ui/output_spec.lua index 1dbbe76bac..719634aa2f 100644 --- a/test/functional/ui/output_spec.lua +++ b/test/functional/ui/output_spec.lua @@ -21,8 +21,12 @@ describe("shell command :!", function() local screen before_each(function() clear() - screen = child_session.screen_setup(0, '["'..helpers.nvim_prog.. - '", "-u", "NONE", "-i", "NONE", "--cmd", "colorscheme vim", "--cmd", "'..helpers.nvim_set..'"]') + screen = child_session.setup_child_nvim({ + '-u', 'NONE', + '-i', 'NONE', + '--cmd', 'colorscheme vim', + '--cmd', helpers.nvim_set, + }) screen:expect([[ {1: } | {4:~ }| -- cgit