diff --git a/Makefile.in b/Makefile.in index b73c6127d..9f76473cd 100644 --- a/Makefile.in +++ b/Makefile.in @@ -972,7 +972,7 @@ obj/builtin_set.o: config.h src/builtin.h src/common.h src/fallback.h obj/builtin_set.o: src/signal.h src/env.h src/expand.h src/parse_constants.h obj/builtin_set.o: src/io.h src/proc.h src/parse_tree.h src/tokenizer.h obj/builtin_set.o: src/wgetopt.h src/wutil.h -obj/builtin_set_color.o: config.h src/builtin.h src/common.h src/fallback.h +obj/builtin_set_color.o: config.h src/builtin.h src/common.h src/env.h src/fallback.h obj/builtin_set_color.o: src/signal.h src/color.h src/io.h src/output.h obj/builtin_set_color.o: src/proc.h src/parse_tree.h src/parse_constants.h obj/builtin_set_color.o: src/tokenizer.h src/wgetopt.h src/wutil.h diff --git a/build_tools/iwyu.osx.imp b/build_tools/iwyu.osx.imp index f55ba0be4..b44054996 100644 --- a/build_tools/iwyu.osx.imp +++ b/build_tools/iwyu.osx.imp @@ -93,7 +93,9 @@ { symbol: ["uint32_t", "private", "", "public"] }, { symbol: ["intptr_t", "private", "", "public"] }, { symbol: ["tparm", "private", "", "public"] }, + { symbol: ["tigetflag", "private", "", "public"] }, { symbol: ["ERR", "private", "", "public"] }, + { symbol: ["OK", "private", "", "public"] }, { symbol: ["select", "private", "", "public"] }, { symbol: ["_LIBCPP_VERSION", "private", "", "public"] }, { symbol: ["_LIBCPP_VERSION", "private", "", "public"] }, diff --git a/src/builtin_set_color.cpp b/src/builtin_set_color.cpp index a75989736..0ca1159f5 100644 --- a/src/builtin_set_color.cpp +++ b/src/builtin_set_color.cpp @@ -3,7 +3,6 @@ #include #include -#include #if HAVE_NCURSES_H #include @@ -25,6 +24,7 @@ #include "builtin.h" #include "color.h" #include "common.h" +#include "env.h" #include "io.h" #include "output.h" #include "proc.h" @@ -52,6 +52,9 @@ static int set_color_builtin_outputter(char c) { /// set_color builtin. int builtin_set_color(parser_t &parser, io_streams_t &streams, wchar_t **argv) { + // By the time this is called we should have initialized the curses subsystem. + assert(curses_initialized); + wgetopter_t w; // Variables used for parsing the argument list. const struct woption long_options[] = {{L"background", required_argument, 0, 'b'}, @@ -66,7 +69,6 @@ int builtin_set_color(parser_t &parser, io_streams_t &streams, wchar_t **argv) { {0, 0, 0, 0}}; const wchar_t *short_options = L"b:hvoidrcu"; - int argc = builtin_count_args(argv); // Some code passes variables to set_color that don't exist, like $fish_user_whatever. As a @@ -77,7 +79,6 @@ int builtin_set_color(parser_t &parser, io_streams_t &streams, wchar_t **argv) { const wchar_t *bgcolor = NULL; bool bold = false, underline = false, italics = false, dim = false, reverse = false; - int errret; // Parse options to obtain the requested operation and the modifiers. w.woptind = 0; @@ -162,12 +163,6 @@ int builtin_set_color(parser_t &parser, io_streams_t &streams, wchar_t **argv) { return STATUS_BUILTIN_ERROR; } - // Make sure that the term exists. - if (cur_term == NULL && setupterm(0, STDOUT_FILENO, &errret) == ERR) { - streams.err.append_format(_(L"%ls: Could not set up terminal\n"), argv[0]); - return STATUS_BUILTIN_ERROR; - } - // Test if we have at least basic support for setting fonts, colors and related bits - otherwise // just give up... if (cur_term == NULL || !exit_attribute_mode) { diff --git a/src/env.cpp b/src/env.cpp index 40f796746..71d400db7 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -14,6 +15,13 @@ #include #include +#if HAVE_NCURSES_H +#include +#elif HAVE_NCURSES_CURSES_H +#include +#else +#include +#endif #if HAVE_TERM_H #include #elif HAVE_NCURSES_TERM_H @@ -37,6 +45,7 @@ #include "history.h" #include "input.h" #include "input_common.h" +#include "output.h" #include "path.h" #include "proc.h" #include "reader.h" @@ -46,6 +55,8 @@ /// Value denoting a null string. #define ENV_NULL L"\x1d" +#define DEFAULT_TERM1 "ansi" +#define DEFAULT_TERM2 "dumb" /// Some configuration path environment variables. #define FISH_DATADIR_VAR L"__fish_datadir" @@ -62,20 +73,34 @@ extern char **environ; size_t read_byte_limit = READ_BYTE_LIMIT; bool g_use_posix_spawn = false; // will usually be set to true +bool curses_initialized = false; /// Does the terminal have the "eat_newline_glitch". bool term_has_xn = false; -/// List of all locale environment variable names. +/// This is used to ensure that we don't perform any callbacks from `react_to_variable_change()` +/// when we're importing environment variables in `env_init()`. That's because we don't have any +/// control over the order in which the vars are imported and some of them work in combination. +/// For example, `TERMINFO_DIRS` and `TERM`. If the user has set `TERM` to a custom value that is +/// found in `TERMINFO_DIRS` we don't to call `handle_curses()` before we've imported the latter. +static bool env_initialized = false; + +/// List of all locale environment variable names that might trigger (re)initializing the locale +/// subsystem. static const wchar_t *const locale_variable[] = { L"LANG", L"LANGUAGE", L"LC_ALL", L"LC_ADDRESS", L"LC_COLLATE", L"LC_CTYPE", L"LC_IDENTIFICATION", L"LC_MEASUREMENT", L"LC_MESSAGES", L"LC_MONETARY", L"LC_NAME", L"LC_NUMERIC", L"LC_PAPER", L"LC_TELEPHONE", L"LC_TIME", NULL}; -/// List of all curses environment variable names. +/// List of all curses environment variable names that might trigger (re)initializing the curses +/// subsystem. static const wchar_t *const curses_variable[] = {L"TERM", L"TERMINFO", L"TERMINFO_DIRS", NULL}; +// Some forward declarations to make it easy to logically group the code. +static void init_locale(); +static void init_curses(); + // Struct representing one level in the function variable stack. // Only our variable stack should create and destroy these class env_node_t { @@ -105,7 +130,6 @@ class variable_entry_t { static pthread_mutex_t env_lock = PTHREAD_MUTEX_INITIALIZER; static bool local_scope_exports(const env_node_t *n); -static void handle_locale(const wchar_t *env_var_name); // A class wrapping up a variable stack // Currently there is only one variable stack in fish, @@ -136,6 +160,8 @@ struct var_stack_t { // Pops the top node if it's not global void pop(); + bool var_changed(wchar_t const *const vars[]); + // Returns the next scope to search for a given node, respecting the new_scope lag // Returns NULL if we're done env_node_t *next_scope_to_search(env_node_t *node); @@ -156,23 +182,24 @@ void var_stack_t::push(bool new_scope) { } } +/// Return true if one of the vars in the passed list was changed in the current var scope. +bool var_stack_t::var_changed(wchar_t const *const vars[]) { + for (auto v = vars; *v; v++) { + if (top->env.find(*v) != top->env.end()) return true; + } + return false; +} + void var_stack_t::pop() { - // Don't pop the global - if (this->top.get() == this->global_env) { + // Don't pop the top-most, global, level. + if (top.get() == this->global_env) { debug(0, _(L"Tried to pop empty environment stack.")); sanity_lose(); return; } - const wchar_t *locale_changed = NULL; - - for (int i = 0; locale_variable[i]; i++) { - var_table_t::iterator result = top->env.find(locale_variable[i]); - if (result != top->env.end()) { - locale_changed = locale_variable[i]; - break; - } - } + bool locale_changed = this->var_changed(locale_variable); + bool curses_changed = this->var_changed(curses_variable); if (top->new_scope) { //!OCLINT(collapsible if statements) if (top->exportv || local_scope_exports(top->next.get())) { @@ -196,8 +223,9 @@ void var_stack_t::pop() { break; } } - // TODO: Move this to something general. - if (locale_changed) handle_locale(locale_changed); + + if (locale_changed) init_locale(); + if (curses_changed) init_curses(); } const env_node_t *var_stack_t::next_scope_to_search(const env_node_t *node) const { @@ -303,25 +331,27 @@ static bool var_is_locale(const wcstring &key) { return false; } -/// Properly sets all locale information. -static void handle_locale(const wchar_t *env_var_name) { - debug(2, L"handle_locale() called in response to '%ls' changing", env_var_name); +/// Initialize the locale subsystem. +static void init_locale() { // We have to make a copy because the subsequent setlocale() call to change the locale will // invalidate the pointer from the this setlocale() call. char *old_msg_locale = strdup(setlocale(LC_MESSAGES, NULL)); - const env_var_t val = env_get_string(env_var_name, ENV_EXPORT); - const std::string &value = wcs2string(val); - const std::string &name = wcs2string(env_var_name); - debug(2, L"locale var %s='%s'", name.c_str(), value.c_str()); - if (val.empty()) { - unsetenv(name.c_str()); - } else { - setenv(name.c_str(), value.c_str(), 1); + + for (const wchar_t *const *var_name = locale_variable; *var_name; var_name++) { + const env_var_t val = env_get_string(*var_name, ENV_EXPORT); + const std::string &name = wcs2string(*var_name); + const std::string &value = wcs2string(val); + debug(2, L"locale var %s='%s'", name.c_str(), value.c_str()); + if (val.empty()) { + unsetenv(name.c_str()); + } else { + setenv(name.c_str(), value.c_str(), 1); + } } char *locale = setlocale(LC_ALL, ""); fish_setlocale(); - debug(2, L"handle_locale() setlocale(): '%s'", locale); + debug(2, L"init_locale() setlocale(): '%s'", locale); const char *new_msg_locale = setlocale(LC_MESSAGES, NULL); debug(3, L"old LC_MESSAGES locale: '%s'", old_msg_locale); @@ -380,39 +410,130 @@ static bool does_term_support_setting_title() { return true; } -/// Handle changes to the TERM env var that do not involves the curses subsystem. -static void handle_term() { can_set_term_title = does_term_support_setting_title(); } - -/// Push all curses/terminfo env vars into the global environment where they can be found by those -/// libraries. -static void handle_curses(const wchar_t *env_var_name) { - debug(2, L"handle_curses() called in response to '%ls' changing", env_var_name); - const env_var_t val = env_get_string(env_var_name, ENV_EXPORT); - const std::string &name = wcs2string(env_var_name); - const std::string &value = wcs2string(val); - debug(2, L"curses var %s='%s'", name.c_str(), value.c_str()); - if (val.empty()) { - unsetenv(name.c_str()); +/// Updates our idea of whether we support term256 and term24bit (see issue #10222). +static void update_fish_color_support() { + // Detect or infer term256 support. If fish_term256 is set, we respect it; + // otherwise infer it from the TERM variable or use terminfo. + env_var_t fish_term256 = env_get_string(L"fish_term256"); + env_var_t term = env_get_string(L"TERM"); + bool support_term256 = false; // default to no support + if (!fish_term256.missing_or_empty()) { + support_term256 = from_string(fish_term256); + debug(2, L"256 color support determined by 'fish_term256'"); + } else if (term.find(L"256color") != wcstring::npos) { + // TERM=*256color*: Explicitly supported. + support_term256 = true; + debug(2, L"256 color support enabled for '256color' in TERM"); + } else if (term.find(L"xterm") != wcstring::npos) { + // Assume that all xterms are 256, except for OS X SnowLeopard + const env_var_t prog = env_get_string(L"TERM_PROGRAM"); + const env_var_t progver = env_get_string(L"TERM_PROGRAM_VERSION"); + if (prog == L"Apple_Terminal" && !progver.missing_or_empty()) { + // OS X Lion is version 300+, it has 256 color support + if (strtod(wcs2str(progver), NULL) > 300) { + support_term256 = true; + debug(2, L"256 color support enabled for TERM=xterm + modern Terminal.app"); + } + } else { + support_term256 = true; + debug(2, L"256 color support enabled for TERM=xterm"); + } + } else if (cur_term != NULL) { + // See if terminfo happens to identify 256 colors + support_term256 = (max_colors >= 256); + debug(2, L"256 color support: using %d colors per terminfo", max_colors); } else { - setenv(name.c_str(), value.c_str(), 1); + debug(2, L"256 color support not enabled (yet)"); } - // TODO: Modify input_init() to allow calling it when the terminfo env vars are dynamically - // changed. At the present time it can be called just once. Also, we should really only do this - // if the TERM var is set. - // input_init(); - term_has_xn = tgetflag((char *)"xn") == 1; // does terminal have the eat_newline_glitch + env_var_t fish_term24bit = env_get_string(L"fish_term24bit"); + bool support_term24bit; + if (!fish_term24bit.missing_or_empty()) { + support_term24bit = from_string(fish_term24bit); + debug(2, L"'fish_term24bit' preference: 24-bit color %s", + support_term24bit ? L"enabled" : L"disabled"); + } else { + // We don't attempt to infer term24 bit support yet. + support_term24bit = false; + } + + color_support_t support = (support_term256 ? color_support_term256 : 0) | + (support_term24bit ? color_support_term24bit : 0); + output_set_color_support(support); +} + +// Try to initialize the terminfo/curses subsystem using our fallback terminal name. Do not set +// `TERM` to our fallback. We're only doing this in the hope of getting a minimally functional +// shell. If we launch an external command that uses TERM it should get the same value we were +// given, if any. +static bool initialize_curses_using_fallback(const char *term) { + // If $TERM is already set to the fallback name we're about to use there isn't any point in + // seeing if the fallback name can be used. + const char *term_env = wcs2str(env_get_string(L"TERM")); + if (!strcmp(term_env, DEFAULT_TERM1) || !strcmp(term_env, DEFAULT_TERM2)) return false; + + if (is_interactive_session) { + debug(1, _(L"Using fallback terminal type '%s'."), term); + } + int err_ret; + if (setupterm((char *)term, STDOUT_FILENO, &err_ret) == OK) return true; + if (is_interactive_session) { + debug(1, _(L"Could not set up terminal using the fallback terminal type '%s'."), term); + } + return false; +} + +/// Initialize the curses subsystem. +static void init_curses() { + for (const wchar_t *const *var_name = curses_variable; *var_name; var_name++) { + const env_var_t val = env_get_string(*var_name, ENV_EXPORT); + const std::string &name = wcs2string(*var_name); + const std::string &value = wcs2string(val); + debug(2, L"curses var %s='%s'", name.c_str(), value.c_str()); + if (val.empty()) { + unsetenv(name.c_str()); + } else { + setenv(name.c_str(), value.c_str(), 1); + } + } + + int err_ret; + if (setupterm(NULL, STDOUT_FILENO, &err_ret) == ERR) { + env_var_t term = env_get_string(L"TERM"); + if (is_interactive_session) { + debug(1, _(L"Could not set up terminal.")); + if (term.missing_or_empty()) { + debug(1, _(L"TERM environment variable not set.")); + } else { + debug(1, _(L"TERM environment variable set to '%ls'."), term.c_str()); + debug(1, _(L"Check that this terminal type is supported on this system.")); + } + } + + if (!initialize_curses_using_fallback(DEFAULT_TERM1)) { + initialize_curses_using_fallback(DEFAULT_TERM2); + } + } + + can_set_term_title = does_term_support_setting_title(); + term_has_xn = tigetflag((char *)"xenl") == 1; // does terminal have the eat_newline_glitch + update_fish_color_support(); // Invalidate the cached escape sequences since they may no longer be valid. cached_esc_sequences.clear(); + curses_initialized = true; } /// React to modifying the given variable. static void react_to_variable_change(const wcstring &key) { + // Don't do any of this until `env_init()` has run. We only want to do this in response to + // variables set by the user; e.g., in a script like *config.fish* or interactively. + if (!env_initialized) return; + if (var_is_locale(key)) { - handle_locale(key.c_str()); + init_locale(); } else if (var_is_curses(key)) { - handle_curses(key.c_str()); - if (key == L"TERM") handle_term(); + init_curses(); + init_input(); } else if (var_is_timezone(key)) { handle_timezone(key.c_str()); } else if (key == L"fish_term256" || key == L"fish_term24bit") { @@ -468,7 +589,7 @@ static void setup_path() { } } -/// Initialize the `COLUMNS` and `LINES` env vars if they don't already exist to reasonable +/// If they don't already exist initialize the `COLUMNS` and `LINES` env vars to reasonable /// defaults. They will be updated later by the `get_current_winsize()` function if they need to be /// adjusted. static void env_set_termsize() { @@ -533,6 +654,42 @@ static void setup_user(bool force) { } } +/// Various things we need to initialize at run-time that don't really fit any of the other init +/// routines. +void misc_init() { + env_set_read_limit(); + + // If stdout is open on a tty ensure stdio is unbuffered. That's because those functions might + // be intermixed with `write()` calls and we need to ensure the writes are not reordered. See + // issue #3748. + if (isatty(STDOUT_FILENO)) { + fflush(stdout); + setvbuf(stdout, NULL, _IONBF, 0); + } + +#ifdef OS_IS_CYGWIN + // MS Windows tty devices do not currently have either a read or write timestamp. Those + // respective fields of `struct stat` are always the current time. Which means we can't + // use them. So we assume no external program has written to the terminal behind our + // back. This makes multiline promptusable. See issue #2859 and + // https://github.com/Microsoft/BashOnWindows/issues/545 + has_working_tty_timestamps = false; +#else + // This covers preview builds of Windows Subsystem for Linux (WSL). + FILE *procsyskosrel; + if ((procsyskosrel = wfopen(L"/proc/sys/kernel/osrelease", "r"))) { + wcstring osrelease; + fgetws2(&osrelease, procsyskosrel); + if (osrelease.find(L"3.4.0-Microsoft") != wcstring::npos) { + has_working_tty_timestamps = false; + } + } + if (procsyskosrel) { + fclose(procsyskosrel); + } +#endif // OS_IS_MS_WINDOWS +} + void env_init(const struct config_paths_t *paths /* or NULL */) { // These variables can not be altered directly by the user. const wchar_t *const ro_keys[] = { @@ -585,6 +742,10 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { env_set(FISH_BIN_DIR, paths->bin.c_str(), ENV_GLOBAL); } + init_locale(); + init_curses(); + init_input(); + // Set up the USER and PATH variables setup_path(); setup_user(false); @@ -647,6 +808,7 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { // scope will persist throughout the lifetime of the fish process, and it will ensure that `set // -l` commands run at the command-line don't affect the global scope. env_push(false); + env_initialized = true; } /// Search all visible scopes in order for the specified key. Return the first scope in which it was diff --git a/src/env.h b/src/env.h index ca8be6462..464f8973b 100644 --- a/src/env.h +++ b/src/env.h @@ -12,6 +12,7 @@ #include "common.h" extern size_t read_byte_limit; +extern bool curses_initialized; // Flags that may be passed as the 'mode' in env_set / env_get_string. enum { @@ -54,6 +55,10 @@ struct config_paths_t { /// Initialize environment variable data. void env_init(const struct config_paths_t *paths = NULL); +/// Various things we need to initialize at run-time that don't really fit any of the other init +/// routines. +void misc_init(); + int env_set(const wcstring &key, const wchar_t *val, env_mode_flags_t mode); class env_var_t : public wcstring { diff --git a/src/fish.cpp b/src/fish.cpp index eeafa5591..3df48c738 100644 --- a/src/fish.cpp +++ b/src/fish.cpp @@ -44,7 +44,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA #include "fish_version.h" #include "function.h" #include "history.h" -#include "input.h" #include "io.h" #include "parser.h" #include "path.h" @@ -318,42 +317,6 @@ static int fish_parse_opt(int argc, char **argv, std::vector *cmds) return optind; } -/// Various things we need to initialize at run-time that don't really fit any of the other init -/// routines. -static void misc_init() { - env_set_read_limit(); - - // If stdout is open on a tty ensure stdio is unbuffered. That's because those functions might - // be intermixed with `write()` calls and we need to ensure the writes are not reordered. See - // issue #3748. - if (isatty(STDOUT_FILENO)) { - fflush(stdout); - setvbuf(stdout, NULL, _IONBF, 0); - } - -#ifdef OS_IS_CYGWIN - // MS Windows tty devices do not currently have either a read or write timestamp. Those - // respective fields of `struct stat` are always the current time. Which means we can't - // use them. So we assume no external program has written to the terminal behind our - // back. This makes multiline promptusable. See issue #2859 and - // https://github.com/Microsoft/BashOnWindows/issues/545 - has_working_tty_timestamps = false; -#else - // This covers preview builds of Windows Subsystem for Linux (WSL). - FILE *procsyskosrel; - if ((procsyskosrel = wfopen(L"/proc/sys/kernel/osrelease", "r"))) { - wcstring osrelease; - fgetws2(&osrelease, procsyskosrel); - if (osrelease.find(L"3.4.0-Microsoft") != wcstring::npos) { - has_working_tty_timestamps = false; - } - } - if (procsyskosrel) { - fclose(procsyskosrel); - } -#endif // OS_IS_MS_WINDOWS -} - int main(int argc, char **argv) { int res = 1; int my_optind = 0; @@ -389,17 +352,14 @@ int main(int argc, char **argv) { } const struct config_paths_t paths = determine_config_directory_paths(argv[0]); - + env_init(&paths); proc_init(); event_init(); builtin_init(); function_init(); - env_init(&paths); + misc_init(); reader_init(); history_init(); - // For set_color to support term256 in config.fish (issue #1022). - update_fish_color_support(); - misc_init(); parser_t &parser = parser_t::principal_parser(); diff --git a/src/fish_indent.cpp b/src/fish_indent.cpp index e4e6b867f..521e19948 100644 --- a/src/fish_indent.cpp +++ b/src/fish_indent.cpp @@ -36,7 +36,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA #include "env.h" #include "fish_version.h" #include "highlight.h" -#include "input.h" #include "output.h" #include "parse_constants.h" #include "parse_tree.h" @@ -347,7 +346,6 @@ int main(int argc, char *argv[]) { // (e.g., "# -*- coding: -*-"). setlocale(LC_ALL, ""); env_init(); - input_init(); // Types of output we support. enum { diff --git a/src/fish_key_reader.cpp b/src/fish_key_reader.cpp index 69819ffc1..dbd5daa1c 100644 --- a/src/fish_key_reader.cpp +++ b/src/fish_key_reader.cpp @@ -294,7 +294,6 @@ static void setup_and_process_keys(bool continuous_mode) { setup_fork_guards(); env_init(); reader_init(); - input_init(); proc_push_interactive(1); install_our_signal_handlers(); diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index b5705aea7..7a529c856 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -921,7 +921,7 @@ static void test_utf82wchar(const char *src, size_t slen, const wchar_t *dst, si size = utf8_to_wchar(src, slen, NULL, flags); } else { mem = (wchar_t *)malloc(dlen * sizeof(*mem)); - if (mem == NULL) { + if (!mem) { err(L"u2w: %s: MALLOC FAILED\n", descr); return; } @@ -969,7 +969,7 @@ static void test_wchar2utf8(const wchar_t *src, size_t slen, const char *dst, si if (dst) { mem = (char *)malloc(dlen); - if (mem == NULL) { + if (!mem) { err(L"w2u: %s: MALLOC FAILED", descr); return; } @@ -1673,8 +1673,13 @@ struct pager_layout_testcase_t { wcstring text = sd.line(0).to_string(); if (text != expected) { - fwprintf(stderr, L"width %zu got <%ls>, expected <%ls>\n", this->width, - text.c_str(), expected.c_str()); + fwprintf(stderr, L"width %zu got %d<%ls>, expected %d<%ls>\n", this->width, + text.length(), text.c_str(), expected.length(), expected.c_str()); + for (size_t i = 0; i < std::max(text.length(), expected.length()); i++) { + fwprintf(stderr, L"i %zu got <%lx> expected <%lx>\n", i, + i >= text.length() ? 0xffff : text[i], + i >= expected.length() ? 0xffff : expected[i]); + } } do_test(text == expected); } @@ -4178,7 +4183,7 @@ int main(int argc, char **argv) { function_init(); builtin_init(); env_init(); - + misc_init(); reader_init(); // Set default signal handlers, so we can ctrl-C out of this. diff --git a/src/input.cpp b/src/input.cpp index 17b5c6dcc..73f70b79d 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -2,19 +2,9 @@ #include "config.h" #include -#include -#include #include -#include #include #include -#if HAVE_NCURSES_H -#include -#elif HAVE_NCURSES_CURSES_H -#include -#else -#include -#endif #if HAVE_TERM_H #include #elif HAVE_NCURSES_TERM_H @@ -33,14 +23,12 @@ #include "input.h" #include "input_common.h" #include "io.h" -#include "output.h" #include "parser.h" #include "proc.h" #include "reader.h" #include "signal.h" // IWYU pragma: keep #include "wutil.h" // IWYU pragma: keep -#define DEFAULT_TERM L"ansi" #define MAX_INPUT_FUNCTION_ARGS 20 /// Struct representing a keybinding. Returned by input_get_mappings. @@ -201,11 +189,11 @@ static std::vector terminfo_mappings; /// List of all terminfo mappings. static std::vector mappings; -/// Set to one when the input subsytem has been initialized. -static bool is_init = false; +/// Set to true when the input subsytem has been initialized. +bool input_initialized = false; /// Initialize terminfo. -static void input_terminfo_init(); +static void init_input_terminfo(); static wchar_t input_function_args[MAX_INPUT_FUNCTION_ARGS]; static bool input_function_status; @@ -300,87 +288,11 @@ static int interrupt_handler() { return R_NULL; } -void update_fish_color_support(void) { - // Detect or infer term256 support. If fish_term256 is set, we respect it; - // otherwise infer it from the TERM variable or use terminfo. - env_var_t fish_term256 = env_get_string(L"fish_term256"); - env_var_t term = env_get_string(L"TERM"); - bool support_term256 = false; // default to no support - if (!fish_term256.missing_or_empty()) { - support_term256 = from_string(fish_term256); - debug(2, L"256 color support determined by 'fish_term256'"); - } else if (term.find(L"256color") != wcstring::npos) { - // TERM=*256color*: Explicitly supported. - support_term256 = true; - debug(2, L"256 color support enabled for '256color' in TERM"); - } else if (term.find(L"xterm") != wcstring::npos) { - // Assume that all xterms are 256, except for OS X SnowLeopard - const env_var_t prog = env_get_string(L"TERM_PROGRAM"); - const env_var_t progver = env_get_string(L"TERM_PROGRAM_VERSION"); - if (prog == L"Apple_Terminal" && !progver.missing_or_empty()) { - // OS X Lion is version 300+, it has 256 color support - if (strtod(wcs2str(progver), NULL) > 300) { - support_term256 = true; - debug(2, L"256 color support enabled for TERM=xterm + modern Terminal.app"); - } - } else { - support_term256 = true; - debug(2, L"256 color support enabled for TERM=xterm"); - } - } else if (cur_term != NULL) { - // See if terminfo happens to identify 256 colors - support_term256 = (max_colors >= 256); - debug(2, L"256 color support: using %d colors per terminfo", max_colors); - } else { - debug(2, L"256 color support not enabled (yet)"); - } - - env_var_t fish_term24bit = env_get_string(L"fish_term24bit"); - bool support_term24bit; - if (!fish_term24bit.missing_or_empty()) { - support_term24bit = from_string(fish_term24bit); - debug(2, L"'fish_term24bit' preference: 24-bit color %s", - support_term24bit ? L"enabled" : L"disabled"); - } else { - // We don't attempt to infer term24 bit support yet. - support_term24bit = false; - } - - color_support_t support = (support_term256 ? color_support_term256 : 0) | - (support_term24bit ? color_support_term24bit : 0); - output_set_color_support(support); -} - -int input_init() { - if (is_init) return 1; - is_init = true; +/// Set up arrays used by readch to detect escape sequences for special keys and perform related +/// initializations for our input subsystem. +void init_input() { input_common_init(&interrupt_handler); - - int err_ret; - if (setupterm(NULL, STDOUT_FILENO, &err_ret) == ERR) { - debug(0, _(L"Could not set up terminal")); - env_var_t term = env_get_string(L"TERM"); - if (term.missing_or_empty()) { - debug(0, _(L"TERM environment variable not set")); - } else { - debug(0, _(L"TERM environment variable set to '%ls'"), term.c_str()); - debug(0, _(L"Check that this terminal type is supported on this system")); - } - - env_set(L"TERM", DEFAULT_TERM, ENV_GLOBAL | ENV_EXPORT); - if (setupterm(NULL, STDOUT_FILENO, &err_ret) == ERR) { - debug(0, - _(L"Could not set up terminal using the fallback terminal type '%ls' - exiting"), - DEFAULT_TERM); - exit_without_destructors(1); - } else { - debug(0, _(L"Using fallback terminal type '%ls'"), DEFAULT_TERM); - } - fputwc(L'\n', stderr); - } - - input_terminfo_init(); - update_fish_color_support(); + init_input_terminfo(); // If we have no keybindings, add a few simple defaults. if (mapping_list.empty()) { @@ -393,17 +305,14 @@ int input_init() { input_mapping_add(L"\x5", L"bind"); } - return 1; + input_initialized = true; + return; } void input_destroy() { - if (!is_init) return; - is_init = false; + if (!input_initialized) return; + input_initialized = false; input_common_destroy(); - - if (del_curterm(cur_term) == ERR) { - debug(0, _(L"Error while closing terminfo")); - } } void input_function_push_arg(wchar_t arg) { @@ -678,8 +587,10 @@ bool input_mapping_get(const wcstring &sequence, const wcstring &mode, wcstring_ return result; } -/// Add all terminfo mappings. -static void input_terminfo_init() { +/// Add all terminfo mappings and cache other terminfo facts we care about. +static void init_input_terminfo() { + assert(curses_initialized); + if (!cur_term) return; // setupterm() failed so we can't referency any key definitions const terminfo_mapping_t tinfos[] = { TERMINFO_ADD(key_a1), TERMINFO_ADD(key_a3), @@ -843,13 +754,12 @@ static void input_terminfo_init() { bool input_terminfo_get_sequence(const wchar_t *name, wcstring *out_seq) { ASSERT_IS_MAIN_THREAD(); + assert(input_initialized); + CHECK(name, 0); const char *res = 0; int err = ENOENT; - CHECK(name, 0); - input_init(); - for (size_t i = 0; i < terminfo_mappings.size(); i++) { const terminfo_mapping_t &m = terminfo_mappings.at(i); if (!wcscmp(name, m.name)) { @@ -869,7 +779,7 @@ bool input_terminfo_get_sequence(const wchar_t *name, wcstring *out_seq) { } bool input_terminfo_get_name(const wcstring &seq, wcstring *out_name) { - input_init(); + assert(input_initialized); for (size_t i = 0; i < terminfo_mappings.size(); i++) { terminfo_mapping_t &m = terminfo_mappings.at(i); @@ -889,11 +799,10 @@ bool input_terminfo_get_name(const wcstring &seq, wcstring *out_name) { } wcstring_list_t input_terminfo_get_names(bool skip_null) { + assert(input_initialized); wcstring_list_t result; result.reserve(terminfo_mappings.size()); - input_init(); - for (size_t i = 0; i < terminfo_mappings.size(); i++) { terminfo_mapping_t &m = terminfo_mappings.at(i); diff --git a/src/input.h b/src/input.h index 403dabf4c..99a78d80d 100644 --- a/src/input.h +++ b/src/input.h @@ -15,11 +15,12 @@ wcstring describe_char(wint_t c); -/// Initialize the terminal by calling setupterm, and set up arrays used by readch to detect escape -/// sequences for special keys. -/// -/// Before calling input_init, terminfo is not initialized and MUST not be used. -int input_init(); +/// Set to true when the input subsytem has been initialized. +extern bool input_initialized; + +/// Set up arrays used by readch to detect escape sequences for special keys and perform related +/// initializations for our input subsystem. +void init_input(); /// free up memory used by terminal functions. void input_destroy(); @@ -101,7 +102,4 @@ wchar_t input_function_get_code(const wcstring &name); /// Returns a list of all existing input function names. wcstring_list_t input_function_get_names(void); -/// Updates our idea of whether we support term256 and term24bit. -void update_fish_color_support(); - #endif diff --git a/src/output.cpp b/src/output.cpp index 92b2355bb..cb7a0aa46 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -96,6 +96,7 @@ static bool write_color_escape(char *todo, unsigned char idx, bool is_fg) { } static bool write_foreground_color(unsigned char idx) { + if (!cur_term) return false; if (set_a_foreground && set_a_foreground[0]) { return write_color_escape(set_a_foreground, idx, true); } else if (set_foreground && set_foreground[0]) { @@ -105,6 +106,7 @@ static bool write_foreground_color(unsigned char idx) { } static bool write_background_color(unsigned char idx) { + if (!cur_term) return false; if (set_a_background && set_a_background[0]) { return write_color_escape(set_a_background, idx, false); } else if (set_background && set_background[0]) { @@ -115,6 +117,7 @@ static bool write_background_color(unsigned char idx) { // Exported for builtin_set_color's usage only. bool write_color(rgb_color_t color, bool is_fg) { + if (!cur_term) return false; bool supports_term24bit = static_cast(output_get_color_support() & color_support_term24bit); if (!supports_term24bit || !color.is_rgb()) { @@ -168,6 +171,7 @@ void set_color(rgb_color_t c, rgb_color_t c2) { debug(3, "set_color %ls : %ls\n", tmp.c_str(), tmp2.c_str()); #endif ASSERT_IS_MAIN_THREAD(); + if (!cur_term) return; const rgb_color_t normal = rgb_color_t::normal(); static rgb_color_t last_color = rgb_color_t::normal(); diff --git a/src/reader.cpp b/src/reader.cpp index e24e36d24..93624b8da 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -1539,10 +1539,10 @@ static bool check_for_orphaned_process(unsigned long loop_count, pid_t shell_pgi /// Initialize data for interactive use. static void reader_interactive_init() { + assert(input_initialized); // See if we are running interactively. pid_t shell_pgid; - input_init(); kill_init(); shell_pgid = getpgrp(); diff --git a/src/screen.cpp b/src/screen.cpp index cad973d4b..ff37d27be 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -7,6 +7,7 @@ // IWYU pragma: no_include #include "config.h" +#include #include #include #include @@ -552,7 +553,7 @@ static void s_move(screen_t *s, data_buffer_t *b, int new_x, int new_y) { // Note that this is required to avoid some visual glitches in iTerm (issue #1448). bool use_multi = multi_str != NULL && multi_str[0] != '\0' && abs(x_steps) * strlen(str) > strlen(multi_str); - if (use_multi) { + if (use_multi && cur_term) { char *multi_param = tparm(multi_str, abs(x_steps)); writembs(multi_param); } else { @@ -900,7 +901,10 @@ static void s_update(screen_t *scr, const wchar_t *left_prompt, const wchar_t *r } /// Returns true if we are using a dumb terminal. -static bool is_dumb(void) { return !cursor_up || !cursor_down || !cursor_left || !cursor_right; } +static bool is_dumb(void) { + if (!cur_term) return true; + return !cursor_up || !cursor_down || !cursor_left || !cursor_right; +} struct screen_layout_t { // The left prompt that we're going to use. @@ -1232,7 +1236,7 @@ void s_reset(screen_t *s, screen_reset_mode_t mode) { // We do `>` rather than `>=` because the code below might require one extra space. if (screen_width > non_space_width) { bool justgrey = true; - if (enter_dim_mode) { + if (cur_term && enter_dim_mode) { std::string dim = tparm(enter_dim_mode); if (!dim.empty()) { // Use dim if they have it, so the color will be based on their actual normal @@ -1241,7 +1245,7 @@ void s_reset(screen_t *s, screen_reset_mode_t mode) { justgrey = false; } } - if (justgrey && set_a_foreground) { + if (cur_term && justgrey && set_a_foreground) { if (max_colors >= 238) { // draw the string in a particular grey abandon_line_string.append(str2wcstring(tparm(set_a_foreground, 237))); @@ -1254,15 +1258,18 @@ void s_reset(screen_t *s, screen_reset_mode_t mode) { abandon_line_string.append(str2wcstring(tparm(set_a_foreground, 0))); } } + abandon_line_string.push_back(omitted_newline_char); - if (exit_attribute_mode) { + if (cur_term && exit_attribute_mode) { abandon_line_string.append( str2wcstring(tparm(exit_attribute_mode))); // normal text ANSI escape sequence } + int newline_glitch_width = term_has_xn ? 0 : 1; abandon_line_string.append(screen_width - non_space_width - newline_glitch_width, L' '); } + abandon_line_string.push_back(L'\r'); abandon_line_string.push_back(omitted_newline_char); // Now we are certainly on a new line. But we may have dropped the omitted newline char on diff --git a/src/screen.h b/src/screen.h index 2c4212fe6..b6fa15943 100644 --- a/src/screen.h +++ b/src/screen.h @@ -14,6 +14,7 @@ #include #include +#include #include #include #include diff --git a/src/signal.cpp b/src/signal.cpp index 8fe2bd2d8..8cf5d01a3 100644 --- a/src/signal.cpp +++ b/src/signal.cpp @@ -220,7 +220,7 @@ static void handle_hup(int sig, siginfo_t *info, void *context) { } /// Handle sigterm. The only thing we do is restore the front process ID, then die. -static void handle_term(int sig, siginfo_t *info, void *context) { +static void handle_sigterm(int sig, siginfo_t *info, void *context) { UNUSED(sig); UNUSED(info); UNUSED(context); @@ -286,7 +286,7 @@ static void set_interactive_handlers() { sigaction(SIGINT, &act, 0); // SIGTERM restores the terminal controlling process before dying. - act.sa_sigaction = &handle_term; + act.sa_sigaction = &handle_sigterm; act.sa_flags = SA_SIGINFO; sigaction(SIGTERM, &act, 0);