Merge #7640 'term' option

This commit is contained in:
Justin M. Keyes 2017-11-27 22:07:23 +01:00 committed by GitHub
commit d109f5645b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 167 additions and 65 deletions

View File

@ -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. example, 'guicursor' sets the terminal cursor style if possible.
*'term'* *E529* *E530* *E531* *'term'* *E529* *E530* *E531*
The 'term' option has a fixed value, present only for script compatibility and 'term' reflects the terminal type derived from |$TERM| and other environment
intentionally not the same as any known terminal type name. It should be a checks. For debugging only; not reliable during startup. >
rare case in Nvim where one needs |term-dependent-settings|. :echo &term
"builtin_x" means one of the |builtin-terms| was chosen, because the expected
terminfo file was not found on the system.
*termcap* *termcap*
Nvim never uses the termcap database, only |terminfo| and |builtin-terms|. Nvim never uses the termcap database, only |terminfo| and |builtin-terms|.

View File

@ -105,6 +105,9 @@ typedef enum {
*/ */
#define VAR_WIN ((char_u *)-1) #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. * These are the global values for options which are also local to a buffer.
* Only to be used in option.c! * 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) bool is_tty_option(const char *name)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT 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 #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) bool get_tty_option(char *name, char **value)
{ {
if (!strcmp(name, "t_Co")) { if (strequal(name, "t_Co")) {
if (value) { if (value) {
if (t_colors <= 1) { if (t_colors <= 1) {
*value = xstrdup(""); *value = xstrdup("");
@ -4548,9 +4555,16 @@ bool get_tty_option(char *name, char **value)
return true; return true;
} }
if (!strcmp(name, "term") || !strcmp(name, "ttytype")) { if (strequal(name, "term")) {
if (value) { 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; return true;
} }
@ -4566,25 +4580,25 @@ bool get_tty_option(char *name, char **value)
return false; 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")) { if (strequal(name, "term")) {
int colors = atoi(value); if (p_term) {
xfree(p_term);
// 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);
} }
p_term = value;
return true; return true;
} }
return (is_tty_option(name) || !strcmp(name, "term") if (strequal(name, "ttytype")) {
|| !strcmp(name, "ttytype")); if (p_ttytype) {
xfree(p_ttytype);
}
p_ttytype = value;
return true;
}
return false;
} }
/// Find index for an option /// Find index for an option
@ -4597,21 +4611,18 @@ static int findoption(const char *const arg)
return findoption_len(arg, strlen(arg)); return findoption_len(arg, strlen(arg));
} }
/* /// Gets the value for an option.
* Get the value for an option. ///
* /// @returns:
* Returns: /// Number or Toggle option: 1, *numval gets value.
* Number or Toggle option: 1, *numval gets value. /// String option: 0, *stringval gets allocated string.
* String option: 0, *stringval gets allocated string. /// Hidden Number or Toggle option: -1.
* Hidden Number or Toggle option: -1. /// hidden String option: -2.
* hidden String option: -2. /// unknown option: -3.
* unknown option: -3. int get_option_value(
*/
int
get_option_value (
char_u *name, char_u *name,
long *numval, long *numval,
char_u **stringval, /* NULL when only checking existence */ char_u **stringval, ///< NULL when only checking existence
int opt_flags int opt_flags
) )
{ {
@ -4619,32 +4630,31 @@ get_option_value (
return 0; return 0;
} }
int opt_idx; int opt_idx = findoption((const char *)name);
char_u *varp;
opt_idx = findoption((const char *)name);
if (opt_idx < 0) { // Unknown option. if (opt_idx < 0) { // Unknown option.
return -3; 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 (options[opt_idx].flags & P_STRING) {
if (varp == NULL) /* hidden option */ if (varp == NULL) { // hidden option
return -2; return -2;
}
if (stringval != NULL) { if (stringval != NULL) {
*stringval = vim_strsave(*(char_u **)(varp)); *stringval = vim_strsave(*(char_u **)(varp));
} }
return 0; return 0;
} }
if (varp == NULL) /* hidden option */ if (varp == NULL) { // hidden option
return -1; return -1;
if (options[opt_idx].flags & P_NUM) }
if (options[opt_idx].flags & P_NUM) {
*numval = *(long *)varp; *numval = *(long *)varp;
else { } else {
/* Special case: 'modified' is b_changed, but we also want to consider // Special case: 'modified' is b_changed, but we also want to consider
* it set when 'ff' or 'fenc' changed. */ // it set when 'ff' or 'fenc' changed.
if ((int *)varp == &curbuf->b_changed) { if ((int *)varp == &curbuf->b_changed) {
*numval = curbufIsChanged(); *numval = curbufIsChanged();
} else { } else {
@ -4791,8 +4801,8 @@ char *set_option_value(const char *const name, const long number,
const char *const string, const int opt_flags) const char *const string, const int opt_flags)
FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_NONNULL_ARG(1)
{ {
if (set_tty_option(name, string)) { if (is_tty_option(name)) {
return NULL; return NULL; // Fail silently; many old vimrcs set t_xx options.
} }
int opt_idx; int opt_idx;

View File

@ -9,6 +9,7 @@
#include <unibilium.h> #include <unibilium.h>
#include "nvim/log.h" #include "nvim/log.h"
#include "nvim/memory.h"
#include "nvim/tui/terminfo.h" #include "nvim/tui/terminfo.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS #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 /// Loads a built-in terminfo db when we (unibilium) failed to load a terminfo
/// record from the environment (termcap systems, unrecognized $TERM, …). /// record from the environment (termcap systems, unrecognized $TERM, …).
/// We do not attempt to detect xterm pretenders here. /// 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")) { if (terminfo_is_term_family(term, "xterm")) {
*termname = xstrdup("builtin_xterm");
return unibi_from_mem((const char *)xterm_256colour_terminfo, return unibi_from_mem((const char *)xterm_256colour_terminfo,
sizeof xterm_256colour_terminfo); sizeof xterm_256colour_terminfo);
} else if (terminfo_is_term_family(term, "screen")) { } else if (terminfo_is_term_family(term, "screen")) {
*termname = xstrdup("builtin_screen");
return unibi_from_mem((const char *)screen_256colour_terminfo, return unibi_from_mem((const char *)screen_256colour_terminfo,
sizeof screen_256colour_terminfo); sizeof screen_256colour_terminfo);
} else if (terminfo_is_term_family(term, "tmux")) { } else if (terminfo_is_term_family(term, "tmux")) {
*termname = xstrdup("builtin_tmux");
return unibi_from_mem((const char *)tmux_256colour_terminfo, return unibi_from_mem((const char *)tmux_256colour_terminfo,
sizeof tmux_256colour_terminfo); sizeof tmux_256colour_terminfo);
} else if (terminfo_is_term_family(term, "rxvt")) { } else if (terminfo_is_term_family(term, "rxvt")) {
*termname = xstrdup("builtin_rxvt");
return unibi_from_mem((const char *)rxvt_256colour_terminfo, return unibi_from_mem((const char *)rxvt_256colour_terminfo,
sizeof rxvt_256colour_terminfo); sizeof rxvt_256colour_terminfo);
} else if (terminfo_is_term_family(term, "putty")) { } else if (terminfo_is_term_family(term, "putty")) {
*termname = xstrdup("builtin_putty");
return unibi_from_mem((const char *)putty_256colour_terminfo, return unibi_from_mem((const char *)putty_256colour_terminfo,
sizeof putty_256colour_terminfo); sizeof putty_256colour_terminfo);
} else if (terminfo_is_term_family(term, "linux")) { } else if (terminfo_is_term_family(term, "linux")) {
*termname = xstrdup("builtin_linux");
return unibi_from_mem((const char *)linux_16colour_terminfo, return unibi_from_mem((const char *)linux_16colour_terminfo,
sizeof linux_16colour_terminfo); sizeof linux_16colour_terminfo);
} else if (terminfo_is_term_family(term, "interix")) { } else if (terminfo_is_term_family(term, "interix")) {
*termname = xstrdup("builtin_interix");
return unibi_from_mem((const char *)interix_8colour_terminfo, return unibi_from_mem((const char *)interix_8colour_terminfo,
sizeof interix_8colour_terminfo); sizeof interix_8colour_terminfo);
} else if (terminfo_is_term_family(term, "iterm") } else if (terminfo_is_term_family(term, "iterm")
|| terminfo_is_term_family(term, "iterm2") || terminfo_is_term_family(term, "iterm2")
|| terminfo_is_term_family(term, "iTerm.app") || terminfo_is_term_family(term, "iTerm.app")
|| terminfo_is_term_family(term, "iTerm2.app")) { || terminfo_is_term_family(term, "iTerm2.app")) {
*termname = xstrdup("builtin_iterm");
return unibi_from_mem((const char *)iterm_256colour_terminfo, return unibi_from_mem((const char *)iterm_256colour_terminfo,
sizeof iterm_256colour_terminfo); sizeof iterm_256colour_terminfo);
} else if (terminfo_is_term_family(term, "st")) { } else if (terminfo_is_term_family(term, "st")) {
*termname = xstrdup("builtin_st");
return unibi_from_mem((const char *)st_256colour_terminfo, return unibi_from_mem((const char *)st_256colour_terminfo,
sizeof st_256colour_terminfo); sizeof st_256colour_terminfo);
} else if (terminfo_is_term_family(term, "gnome") } else if (terminfo_is_term_family(term, "gnome")
|| terminfo_is_term_family(term, "vte")) { || terminfo_is_term_family(term, "vte")) {
*termname = xstrdup("builtin_vte");
return unibi_from_mem((const char *)vte_256colour_terminfo, return unibi_from_mem((const char *)vte_256colour_terminfo,
sizeof vte_256colour_terminfo); sizeof vte_256colour_terminfo);
} else { } else {
*termname = xstrdup("builtin_ansi");
return unibi_from_mem((const char *)ansi_terminfo, return unibi_from_mem((const char *)ansi_terminfo,
sizeof 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 // Disable BCE by default (for built-in terminfos). #7624
// https://github.com/kovidgoyal/kitty/issues/160#issuecomment-346470545 // https://github.com/kovidgoyal/kitty/issues/160#issuecomment-346470545
unibi_set_bool(ut, unibi_back_color_erase, false); unibi_set_bool(ut, unibi_back_color_erase, false);

View File

@ -23,6 +23,7 @@
#include "nvim/map.h" #include "nvim/map.h"
#include "nvim/main.h" #include "nvim/main.h"
#include "nvim/memory.h" #include "nvim/memory.h"
#include "nvim/option.h"
#include "nvim/api/vim.h" #include "nvim/api/vim.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/event/loop.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); 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) static void terminfo_start(UI *ui)
{ {
TUIData *data = ui->data; TUIData *data = ui->data;
@ -190,12 +198,20 @@ static void terminfo_start(UI *ui)
data->unibi_ext.reset_cursor_style = -1; data->unibi_ext.reset_cursor_style = -1;
data->out_fd = 1; data->out_fd = 1;
data->out_isatty = os_isatty(data->out_fd); data->out_isatty = os_isatty(data->out_fd);
// setup unibilium
// Set up unibilium/terminfo.
const char *term = os_getenv("TERM"); const char *term = os_getenv("TERM");
data->ut = unibi_from_env(); data->ut = unibi_from_env();
char *termname = NULL;
if (!data->ut) { 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 . // None of the following work over SSH; see :help TERM .
const char *colorterm = os_getenv("COLORTERM"); const char *colorterm = os_getenv("COLORTERM");
const char *termprg = os_getenv("TERM_PROGRAM"); const char *termprg = os_getenv("TERM_PROGRAM");
@ -344,7 +360,7 @@ static void tui_scheduler(Event event, void *d)
{ {
UI *ui = d; UI *ui = d;
TUIData *data = ui->data; TUIData *data = ui->data;
loop_schedule(data->loop, event); loop_schedule(data->loop, event); // `tui_loop` local to tui_main().
} }
#ifdef UNIX #ifdef UNIX

View File

@ -603,6 +603,15 @@ local function get_pathsep()
return funcs.fnamemodify('.', ':p'):sub(-1) return funcs.fnamemodify('.', ':p'):sub(-1)
end 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) local function missing_provider(provider)
if provider == 'ruby' then if provider == 'ruby' then
local prog = funcs['provider#' .. provider .. '#Detect']() local prog = funcs['provider#' .. provider .. '#Detect']()
@ -732,6 +741,7 @@ local module = {
missing_provider = missing_provider, missing_provider = missing_provider,
alter_slashes = alter_slashes, alter_slashes = alter_slashes,
hexdump = hexdump, hexdump = hexdump,
new_pipename = new_pipename,
} }
return function(after_each) return function(after_each)

View File

@ -1,12 +1,16 @@
-- TUI acceptance tests. -- TUI acceptance tests.
-- Uses :terminal as a way to send keys and assert screen state. -- Uses :terminal as a way to send keys and assert screen state.
local global_helpers = require('test.helpers') local global_helpers = require('test.helpers')
local uname = global_helpers.uname
local helpers = require('test.functional.helpers')(after_each) local helpers = require('test.functional.helpers')(after_each)
local thelpers = require('test.functional.terminal.helpers') local thelpers = require('test.functional.terminal.helpers')
local feed_data = thelpers.feed_data local feed_data = thelpers.feed_data
local feed_command = helpers.feed_command local feed_command = helpers.feed_command
local clear = helpers.clear
local nvim_dir = helpers.nvim_dir local nvim_dir = helpers.nvim_dir
local retry = helpers.retry local retry = helpers.retry
local nvim_prog = helpers.nvim_prog
local nvim_set = helpers.nvim_set
if helpers.pending_win32(pending) then return end if helpers.pending_win32(pending) then return end
@ -14,9 +18,9 @@ describe('tui', function()
local screen local screen
before_each(function() before_each(function()
helpers.clear() clear()
screen = thelpers.screen_setup(0, '["'..helpers.nvim_prog screen = thelpers.screen_setup(0, '["'..nvim_prog
..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile noshowcmd noruler"]') ..'", "-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. -- 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. -- this will be fixed later but for now we require a high timeout.
screen.timeout = 60000 screen.timeout = 60000
@ -372,19 +376,20 @@ end)
-- does not initialize the TUI. -- does not initialize the TUI.
describe("tui 't_Co' (terminal colors)", function() describe("tui 't_Co' (terminal colors)", function()
local screen 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) local function assert_term_colors(term, colorterm, maxcolors)
helpers.clear({env={TERM=term}, args={}}) helpers.clear({env={TERM=term}, args={}})
-- This is ugly because :term/termopen() forces TERM=xterm-256color. -- This is ugly because :term/termopen() forces TERM=xterm-256color.
-- TODO: Revisit this after jobstart/termopen accept `env` dict. -- TODO: Revisit this after jobstart/termopen accept `env` dict.
screen = thelpers.screen_setup(0, string.format( 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 "", term or "",
(colorterm ~= nil and "COLORTERM="..colorterm 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() helpers.wait()
local tline local tline
if maxcolors == 8 or maxcolors == 16 then if maxcolors == 8 or maxcolors == 16 then
@ -397,10 +402,10 @@ describe("tui 't_Co' (terminal colors)", function()
%s| %s|
%s| %s|
%s| %s|
{5:[No Name] }| %s|
%-3s | %-3s |
{3:-- TERMINAL --} | {3:-- TERMINAL --} |
]], tline, tline, tline, tostring(maxcolors and maxcolors or ""))) ]], tline, tline, tline, tline, tostring(maxcolors and maxcolors or "")))
end end
-- ansi and no terminal type at all: -- ansi and no terminal type at all:
@ -628,3 +633,42 @@ describe("tui 't_Co' (terminal colors)", function()
end) end)
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)

View File

@ -4,7 +4,7 @@ local os = require('os')
local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert
local command = helpers.command local command = helpers.command
local eval, exc_exec = helpers.eval, helpers.exc_exec 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 local curbufmeths = helpers.curbufmeths
describe('colorscheme compatibility', function() describe('colorscheme compatibility', function()
@ -14,8 +14,6 @@ describe('colorscheme compatibility', function()
it('t_Co is set to 256 by default', function() it('t_Co is set to 256 by default', function()
eq('256', eval('&t_Co')) eq('256', eval('&t_Co'))
request('nvim_set_option', 't_Co', '88')
eq('88', eval('&t_Co'))
end) end)
end) end)