diff options
| -rw-r--r-- | runtime/doc/vim_diff.txt | 8 | ||||
| -rw-r--r-- | src/nvim/option.c | 96 | ||||
| -rw-r--r-- | src/nvim/tui/terminfo.c | 28 | ||||
| -rw-r--r-- | src/nvim/tui/tui.c | 22 | ||||
| -rw-r--r-- | test/functional/helpers.lua | 10 | ||||
| -rw-r--r-- | test/functional/terminal/tui_spec.lua | 62 | ||||
| -rw-r--r-- | test/functional/ui/highlight_spec.lua | 4 | 
7 files changed, 166 insertions, 64 deletions
| diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 026ff6a0fb..c8155f7a68 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -333,9 +333,11 @@ terminal capabilities. Instead Nvim treats the terminal as any other UI. For  example, 'guicursor' sets the terminal cursor style if possible.  						  *'term'* *E529* *E530* *E531* -The 'term' option has a fixed value, present only for script compatibility and -intentionally not the same as any known terminal type name.  It should be a -rare case in Nvim where one needs |term-dependent-settings|. +'term' reflects the terminal type derived from |$TERM| and other environment +checks.  For debugging only; not reliable during startup. > +	:echo &term +"builtin_x" means one of the |builtin-terms| was chosen, because the expected +terminfo file was not found on the system.  								*termcap*  Nvim never uses the termcap database, only |terminfo| and |builtin-terms|. diff --git a/src/nvim/option.c b/src/nvim/option.c index 65ab7a54a6..913d27d508 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -105,6 +105,9 @@ typedef enum {   */  #define VAR_WIN ((char_u *)-1) +static char *p_term = NULL; +static char *p_ttytype = NULL; +  /*   * These are the global values for options which are also local to a buffer.   * Only to be used in option.c! @@ -4530,13 +4533,17 @@ int findoption_len(const char *const arg, const size_t len)  bool is_tty_option(const char *name)    FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT  { -  return (name[0] == 't' && name[1] == '_') || strcmp(name, "term") == 0; +  return (name[0] == 't' && name[1] == '_') +    || strequal(name, "term") +    || strequal(name, "ttytype");  }  #define TCO_BUFFER_SIZE 8 +/// @param name TUI-related option +/// @param[out,allocated] value option string value  bool get_tty_option(char *name, char **value)  { -  if (!strcmp(name, "t_Co")) { +  if (strequal(name, "t_Co")) {      if (value) {        if (t_colors <= 1) {          *value = xstrdup(""); @@ -4548,9 +4555,16 @@ bool get_tty_option(char *name, char **value)      return true;    } -  if (!strcmp(name, "term") || !strcmp(name, "ttytype")) { +  if (strequal(name, "term")) {      if (value) { -      *value = xstrdup("nvim"); +      *value = p_term ? xstrdup(p_term) : xstrdup("nvim"); +    } +    return true; +  } + +  if (strequal(name, "ttytype")) { +    if (value) { +      *value = p_ttytype ? xstrdup(p_ttytype) : xstrdup("nvim");      }      return true;    } @@ -4566,25 +4580,25 @@ bool get_tty_option(char *name, char **value)    return false;  } -bool set_tty_option(const char *name, const char *value) +bool set_tty_option(const char *name, char *value)  { -  if (!strcmp(name, "t_Co")) { -    int colors = atoi(value); - -    // Only reinitialize colors if t_Co value has really changed to -    // avoid expensive reload of colorscheme if t_Co is set to the -    // same value multiple times -    if (colors != t_colors) { -      t_colors = colors; -      // We now have a different color setup, initialize it again. -      init_highlight(true, false); +  if (strequal(name, "term")) { +    if (p_term) { +      xfree(p_term);      } +    p_term = value; +    return true; +  } +  if (strequal(name, "ttytype")) { +    if (p_ttytype) { +      xfree(p_ttytype); +    } +    p_ttytype = value;      return true;    } -  return (is_tty_option(name) || !strcmp(name, "term") -          || !strcmp(name, "ttytype")); +  return false;  }  /// Find index for an option @@ -4597,21 +4611,18 @@ static int findoption(const char *const arg)    return findoption_len(arg, strlen(arg));  } -/* - * Get the value for an option. - * - * Returns: - * Number or Toggle option: 1, *numval gets value. - *	     String option: 0, *stringval gets allocated string. - * Hidden Number or Toggle option: -1. - *	     hidden String option: -2. - *		   unknown option: -3. - */ -int  -get_option_value ( +/// Gets the value for an option. +/// +/// @returns: +/// Number or Toggle option: 1, *numval gets value. +///           String option: 0, *stringval gets allocated string. +/// Hidden Number or Toggle option: -1. +///           hidden String option: -2. +///                 unknown option: -3. +int get_option_value(      char_u *name,      long *numval, -    char_u **stringval,            /* NULL when only checking existence */ +    char_u **stringval,            ///< NULL when only checking existence      int opt_flags  )  { @@ -4619,32 +4630,31 @@ get_option_value (      return 0;    } -  int opt_idx; -  char_u      *varp; - -  opt_idx = findoption((const char *)name); +  int opt_idx = findoption((const char *)name);    if (opt_idx < 0) {  // Unknown option.      return -3;    } -  varp = get_varp_scope(&(options[opt_idx]), opt_flags); +  char_u *varp = get_varp_scope(&(options[opt_idx]), opt_flags);    if (options[opt_idx].flags & P_STRING) { -    if (varp == NULL)                       /* hidden option */ +    if (varp == NULL) {  // hidden option        return -2; +    }      if (stringval != NULL) {        *stringval = vim_strsave(*(char_u **)(varp));      }      return 0;    } -  if (varp == NULL)                 /* hidden option */ +  if (varp == NULL) {  // hidden option      return -1; -  if (options[opt_idx].flags & P_NUM) +  } +  if (options[opt_idx].flags & P_NUM) {      *numval = *(long *)varp; -  else { -    /* Special case: 'modified' is b_changed, but we also want to consider -     * it set when 'ff' or 'fenc' changed. */ +  } else { +    // Special case: 'modified' is b_changed, but we also want to consider +    // it set when 'ff' or 'fenc' changed.      if ((int *)varp == &curbuf->b_changed) {        *numval = curbufIsChanged();      } else { @@ -4791,8 +4801,8 @@ char *set_option_value(const char *const name, const long number,                         const char *const string, const int opt_flags)    FUNC_ATTR_NONNULL_ARG(1)  { -  if (set_tty_option(name, string)) { -    return NULL; +  if (is_tty_option(name)) { +    return NULL;  // Fail silently; many old vimrcs set t_xx options.    }    int opt_idx; diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c index 75e9a2d8da..fdc33f0a77 100644 --- a/src/nvim/tui/terminfo.c +++ b/src/nvim/tui/terminfo.c @@ -9,6 +9,7 @@  #include <unibilium.h>  #include "nvim/log.h" +#include "nvim/memory.h"  #include "nvim/tui/terminfo.h"  #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -94,51 +95,72 @@ bool terminfo_is_term_family(const char *term, const char *family)  /// Loads a built-in terminfo db when we (unibilium) failed to load a terminfo  /// record from the environment (termcap systems, unrecognized $TERM, …).  /// We do not attempt to detect xterm pretenders here. -static unibi_term *terminfo_builtin(const char *term) +/// +/// @param term $TERM value +/// @param[out,allocated] termname decided builtin 'term' name +/// @return [allocated] terminfo structure +static unibi_term *terminfo_builtin(const char *term, char **termname)  {    if (terminfo_is_term_family(term, "xterm")) { +    *termname = xstrdup("builtin_xterm");      return unibi_from_mem((const char *)xterm_256colour_terminfo,                            sizeof xterm_256colour_terminfo);    } else if (terminfo_is_term_family(term, "screen")) { +    *termname = xstrdup("builtin_screen");      return unibi_from_mem((const char *)screen_256colour_terminfo,                            sizeof screen_256colour_terminfo);    } else if (terminfo_is_term_family(term, "tmux")) { +    *termname = xstrdup("builtin_tmux");      return unibi_from_mem((const char *)tmux_256colour_terminfo,                            sizeof tmux_256colour_terminfo);    } else if (terminfo_is_term_family(term, "rxvt")) { +    *termname = xstrdup("builtin_rxvt");      return unibi_from_mem((const char *)rxvt_256colour_terminfo,                            sizeof rxvt_256colour_terminfo);    } else if (terminfo_is_term_family(term, "putty")) { +    *termname = xstrdup("builtin_putty");      return unibi_from_mem((const char *)putty_256colour_terminfo,                            sizeof putty_256colour_terminfo);    } else if (terminfo_is_term_family(term, "linux")) { +    *termname = xstrdup("builtin_linux");      return unibi_from_mem((const char *)linux_16colour_terminfo,                            sizeof linux_16colour_terminfo);    } else if (terminfo_is_term_family(term, "interix")) { +    *termname = xstrdup("builtin_interix");      return unibi_from_mem((const char *)interix_8colour_terminfo,                            sizeof interix_8colour_terminfo);    } else if (terminfo_is_term_family(term, "iterm")               || terminfo_is_term_family(term, "iterm2")               || terminfo_is_term_family(term, "iTerm.app")               || terminfo_is_term_family(term, "iTerm2.app")) { +    *termname = xstrdup("builtin_iterm");      return unibi_from_mem((const char *)iterm_256colour_terminfo,                            sizeof iterm_256colour_terminfo);    } else if (terminfo_is_term_family(term, "st")) { +    *termname = xstrdup("builtin_st");      return unibi_from_mem((const char *)st_256colour_terminfo,                            sizeof st_256colour_terminfo);    } else if (terminfo_is_term_family(term, "gnome")               || terminfo_is_term_family(term, "vte")) { +    *termname = xstrdup("builtin_vte");      return unibi_from_mem((const char *)vte_256colour_terminfo,                            sizeof vte_256colour_terminfo);    } else { +    *termname = xstrdup("builtin_ansi");      return unibi_from_mem((const char *)ansi_terminfo,                            sizeof ansi_terminfo);    }  } -unibi_term *terminfo_from_builtin(const char *term) +/// @param term $TERM value +/// @param[out,allocated] termname decided builtin 'term' name +/// @return [allocated] terminfo structure +unibi_term *terminfo_from_builtin(const char *term, char **termname)  { -  unibi_term *ut = terminfo_builtin(term); +  unibi_term *ut = terminfo_builtin(term, termname); +  if (*termname == NULL) { +    *termname = xstrdup("builtin_?"); +  }    // Disable BCE by default (for built-in terminfos). #7624    // https://github.com/kovidgoyal/kitty/issues/160#issuecomment-346470545    unibi_set_bool(ut, unibi_back_color_erase, false); diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index c2e597c36c..732a86d0fb 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -23,6 +23,7 @@  #include "nvim/map.h"  #include "nvim/main.h"  #include "nvim/memory.h" +#include "nvim/option.h"  #include "nvim/api/vim.h"  #include "nvim/api/private/helpers.h"  #include "nvim/event/loop.h" @@ -166,6 +167,13 @@ static size_t unibi_pre_fmt_str(TUIData *data, unsigned int unibi_index,    return unibi_run(str, data->params, buf, len);  } +static void termname_set_event(void **argv) +{ +  char *termname = argv[0]; +  set_tty_option("term", termname); +  // Do not free termname, it is freed by set_tty_option. +} +  static void terminfo_start(UI *ui)  {    TUIData *data = ui->data; @@ -190,12 +198,20 @@ static void terminfo_start(UI *ui)    data->unibi_ext.reset_cursor_style = -1;    data->out_fd = 1;    data->out_isatty = os_isatty(data->out_fd); -  // setup unibilium + +  // Set up unibilium/terminfo.    const char *term = os_getenv("TERM");    data->ut = unibi_from_env(); +  char *termname = NULL;    if (!data->ut) { -    data->ut = terminfo_from_builtin(term); +    data->ut = terminfo_from_builtin(term, &termname); +  } else { +    termname = xstrdup(term);    } +  // Update 'term' option. +  loop_schedule_deferred(&main_loop, +                         event_create(termname_set_event, 1, termname)); +    // None of the following work over SSH; see :help TERM .    const char *colorterm = os_getenv("COLORTERM");    const char *termprg = os_getenv("TERM_PROGRAM"); @@ -344,7 +360,7 @@ static void tui_scheduler(Event event, void *d)  {    UI *ui = d;    TUIData *data = ui->data; -  loop_schedule(data->loop, event); +  loop_schedule(data->loop, event);  // `tui_loop` local to tui_main().  }  #ifdef UNIX diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 272d2045c9..da334d4ac6 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -603,6 +603,15 @@ local function get_pathsep()    return funcs.fnamemodify('.', ':p'):sub(-1)  end +-- Returns a valid, platform-independent $NVIM_LISTEN_ADDRESS. +-- Useful for communicating with child instances. +local function new_pipename() +  -- HACK: Start a server temporarily, get the name, then stop it. +  local pipename = nvim_eval('serverstart()') +  funcs.serverstop(pipename) +  return pipename +end +  local function missing_provider(provider)    if provider == 'ruby' then      local prog = funcs['provider#' .. provider .. '#Detect']() @@ -732,6 +741,7 @@ local module = {    missing_provider = missing_provider,    alter_slashes = alter_slashes,    hexdump = hexdump, +  new_pipename = new_pipename,  }  return function(after_each) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 777ef65d9e..d5f6a21d1d 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -1,12 +1,16 @@  -- TUI acceptance tests.  -- Uses :terminal as a way to send keys and assert screen state.  local global_helpers = require('test.helpers') +local uname = global_helpers.uname  local helpers = require('test.functional.helpers')(after_each)  local thelpers = require('test.functional.terminal.helpers')  local feed_data = thelpers.feed_data  local feed_command = helpers.feed_command +local clear = helpers.clear  local nvim_dir = helpers.nvim_dir  local retry = helpers.retry +local nvim_prog = helpers.nvim_prog +local nvim_set = helpers.nvim_set  if helpers.pending_win32(pending) then return end @@ -14,9 +18,9 @@ describe('tui', function()    local screen    before_each(function() -    helpers.clear() -    screen = thelpers.screen_setup(0, '["'..helpers.nvim_prog -      ..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile noshowcmd noruler"]') +    clear() +    screen = thelpers.screen_setup(0, '["'..nvim_prog +      ..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile noshowcmd noruler undodir=. directory=. viewdir=. backupdir=."]')      -- right now pasting can be really slow in the TUI, especially in ASAN.      -- this will be fixed later but for now we require a high timeout.      screen.timeout = 60000 @@ -372,19 +376,20 @@ end)  -- does not initialize the TUI.  describe("tui 't_Co' (terminal colors)", function()    local screen -  local is_freebsd = (string.lower(global_helpers.uname()) == 'freebsd') +  local is_freebsd = (string.lower(uname()) == 'freebsd')    local function assert_term_colors(term, colorterm, maxcolors)      helpers.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 "silent set noswapfile noshowcmd noruler"']]=], +      [=[['sh', '-c', 'LANG=C TERM=%s %s %s -u NONE -i NONE --cmd "%s"']]=],        term or "",        (colorterm ~= nil and "COLORTERM="..colorterm or ""), -      helpers.nvim_prog)) +      nvim_prog, +      nvim_set)) -    thelpers.feed_data(":echo &t_Co\n") +    feed_data(":echo &t_Co\n")      helpers.wait()      local tline      if maxcolors == 8 or maxcolors == 16 then @@ -397,10 +402,10 @@ describe("tui 't_Co' (terminal colors)", function()        %s|        %s|        %s| -      {5:[No Name]                                         }| +      %s|        %-3s                                               |        {3:-- TERMINAL --}                                    | -    ]], tline, tline, tline, tostring(maxcolors and maxcolors or ""))) +    ]], tline, tline, tline, tline, tostring(maxcolors and maxcolors or "")))    end    -- ansi and no terminal type at all: @@ -628,3 +633,42 @@ describe("tui 't_Co' (terminal colors)", function()    end)  end) + +-- These tests require `thelpers` because --headless/--embed +-- does not initialize the TUI. +describe("tui 'term' option", function() +  local screen +  local is_bsd = not not string.find(string.lower(uname()), 'bsd') + +  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) + +    local full_timeout = screen.timeout +    screen.timeout = 250  -- We want screen:expect() to fail quickly. +    retry(nil, 2 * full_timeout, function()  -- Wait for TUI thread to set 'term'. +      feed_data(":echo 'term='.(&term)\n") +      screen:expect('term='..term_expected, nil, nil, nil, true) +    end) +  end + +  it('gets builtin term if $TERM is invalid', function() +    assert_term("foo", "builtin_ansi") +  end) + +  it('gets system-provided term if $TERM is valid', function() +    if is_bsd then  -- BSD lacks terminfo, we always use builtin there. +      assert_term("xterm", "builtin_xterm") +    else +      assert_term("xterm", "xterm") +    end +  end) + +end) diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index d1357ea525..2252e3580f 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -4,7 +4,7 @@ local os = require('os')  local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert  local command = helpers.command  local eval, exc_exec = helpers.eval, helpers.exc_exec -local feed_command, request, eq = helpers.feed_command, helpers.request, helpers.eq +local feed_command, eq = helpers.feed_command, helpers.eq  local curbufmeths = helpers.curbufmeths  describe('colorscheme compatibility', function() @@ -14,8 +14,6 @@ describe('colorscheme compatibility', function()    it('t_Co is set to 256 by default', function()      eq('256', eval('&t_Co')) -    request('nvim_set_option', 't_Co', '88') -    eq('88', eval('&t_Co'))    end)  end) | 
