From b7fceddfc8e79e528620a4948e13d24c33629d63 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 25 Mar 2019 20:18:00 -0700 Subject: [PATCH 01/13] Refactor some environment code into env_dispatch.cpp This new file is supposed to encapsulate all of the logic around reacting to variable changes, as opposed to the environment core. This is to help break up the env.cpp monolith. --- CMakeLists.txt | 2 +- src/env.cpp | 286 ++++------------------------------------ src/env.h | 9 ++ src/env_dispatch.cpp | 308 +++++++++++++++++++++++++++++++++++++++++++ src/env_dispatch.h | 24 ++++ 5 files changed, 364 insertions(+), 265 deletions(-) create mode 100644 src/env_dispatch.cpp create mode 100644 src/env_dispatch.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d7250acd3..cfd6be904 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,7 +78,7 @@ SET(FISH_SRCS src/builtin_return.cpp src/builtin_set.cpp src/builtin_set_color.cpp src/builtin_source.cpp src/builtin_status.cpp src/builtin_string.cpp src/builtin_test.cpp src/builtin_ulimit.cpp src/builtin_wait.cpp - src/color.cpp src/common.cpp src/complete.cpp src/env.cpp + src/color.cpp src/common.cpp src/complete.cpp src/env.cpp src/env_dispatch.cpp src/env_universal_common.cpp src/event.cpp src/exec.cpp src/expand.cpp src/fallback.cpp src/fish_version.cpp src/function.cpp src/highlight.cpp src/history.cpp src/input.cpp src/input_common.cpp src/intern.cpp src/io.cpp diff --git a/src/env.cpp b/src/env.cpp index 268234221..7a81795c4 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -40,8 +39,8 @@ #include "builtin_bind.h" #include "common.h" -#include "complete.h" #include "env.h" +#include "env_dispatch.h" #include "env_universal_common.h" #include "event.h" #include "expand.h" @@ -88,34 +87,6 @@ bool curses_initialized = false; /// Does the terminal have the "eat_newline_glitch". bool term_has_xn = false; -/// 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 wcstring_list_t locale_variables({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"}); - -/// List of all curses environment variable names that might trigger (re)initializing the curses -/// subsystem. -static const wcstring_list_t curses_variables({L"TERM", L"TERMINFO", L"TERMINFO_DIRS"}); - -typedef std::unordered_map - var_dispatch_table_t; -static var_dispatch_table_t create_var_dispatch_table(); -static const var_dispatch_table_t s_var_dispatch_table = create_var_dispatch_table(); - -// Some forward declarations to make it easy to logically group the code. -static void init_locale(const environment_t &vars); -static void init_curses(const environment_t &vars); - // Struct representing one level in the function variable stack. // Only our variable stack should create and destroy these class env_node_t { @@ -317,6 +288,19 @@ static bool is_electric(const wcstring &key) { return contains(env_electric, key env_stack_t::env_stack_t() : vars_(make_unique()) {} env_stack_t::env_stack_t(std::unique_ptr vars) : vars_(std::move(vars)) {} +void env_stack_t::universal_barrier() { + ASSERT_IS_MAIN_THREAD(); + if (!uvars()) return; + + callback_data_list_t callbacks; + bool changed = uvars()->sync(callbacks); + if (changed) { + universal_notifier_t::default_notifier().post_notification(); + } + + env_universal_callbacks(this, callbacks); +} + // Get the variable stack var_stack_t &env_stack_t::vars_stack() { return *vars_; } @@ -336,25 +320,11 @@ static mode_t get_umask() { return res; } -/// Properly sets all timezone information. -static void handle_timezone(const wchar_t *env_var_name, const environment_t &vars) { - const auto var = vars.get(env_var_name, ENV_DEFAULT); - debug(2, L"handle_timezone() current timezone var: |%ls| => |%ls|", env_var_name, - !var ? L"MISSING" : var->as_string().c_str()); - const std::string &name = wcs2string(env_var_name); - if (var.missing_or_empty()) { - unsetenv(name.c_str()); - } else { - const std::string value = wcs2string(var->as_string()); - setenv(name.c_str(), value.c_str(), 1); - } - tzset(); -} /// Some env vars contain a list of paths where an empty path element is equivalent to ".". /// Unfortunately that convention causes problems for fish scripts. So this function replaces the /// empty path element with an explicit ".". See issue #3914. -static void fix_colon_delimited_var(const wcstring &var_name, env_stack_t &vars) { +void fix_colon_delimited_var(const wcstring &var_name, env_stack_t &vars) { const auto paths = vars.get(var_name); if (paths.missing_or_empty()) return; @@ -373,7 +343,7 @@ static void fix_colon_delimited_var(const wcstring &var_name, env_stack_t &vars) } /// Initialize the locale subsystem. -static void init_locale(const environment_t &vars) { +void init_locale(const environment_t &vars) { // 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)); @@ -450,7 +420,7 @@ static bool does_term_support_setting_title(const environment_t &vars) { } /// Updates our idea of whether we support term256 and term24bit (see issue #10222). -static void update_fish_color_support(const environment_t &vars) { +void update_fish_color_support(const environment_t &vars) { // Detect or infer term256 support. If fish_term256 is set, we respect it; // otherwise infer it from the TERM variable or use terminfo. wcstring term; @@ -535,41 +505,8 @@ static void init_path_vars() { fix_colon_delimited_var(L"CDPATH", env_stack_t::globals()); } -/// Update the value of g_guessed_fish_emoji_width -static void guess_emoji_width() { - wcstring term; - auto &vars = env_stack_t::globals(); - if (auto term_var = vars.get(L"TERM_PROGRAM")) { - term = term_var->as_string(); - } - - double version = 0; - if (auto version_var = vars.get(L"TERM_PROGRAM_VERSION")) { - std::string narrow_version = wcs2string(version_var->as_string()); - version = strtod(narrow_version.c_str(), NULL); - } - - - if (term == L"Apple_Terminal" && version >= 400) { - // Apple Terminal on High Sierra - g_guessed_fish_emoji_width = 2; - debug(2, "default emoji width: 2 for %ls", term.c_str()); - } else if (term == L"iTerm.app") { - // iTerm2 defaults to Unicode 8 sizes. - // See https://gitlab.com/gnachman/iterm2/wikis/unicodeversionswitching - g_guessed_fish_emoji_width = 1; - debug(2, "default emoji width: 1"); - } else { - // Default to whatever system wcwidth says to U+1F603, - // but only if it's at least 1. - int w = wcwidth(L'😃'); - g_guessed_fish_emoji_width = w > 0 ? w : 1; - debug(2, "default emoji width: %d", g_guessed_fish_emoji_width); - } -} - /// Initialize the curses subsystem. -static void init_curses(const environment_t &vars) { +void init_curses(const environment_t &vars) { for (const auto &var_name : curses_variables) { std::string name = wcs2string(var_name); const auto var = vars.get(var_name, ENV_EXPORT); @@ -609,33 +546,6 @@ static void init_curses(const environment_t &vars) { curses_initialized = true; } -/// React to modifying the given variable. -static void react_to_variable_change(const wchar_t *op, const wcstring &key, env_stack_t &vars) { - // 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 or as part - // of loading the universal variables for the first time. Variables we import from the - // environment or that are otherwise set by fish before this gets called have to explicitly - // call the appropriate functions to put the value of the var into effect. - if (!env_initialized) return; - - auto dispatch = s_var_dispatch_table.find(key); - if (dispatch != s_var_dispatch_table.end()) { - (*dispatch->second)(op, key, vars); - } else if (string_prefixes_string(L"fish_color_", key)) { - reader_react_to_color_change(); - } -} - -/// Universal variable callback function. This function makes sure the proper events are triggered -/// when an event occurs. -static void universal_callback(env_stack_t *stack, const callback_data_t &cb) { - const wchar_t *op = cb.is_erase() ? L"ERASE" : L"SET"; - - react_to_variable_change(op, cb.key, *stack); - stack->mark_changed_exported(); - event_fire(event_t::variable(cb.key, {L"VARIABLE", op, cb.key})); -} - /// Make sure the PATH variable contains something. static void setup_path() { auto &vars = env_stack_t::globals(); @@ -733,155 +643,6 @@ void misc_init() { } } -static void env_universal_callbacks(env_stack_t *stack, const callback_data_list_t &callbacks) { - for (const callback_data_t &cb : callbacks) { - universal_callback(stack, cb); - } -} - -void env_stack_t::universal_barrier() { - ASSERT_IS_MAIN_THREAD(); - if (!uvars()) return; - - callback_data_list_t callbacks; - bool changed = uvars()->sync(callbacks); - if (changed) { - universal_notifier_t::default_notifier().post_notification(); - } - - env_universal_callbacks(this, callbacks); -} - -static void handle_fish_term_change(const wcstring &op, const wcstring &var_name, - env_stack_t &vars) { - UNUSED(op); - UNUSED(var_name); - update_fish_color_support(vars); - reader_react_to_color_change(); -} - -static void handle_escape_delay_change(const wcstring &op, const wcstring &var_name, - env_stack_t &vars) { - UNUSED(op); - UNUSED(var_name); - update_wait_on_escape_ms(vars); -} - -static void handle_change_emoji_width(const wcstring &op, const wcstring &var_name, - env_stack_t &vars) { - (void)op; - (void)var_name; - int new_width = 0; - if (auto width_str = vars.get(L"fish_emoji_width")) { - new_width = fish_wcstol(width_str->as_string().c_str()); - } - g_fish_emoji_width = std::max(0, new_width); - debug(2, "'fish_emoji_width' preference: %d, overwriting default", g_fish_emoji_width); -} - -static void handle_change_ambiguous_width(const wcstring &op, const wcstring &var_name, - env_stack_t &vars) { - (void)op; - (void)var_name; - int new_width = 1; - if (auto width_str = vars.get(L"fish_ambiguous_width")) { - new_width = fish_wcstol(width_str->as_string().c_str()); - } - g_fish_ambiguous_width = std::max(0, new_width); -} - -static void handle_term_size_change(const wcstring &op, const wcstring &var_name, - env_stack_t &vars) { - UNUSED(op); - UNUSED(var_name); - UNUSED(vars); - invalidate_termsize(true); // force fish to update its idea of the terminal size plus vars -} - -static void handle_read_limit_change(const wcstring &op, const wcstring &var_name, - env_stack_t &vars) { - UNUSED(op); - UNUSED(var_name); - vars.set_read_limit(); -} - -static void handle_fish_history_change(const wcstring &op, const wcstring &var_name, - env_stack_t &vars) { - UNUSED(op); - UNUSED(var_name); - reader_change_history(history_session_id(vars)); -} - -static void handle_function_path_change(const wcstring &op, const wcstring &var_name, - env_stack_t &vars) { - UNUSED(op); - UNUSED(var_name); - UNUSED(vars); - function_invalidate_path(); -} - -static void handle_complete_path_change(const wcstring &op, const wcstring &var_name, - env_stack_t &vars) { - UNUSED(op); - UNUSED(var_name); - UNUSED(vars); - complete_invalidate_path(); -} - -static void handle_tz_change(const wcstring &op, const wcstring &var_name, env_stack_t &vars) { - UNUSED(op); - handle_timezone(var_name.c_str(), vars); -} - -static void handle_magic_colon_var_change(const wcstring &op, const wcstring &var_name, - env_stack_t &vars) { - UNUSED(op); - fix_colon_delimited_var(var_name, vars); -} - -static void handle_locale_change(const wcstring &op, const wcstring &var_name, env_stack_t &vars) { - UNUSED(op); - UNUSED(var_name); - init_locale(vars); - // We need to re-guess emoji width because the locale might have changed to a multibyte one. - guess_emoji_width(); -} - -static void handle_curses_change(const wcstring &op, const wcstring &var_name, env_stack_t &vars) { - UNUSED(op); - UNUSED(var_name); - guess_emoji_width(); - init_curses(vars); -} - -/// Populate the dispatch table used by `react_to_variable_change()` to efficiently call the -/// appropriate function to handle a change to a variable. -static var_dispatch_table_t create_var_dispatch_table() { - var_dispatch_table_t var_dispatch_table; - for (const auto &var_name : locale_variables) { - var_dispatch_table.emplace(var_name, handle_locale_change); - } - - for (const auto &var_name : curses_variables) { - var_dispatch_table.emplace(var_name, handle_curses_change); - } - - var_dispatch_table.emplace(L"PATH", handle_magic_colon_var_change); - var_dispatch_table.emplace(L"CDPATH", handle_magic_colon_var_change); - var_dispatch_table.emplace(L"fish_term256", handle_fish_term_change); - var_dispatch_table.emplace(L"fish_term24bit", handle_fish_term_change); - var_dispatch_table.emplace(L"fish_escape_delay_ms", handle_escape_delay_change); - var_dispatch_table.emplace(L"fish_emoji_width", handle_change_emoji_width); - var_dispatch_table.emplace(L"fish_ambiguous_width", handle_change_ambiguous_width); - var_dispatch_table.emplace(L"LINES", handle_term_size_change); - var_dispatch_table.emplace(L"COLUMNS", handle_term_size_change); - var_dispatch_table.emplace(L"fish_complete_path", handle_complete_path_change); - var_dispatch_table.emplace(L"fish_function_path", handle_function_path_change); - var_dispatch_table.emplace(L"fish_read_limit", handle_read_limit_change); - var_dispatch_table.emplace(L"fish_history", handle_fish_history_change); - var_dispatch_table.emplace(L"TZ", handle_tz_change); - return var_dispatch_table; -} void env_init(const struct config_paths_t *paths /* or NULL */) { env_stack_t &vars = env_stack_t::globals(); @@ -1027,11 +788,8 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { // Set fish_bind_mode to "default". vars.set_one(FISH_BIND_MODE_VAR, ENV_GLOBAL, DEFAULT_BIND_MODE); - // This is somewhat subtle. At this point we consider our environment to be sufficiently - // initialized that we can react to changes to variables. Prior to doing this we expect that the - // code for setting vars that might have side-effects will do whatever - // `react_to_variable_change()` would do for that var. - env_initialized = true; + // Allow changes to variables to produce events. + env_dispatch_mark_initialization_finished(); // Set up universal variables. The empty string means to use the default path. assert(s_universal_variables == NULL); @@ -1247,7 +1005,7 @@ int env_stack_t::set_internal(const wcstring &key, env_mode_flags_t input_var_mo } event_fire(event_t::variable(key, {L"VARIABLE", L"SET", key})); - react_to_variable_change(L"SET", key, *this); + env_dispatch_var_change(L"SET", key, *this); return ENV_OK; } @@ -1333,7 +1091,7 @@ int env_stack_t::remove(const wcstring &key, int var_mode) { if (is_exported) vars_stack().mark_changed_exported(); } - react_to_variable_change(L"ERASE", key, *this); + env_dispatch_var_change(L"ERASE", key, *this); return erased ? ENV_OK : ENV_NOT_FOUND; } diff --git a/src/env.h b/src/env.h index be26aefa2..8bb725ebf 100644 --- a/src/env.h +++ b/src/env.h @@ -316,4 +316,13 @@ bool term_supports_setting_title(); /// Gets a path appropriate for runtime storage wcstring env_get_runtime_path(); + +/// Replace empty path elements with "." - see #3914. +void fix_colon_delimited_var(const wcstring &var_name, env_stack_t &vars); + +// Temporararily exposed so that env_dispatch can call these. +void init_locale(const environment_t &vars); +void init_curses(const environment_t &vars); +void update_fish_color_support(const environment_t &vars); + #endif diff --git a/src/env_dispatch.cpp b/src/env_dispatch.cpp new file mode 100644 index 000000000..04563a126 --- /dev/null +++ b/src/env_dispatch.cpp @@ -0,0 +1,308 @@ +// Support for dispatching on environment changes. +#include "config.h" // IWYU pragma: keep + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if HAVE_CURSES_H +#include +#elif HAVE_NCURSES_H +#include +#elif HAVE_NCURSES_CURSES_H +#include +#endif +#if HAVE_TERM_H +#include +#elif HAVE_NCURSES_TERM_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "builtin_bind.h" +#include "common.h" +#include "complete.h" +#include "env.h" +#include "env_universal_common.h" +#include "event.h" +#include "expand.h" +#include "fallback.h" // IWYU pragma: keep +#include "fish_version.h" +#include "function.h" +#include "history.h" +#include "input.h" +#include "input_common.h" +#include "output.h" +#include "path.h" +#include "proc.h" +#include "reader.h" +#include "sanity.h" +#include "screen.h" +#include "wutil.h" // IWYU pragma: keep + +#define DEFAULT_TERM1 "ansi" +#define DEFAULT_TERM2 "dumb" + +/// Some configuration path environment variables. +#define FISH_DATADIR_VAR L"__fish_data_dir" +#define FISH_SYSCONFDIR_VAR L"__fish_sysconf_dir" +#define FISH_HELPDIR_VAR L"__fish_help_dir" +#define FISH_BIN_DIR L"__fish_bin_dir" +#define FISH_CONFIG_DIR L"__fish_config_dir" +#define FISH_USER_DATA_DIR L"__fish_user_data_dir" + +/// List of all locale environment variable names that might trigger (re)initializing the locale +/// subsystem. +extern const wcstring_list_t locale_variables({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"}); + +/// List of all curses environment variable names that might trigger (re)initializing the curses +/// subsystem. +extern const wcstring_list_t curses_variables({L"TERM", L"TERMINFO", L"TERMINFO_DIRS"}); + +typedef std::unordered_map + var_dispatch_table_t; +static var_dispatch_table_t create_var_dispatch_table(); +static const var_dispatch_table_t s_var_dispatch_table = create_var_dispatch_table(); + +/// This is used to ensure that we don't perform any callbacks from `env_dispatch_var_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; + +void env_dispatch_mark_initialization_finished() { env_initialized = true; } + +/// Properly sets all timezone information. +static void handle_timezone(const wchar_t *env_var_name, const environment_t &vars) { + const auto var = vars.get(env_var_name, ENV_DEFAULT); + debug(2, L"handle_timezone() current timezone var: |%ls| => |%ls|", env_var_name, + !var ? L"MISSING" : var->as_string().c_str()); + const std::string &name = wcs2string(env_var_name); + if (var.missing_or_empty()) { + unsetenv(name.c_str()); + } else { + const std::string value = wcs2string(var->as_string()); + setenv(name.c_str(), value.c_str(), 1); + } + tzset(); +} + +/// Update the value of g_guessed_fish_emoji_width +void guess_emoji_width() { + wcstring term; + auto &vars = env_stack_t::globals(); + if (auto term_var = vars.get(L"TERM_PROGRAM")) { + term = term_var->as_string(); + } + + double version = 0; + if (auto version_var = vars.get(L"TERM_PROGRAM_VERSION")) { + std::string narrow_version = wcs2string(version_var->as_string()); + version = strtod(narrow_version.c_str(), NULL); + } + + if (term == L"Apple_Terminal" && version >= 400) { + // Apple Terminal on High Sierra + g_guessed_fish_emoji_width = 2; + debug(2, "default emoji width: 2 for %ls", term.c_str()); + } else if (term == L"iTerm.app") { + // iTerm2 defaults to Unicode 8 sizes. + // See https://gitlab.com/gnachman/iterm2/wikis/unicodeversionswitching + g_guessed_fish_emoji_width = 1; + debug(2, "default emoji width: 1"); + } else { + // Default to whatever system wcwidth says to U+1F603, + // but only if it's at least 1. + int w = wcwidth(L'😃'); + g_guessed_fish_emoji_width = w > 0 ? w : 1; + debug(2, "default emoji width: %d", g_guessed_fish_emoji_width); + } +} + +/// React to modifying the given variable. +void env_dispatch_var_change(const wchar_t *op, const wcstring &key, env_stack_t &vars) { + // 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 or as part + // of loading the universal variables for the first time. Variables we import from the + // environment or that are otherwise set by fish before this gets called have to explicitly + // call the appropriate functions to put the value of the var into effect. + if (!env_initialized) return; + + auto dispatch = s_var_dispatch_table.find(key); + if (dispatch != s_var_dispatch_table.end()) { + (*dispatch->second)(op, key, vars); + } else if (string_prefixes_string(L"fish_color_", key)) { + reader_react_to_color_change(); + } +} + +/// Universal variable callback function. This function makes sure the proper events are triggered +/// when an event occurs. +static void universal_callback(env_stack_t *stack, const callback_data_t &cb) { + const wchar_t *op = cb.is_erase() ? L"ERASE" : L"SET"; + + env_dispatch_var_change(op, cb.key, *stack); + stack->mark_changed_exported(); + event_fire(event_t::variable(cb.key, {L"VARIABLE", op, cb.key})); +} + +void env_universal_callbacks(env_stack_t *stack, const callback_data_list_t &callbacks) { + for (const callback_data_t &cb : callbacks) { + universal_callback(stack, cb); + } +} + +static void handle_fish_term_change(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { + UNUSED(op); + UNUSED(var_name); + update_fish_color_support(vars); + reader_react_to_color_change(); +} + +static void handle_escape_delay_change(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { + UNUSED(op); + UNUSED(var_name); + update_wait_on_escape_ms(vars); +} + +static void handle_change_emoji_width(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { + (void)op; + (void)var_name; + int new_width = 0; + if (auto width_str = vars.get(L"fish_emoji_width")) { + new_width = fish_wcstol(width_str->as_string().c_str()); + } + g_fish_emoji_width = std::max(0, new_width); + debug(2, "'fish_emoji_width' preference: %d, overwriting default", g_fish_emoji_width); +} + +static void handle_change_ambiguous_width(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { + (void)op; + (void)var_name; + int new_width = 1; + if (auto width_str = vars.get(L"fish_ambiguous_width")) { + new_width = fish_wcstol(width_str->as_string().c_str()); + } + g_fish_ambiguous_width = std::max(0, new_width); +} + +static void handle_term_size_change(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { + UNUSED(op); + UNUSED(var_name); + UNUSED(vars); + invalidate_termsize(true); // force fish to update its idea of the terminal size plus vars +} + +static void handle_read_limit_change(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { + UNUSED(op); + UNUSED(var_name); + vars.set_read_limit(); +} + +static void handle_fish_history_change(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { + UNUSED(op); + UNUSED(var_name); + reader_change_history(history_session_id(vars)); +} + +static void handle_function_path_change(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { + UNUSED(op); + UNUSED(var_name); + UNUSED(vars); + function_invalidate_path(); +} + +static void handle_complete_path_change(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { + UNUSED(op); + UNUSED(var_name); + UNUSED(vars); + complete_invalidate_path(); +} + +static void handle_tz_change(const wcstring &op, const wcstring &var_name, env_stack_t &vars) { + UNUSED(op); + handle_timezone(var_name.c_str(), vars); +} + +static void handle_magic_colon_var_change(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { + UNUSED(op); + fix_colon_delimited_var(var_name, vars); +} + +static void handle_locale_change(const wcstring &op, const wcstring &var_name, env_stack_t &vars) { + UNUSED(op); + UNUSED(var_name); + init_locale(vars); + // We need to re-guess emoji width because the locale might have changed to a multibyte one. + guess_emoji_width(); +} + +static void handle_curses_change(const wcstring &op, const wcstring &var_name, env_stack_t &vars) { + UNUSED(op); + UNUSED(var_name); + guess_emoji_width(); + init_curses(vars); +} + +/// Populate the dispatch table used by `env_dispatch_var_change()` to efficiently call the +/// appropriate function to handle a change to a variable. +static var_dispatch_table_t create_var_dispatch_table() { + var_dispatch_table_t var_dispatch_table; + for (const auto &var_name : locale_variables) { + var_dispatch_table.emplace(var_name, handle_locale_change); + } + + for (const auto &var_name : curses_variables) { + var_dispatch_table.emplace(var_name, handle_curses_change); + } + + var_dispatch_table.emplace(L"PATH", handle_magic_colon_var_change); + var_dispatch_table.emplace(L"CDPATH", handle_magic_colon_var_change); + var_dispatch_table.emplace(L"fish_term256", handle_fish_term_change); + var_dispatch_table.emplace(L"fish_term24bit", handle_fish_term_change); + var_dispatch_table.emplace(L"fish_escape_delay_ms", handle_escape_delay_change); + var_dispatch_table.emplace(L"fish_emoji_width", handle_change_emoji_width); + var_dispatch_table.emplace(L"fish_ambiguous_width", handle_change_ambiguous_width); + var_dispatch_table.emplace(L"LINES", handle_term_size_change); + var_dispatch_table.emplace(L"COLUMNS", handle_term_size_change); + var_dispatch_table.emplace(L"fish_complete_path", handle_complete_path_change); + var_dispatch_table.emplace(L"fish_function_path", handle_function_path_change); + var_dispatch_table.emplace(L"fish_read_limit", handle_read_limit_change); + var_dispatch_table.emplace(L"fish_history", handle_fish_history_change); + var_dispatch_table.emplace(L"TZ", handle_tz_change); + return var_dispatch_table; +} diff --git a/src/env_dispatch.h b/src/env_dispatch.h new file mode 100644 index 000000000..9f10ff060 --- /dev/null +++ b/src/env_dispatch.h @@ -0,0 +1,24 @@ +// Prototypes for functions that react to environment variable changes +#ifndef FISH_ENV_DISPATCH_H +#define FISH_ENV_DISPATCH_H + +#include "config.h" // IWYU pragma: keep + +#include "common.h" +#include "env_universal_common.h" + +#include + +/// Mark initialization as finished. +void env_dispatch_mark_initialization_finished(); + +class env_stack_t; +void env_dispatch_var_change(const wchar_t *op, const wcstring &key, env_stack_t &vars); +void guess_emoji_width(); + +void env_universal_callbacks(env_stack_t *stack, const callback_data_list_t &callbacks); + +extern const wcstring_list_t locale_variables; +extern const wcstring_list_t curses_variables; + +#endif From b67174b4a3ce61ab7f42fed673c4f06ec35d1afb Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Sun, 31 Mar 2019 19:11:52 -0700 Subject: [PATCH 02/13] Clean up env_dispatch_table --- src/env.cpp | 2 +- src/env_dispatch.cpp | 110 ++++++++++++++++++++++++++++--------------- src/env_dispatch.h | 4 +- 3 files changed, 74 insertions(+), 42 deletions(-) diff --git a/src/env.cpp b/src/env.cpp index 7a81795c4..c0a00fec7 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -789,7 +789,7 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { vars.set_one(FISH_BIND_MODE_VAR, ENV_GLOBAL, DEFAULT_BIND_MODE); // Allow changes to variables to produce events. - env_dispatch_mark_initialization_finished(); + env_dispatch_init(); // Set up universal variables. The empty string means to use the default path. assert(s_universal_variables == NULL); diff --git a/src/env_dispatch.cpp b/src/env_dispatch.cpp index 04563a126..74ca13788 100644 --- a/src/env_dispatch.cpp +++ b/src/env_dispatch.cpp @@ -82,19 +82,53 @@ extern const wcstring_list_t locale_variables({L"LANG", L"LANGUAGE", L"LC_ALL", /// subsystem. extern const wcstring_list_t curses_variables({L"TERM", L"TERMINFO", L"TERMINFO_DIRS"}); -typedef std::unordered_map - var_dispatch_table_t; -static var_dispatch_table_t create_var_dispatch_table(); -static const var_dispatch_table_t s_var_dispatch_table = create_var_dispatch_table(); +class var_dispatch_table_t { + using named_callback_t = std::function; + std::unordered_map named_table_; -/// This is used to ensure that we don't perform any callbacks from `env_dispatch_var_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; + using anon_callback_t = std::function; + std::unordered_map anon_table_; -void env_dispatch_mark_initialization_finished() { env_initialized = true; } + bool observes_var(const wcstring &name) { + return named_table_.count(name) || anon_table_.count(name); + } + + public: + /// Add a callback for the given variable, which expects the name. + /// We must not already be observing this variable. + void add(wcstring name, named_callback_t cb) { + assert(!observes_var(name) && "Already observing that variable"); + named_table_.emplace(std::move(name), std::move(cb)); + } + + /// Add a callback for the given variable, which ignores the name. + /// We must not already be observing this variable. + void add(wcstring name, anon_callback_t cb) { + assert(!observes_var(name) && "Already observing that variable"); + anon_table_.emplace(std::move(name), std::move(cb)); + } + + void dispatch(const wchar_t *op, const wcstring &key, env_stack_t &vars) const { + auto named = named_table_.find(key); + if (named != named_table_.end()) { + named->second(op, key, vars); + } + auto anon = anon_table_.find(key); + if (anon != anon_table_.end()) { + anon->second(vars); + } + } +}; + +// return a new-ly allocated dispatch table, running those dispatch functions which should be +// initialized. +static std::unique_ptr create_dispatch_table(); + +// A pointer to the variable dispatch table. This is allocated with new() and deliberately leaked to +// avoid shutdown destructors. This is set during startup and should not be modified after. +static const var_dispatch_table_t *s_var_dispatch_table; + +void env_dispatch_init() { s_var_dispatch_table = create_dispatch_table().release(); } /// Properly sets all timezone information. static void handle_timezone(const wchar_t *env_var_name, const environment_t &vars) { @@ -145,17 +179,14 @@ void guess_emoji_width() { /// React to modifying the given variable. void env_dispatch_var_change(const wchar_t *op, const wcstring &key, env_stack_t &vars) { - // 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 or as part - // of loading the universal variables for the first time. Variables we import from the - // environment or that are otherwise set by fish before this gets called have to explicitly - // call the appropriate functions to put the value of the var into effect. - if (!env_initialized) return; + ASSERT_IS_MAIN_THREAD(); + // Do nothing if not yet fully initialized. + if (!s_var_dispatch_table) return; - auto dispatch = s_var_dispatch_table.find(key); - if (dispatch != s_var_dispatch_table.end()) { - (*dispatch->second)(op, key, vars); - } else if (string_prefixes_string(L"fish_color_", key)) { + s_var_dispatch_table->dispatch(op, key, vars); + + // Eww. + if (string_prefixes_string(L"fish_color_", key)) { reader_react_to_color_change(); } } @@ -280,29 +311,30 @@ static void handle_curses_change(const wcstring &op, const wcstring &var_name, e /// Populate the dispatch table used by `env_dispatch_var_change()` to efficiently call the /// appropriate function to handle a change to a variable. -static var_dispatch_table_t create_var_dispatch_table() { - var_dispatch_table_t var_dispatch_table; +/// Note this returns a new-allocated value that we expect to leak. +static std::unique_ptr create_dispatch_table() { + auto var_dispatch_table = make_unique(); for (const auto &var_name : locale_variables) { - var_dispatch_table.emplace(var_name, handle_locale_change); + var_dispatch_table->add(var_name, handle_locale_change); } for (const auto &var_name : curses_variables) { - var_dispatch_table.emplace(var_name, handle_curses_change); + var_dispatch_table->add(var_name, handle_curses_change); } - var_dispatch_table.emplace(L"PATH", handle_magic_colon_var_change); - var_dispatch_table.emplace(L"CDPATH", handle_magic_colon_var_change); - var_dispatch_table.emplace(L"fish_term256", handle_fish_term_change); - var_dispatch_table.emplace(L"fish_term24bit", handle_fish_term_change); - var_dispatch_table.emplace(L"fish_escape_delay_ms", handle_escape_delay_change); - var_dispatch_table.emplace(L"fish_emoji_width", handle_change_emoji_width); - var_dispatch_table.emplace(L"fish_ambiguous_width", handle_change_ambiguous_width); - var_dispatch_table.emplace(L"LINES", handle_term_size_change); - var_dispatch_table.emplace(L"COLUMNS", handle_term_size_change); - var_dispatch_table.emplace(L"fish_complete_path", handle_complete_path_change); - var_dispatch_table.emplace(L"fish_function_path", handle_function_path_change); - var_dispatch_table.emplace(L"fish_read_limit", handle_read_limit_change); - var_dispatch_table.emplace(L"fish_history", handle_fish_history_change); - var_dispatch_table.emplace(L"TZ", handle_tz_change); + var_dispatch_table->add(L"PATH", handle_magic_colon_var_change); + var_dispatch_table->add(L"CDPATH", handle_magic_colon_var_change); + var_dispatch_table->add(L"fish_term256", handle_fish_term_change); + var_dispatch_table->add(L"fish_term24bit", handle_fish_term_change); + var_dispatch_table->add(L"fish_escape_delay_ms", handle_escape_delay_change); + var_dispatch_table->add(L"fish_emoji_width", handle_change_emoji_width); + var_dispatch_table->add(L"fish_ambiguous_width", handle_change_ambiguous_width); + var_dispatch_table->add(L"LINES", handle_term_size_change); + var_dispatch_table->add(L"COLUMNS", handle_term_size_change); + var_dispatch_table->add(L"fish_complete_path", handle_complete_path_change); + var_dispatch_table->add(L"fish_function_path", handle_function_path_change); + var_dispatch_table->add(L"fish_read_limit", handle_read_limit_change); + var_dispatch_table->add(L"fish_history", handle_fish_history_change); + var_dispatch_table->add(L"TZ", handle_tz_change); return var_dispatch_table; } diff --git a/src/env_dispatch.h b/src/env_dispatch.h index 9f10ff060..b862b7445 100644 --- a/src/env_dispatch.h +++ b/src/env_dispatch.h @@ -9,8 +9,8 @@ #include -/// Mark initialization as finished. -void env_dispatch_mark_initialization_finished(); +/// Initialize variable dispatch. +void env_dispatch_init(); class env_stack_t; void env_dispatch_var_change(const wchar_t *op, const wcstring &key, env_stack_t &vars); From e7de9cc37109c4cb05dcbaa79abbe39b96ecf282 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Sun, 31 Mar 2019 19:51:08 -0700 Subject: [PATCH 03/13] Migrate some env initialization into env_dispatch --- src/env.cpp | 3 +-- src/env_dispatch.cpp | 62 +++++++++++++++++++++----------------------- src/env_dispatch.h | 3 ++- 3 files changed, 33 insertions(+), 35 deletions(-) diff --git a/src/env.cpp b/src/env.cpp index c0a00fec7..a3aaf68d2 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -687,7 +687,6 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { init_curses(vars); init_input(); init_path_vars(); - guess_emoji_width(); // Set up the USER and PATH variables setup_path(); @@ -789,7 +788,7 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { vars.set_one(FISH_BIND_MODE_VAR, ENV_GLOBAL, DEFAULT_BIND_MODE); // Allow changes to variables to produce events. - env_dispatch_init(); + env_dispatch_init(vars); // Set up universal variables. The empty string means to use the default path. assert(s_universal_variables == NULL); diff --git a/src/env_dispatch.cpp b/src/env_dispatch.cpp index 74ca13788..ddadc8427 100644 --- a/src/env_dispatch.cpp +++ b/src/env_dispatch.cpp @@ -120,6 +120,9 @@ class var_dispatch_table_t { } }; +// Run those dispatch functions which want to be run at startup. +static void run_inits(const environment_t &vars); + // return a new-ly allocated dispatch table, running those dispatch functions which should be // initialized. static std::unique_ptr create_dispatch_table(); @@ -128,7 +131,12 @@ static std::unique_ptr create_dispatch_table(); // avoid shutdown destructors. This is set during startup and should not be modified after. static const var_dispatch_table_t *s_var_dispatch_table; -void env_dispatch_init() { s_var_dispatch_table = create_dispatch_table().release(); } +void env_dispatch_init(const environment_t &vars) { + run_inits(vars); + // Note this deliberately leaks; the dispatch table is immortal. + // Via this construct we can avoid invoking destructors at shutdown. + s_var_dispatch_table = create_dispatch_table().release(); +} /// Properly sets all timezone information. static void handle_timezone(const wchar_t *env_var_name, const environment_t &vars) { @@ -146,9 +154,15 @@ static void handle_timezone(const wchar_t *env_var_name, const environment_t &va } /// Update the value of g_guessed_fish_emoji_width -void guess_emoji_width() { +static void guess_emoji_width(const environment_t &vars) { + if (auto width_str = vars.get(L"fish_emoji_width")) { + int new_width = fish_wcstol(width_str->as_string().c_str()); + g_fish_emoji_width = std::max(0, new_width); + debug(2, "'fish_emoji_width' preference: %d, overwriting default", g_fish_emoji_width); + return; + } + wcstring term; - auto &vars = env_stack_t::globals(); if (auto term_var = vars.get(L"TERM_PROGRAM")) { term = term_var->as_string(); } @@ -215,25 +229,6 @@ static void handle_fish_term_change(const wcstring &op, const wcstring &var_name reader_react_to_color_change(); } -static void handle_escape_delay_change(const wcstring &op, const wcstring &var_name, - env_stack_t &vars) { - UNUSED(op); - UNUSED(var_name); - update_wait_on_escape_ms(vars); -} - -static void handle_change_emoji_width(const wcstring &op, const wcstring &var_name, - env_stack_t &vars) { - (void)op; - (void)var_name; - int new_width = 0; - if (auto width_str = vars.get(L"fish_emoji_width")) { - new_width = fish_wcstol(width_str->as_string().c_str()); - } - g_fish_emoji_width = std::max(0, new_width); - debug(2, "'fish_emoji_width' preference: %d, overwriting default", g_fish_emoji_width); -} - static void handle_change_ambiguous_width(const wcstring &op, const wcstring &var_name, env_stack_t &vars) { (void)op; @@ -294,18 +289,14 @@ static void handle_magic_colon_var_change(const wcstring &op, const wcstring &va fix_colon_delimited_var(var_name, vars); } -static void handle_locale_change(const wcstring &op, const wcstring &var_name, env_stack_t &vars) { - UNUSED(op); - UNUSED(var_name); +static void handle_locale_change(const environment_t &vars) { init_locale(vars); // We need to re-guess emoji width because the locale might have changed to a multibyte one. - guess_emoji_width(); + guess_emoji_width(vars); } -static void handle_curses_change(const wcstring &op, const wcstring &var_name, env_stack_t &vars) { - UNUSED(op); - UNUSED(var_name); - guess_emoji_width(); +static void handle_curses_change(const environment_t &vars) { + guess_emoji_width(vars); init_curses(vars); } @@ -326,8 +317,8 @@ static std::unique_ptr create_dispatch_table() { var_dispatch_table->add(L"CDPATH", handle_magic_colon_var_change); var_dispatch_table->add(L"fish_term256", handle_fish_term_change); var_dispatch_table->add(L"fish_term24bit", handle_fish_term_change); - var_dispatch_table->add(L"fish_escape_delay_ms", handle_escape_delay_change); - var_dispatch_table->add(L"fish_emoji_width", handle_change_emoji_width); + var_dispatch_table->add(L"fish_escape_delay_ms", update_wait_on_escape_ms); + var_dispatch_table->add(L"fish_emoji_width", guess_emoji_width); var_dispatch_table->add(L"fish_ambiguous_width", handle_change_ambiguous_width); var_dispatch_table->add(L"LINES", handle_term_size_change); var_dispatch_table->add(L"COLUMNS", handle_term_size_change); @@ -338,3 +329,10 @@ static std::unique_ptr create_dispatch_table() { var_dispatch_table->add(L"TZ", handle_tz_change); return var_dispatch_table; } + +static void run_inits(const environment_t &vars) { + // This is the subset of those dispatch functions which want to be run at startup. + handle_locale_change(vars); + handle_curses_change(vars); + update_wait_on_escape_ms(vars); +} diff --git a/src/env_dispatch.h b/src/env_dispatch.h index b862b7445..ca12cf97f 100644 --- a/src/env_dispatch.h +++ b/src/env_dispatch.h @@ -10,7 +10,8 @@ #include /// Initialize variable dispatch. -void env_dispatch_init(); +class environment_t; +void env_dispatch_init(const environment_t &vars); class env_stack_t; void env_dispatch_var_change(const wchar_t *op, const wcstring &key, env_stack_t &vars); From ab67354192113332be6a4e3d1baf95b9e25f2489 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 8 Apr 2019 12:08:03 -0700 Subject: [PATCH 04/13] Migrate fish_use_posix_spawn into env_dispatch --- src/env.cpp | 6 ------ src/env_dispatch.cpp | 11 +++++++++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/env.cpp b/src/env.cpp index a3aaf68d2..c05c13ecc 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -81,7 +81,6 @@ size_t read_byte_limit = READ_BYTE_LIMIT; static const wchar_t PATH_ARRAY_SEP = L':'; static const wchar_t NONPATH_ARRAY_SEP = L' '; -bool g_use_posix_spawn = false; // will usually be set to true bool curses_initialized = false; /// Does the terminal have the "eat_newline_glitch". @@ -779,11 +778,6 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { vars.set_termsize(); // initialize the terminal size variables vars.set_read_limit(); // initialize the read_byte_limit - // Set g_use_posix_spawn. Default to true. - auto use_posix_spawn = vars.get(L"fish_use_posix_spawn"); - g_use_posix_spawn = - use_posix_spawn.missing_or_empty() ? true : bool_from_string(use_posix_spawn->as_string()); - // Set fish_bind_mode to "default". vars.set_one(FISH_BIND_MODE_VAR, ENV_GLOBAL, DEFAULT_BIND_MODE); diff --git a/src/env_dispatch.cpp b/src/env_dispatch.cpp index ddadc8427..ed84ddc77 100644 --- a/src/env_dispatch.cpp +++ b/src/env_dispatch.cpp @@ -300,6 +300,13 @@ static void handle_curses_change(const environment_t &vars) { init_curses(vars); } +static void handle_fish_use_posix_spawn_change(const environment_t &vars) { + // note this defaults to true + auto use_posix_spawn = vars.get(L"fish_use_posix_spawn"); + g_use_posix_spawn = + use_posix_spawn.missing_or_empty() ? true : bool_from_string(use_posix_spawn->as_string()); +} + /// Populate the dispatch table used by `env_dispatch_var_change()` to efficiently call the /// appropriate function to handle a change to a variable. /// Note this returns a new-allocated value that we expect to leak. @@ -327,6 +334,7 @@ static std::unique_ptr create_dispatch_table() { var_dispatch_table->add(L"fish_read_limit", handle_read_limit_change); var_dispatch_table->add(L"fish_history", handle_fish_history_change); var_dispatch_table->add(L"TZ", handle_tz_change); + var_dispatch_table->add(L"fish_use_posix_spawn", handle_fish_use_posix_spawn_change); return var_dispatch_table; } @@ -336,3 +344,6 @@ static void run_inits(const environment_t &vars) { handle_curses_change(vars); update_wait_on_escape_ms(vars); } + +// Miscellaneous variables. +bool g_use_posix_spawn = false; From 0c5809a0883b8d133c298a8b8c9b963a39489b88 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 8 Apr 2019 12:22:08 -0700 Subject: [PATCH 05/13] Minor cleanup of env_node_t --- src/env.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/env.cpp b/src/env.cpp index c05c13ecc..12bd73ae3 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -95,7 +95,7 @@ class env_node_t { /// Does this node imply a new variable scope? If yes, all non-global variables below this one /// in the stack are invisible. If new_scope is set for the global variable node, the universe /// will explode. - bool new_scope; + const bool new_scope; /// Does this node contain any variables which are exported to subshells /// or does it redefine any variables to not be exported? bool exportv = false; @@ -104,7 +104,11 @@ class env_node_t { env_node_t(bool is_new_scope) : new_scope(is_new_scope) {} - maybe_t find_entry(const wcstring &key); + maybe_t find_entry(const wcstring &key) { + auto it = env.find(key); + if (it != env.end()) return it->second; + return none(); + } /// Return whether this node contains any of the entries in the vars list. bool contains_any_of(const wcstring_list_t &vars) const { @@ -305,12 +309,6 @@ var_stack_t &env_stack_t::vars_stack() { return *vars_; } const var_stack_t &env_stack_t::vars_stack() const { return *vars_; } -maybe_t env_node_t::find_entry(const wcstring &key) { - var_table_t::const_iterator entry = env.find(key); - if (entry != env.end()) return entry->second; - return none(); -} - /// Return the current umask value. static mode_t get_umask() { mode_t res; From 59fb5b1849e953018f8d3d0f90fe990918e3f613 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 8 Apr 2019 12:23:49 -0700 Subject: [PATCH 06/13] var_stack_t::pop() to return the popped node --- src/env.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/env.cpp b/src/env.cpp index 12bd73ae3..af7519dc7 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -175,14 +175,10 @@ struct var_stack_t { } } - // Pops the top node if it's not global - void pop() { - // Don't pop the top-most, global, level. - if (top == this->global_env) { - debug(0, _(L"Tried to pop empty environment stack.")); - sanity_lose(); - return; - } + // Pops the top node, asserting it's not global. + // \return the popped node. + env_node_ref_t pop() { + assert(top != this->global_env && "Cannot pop global node"); bool locale_changed = top->contains_any_of(locale_variables); bool curses_changed = top->contains_any_of(curses_variables); @@ -208,6 +204,7 @@ struct var_stack_t { const auto &vars = env_stack_t::principal(); if (locale_changed) init_locale(vars); if (curses_changed) init_curses(vars); + return old_top; } // Returns the next scope to search for a given node, respecting the new_scope flag. From dc729653beb7beb34ae40ff6d7fbbdb7cb71a9ce Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 8 Apr 2019 12:32:14 -0700 Subject: [PATCH 07/13] Migrate pop complexity from vars_stack_t to env_stack_t When popping a scope from the environment stack, we currently do a lot of nonsense like looking for changed curses variables. We want to centralize this in env_stack_t so that it can be migrated to the env_dispatch logic. Move this logic up one level in preparation for doing that. --- src/env.cpp | 51 ++++++++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/src/env.cpp b/src/env.cpp index af7519dc7..b0e75f4c3 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -179,31 +179,8 @@ struct var_stack_t { // \return the popped node. env_node_ref_t pop() { assert(top != this->global_env && "Cannot pop global node"); - - bool locale_changed = top->contains_any_of(locale_variables); - bool curses_changed = top->contains_any_of(curses_variables); - - if (top->new_scope) { //!OCLINT(collapsible if statements) - if (top->exportv || local_scope_exports(top->next)) { - this->mark_changed_exported(); - } - } - - // Actually do the pop! env_node_ref_t old_top = this->top; this->top = old_top->next; - for (const auto &entry_pair : old_top->env) { - const env_var_t &var = entry_pair.second; - if (var.exports()) { - this->mark_changed_exported(); - break; - } - } - - // TODO: instantize this locale and curses - const auto &vars = env_stack_t::principal(); - if (locale_changed) init_locale(vars); - if (curses_changed) init_curses(vars); return old_top; } @@ -232,11 +209,13 @@ struct var_stack_t { return var_stack_t(*this); } + /// return true if the topomst local scope exports a variable. + bool local_scope_exports(const env_node_ref_t &n) const; + private: /// Copy constructor. This does not copy the export array; it just allows it to be regenerated. var_stack_t(const var_stack_t &rhs) : top(rhs.top), global_env(rhs.global_env) {} - bool local_scope_exports(const env_node_ref_t &n) const; void get_exported(const env_node_t *n, var_table_t &h) const; /// Returns the global variable set. @@ -1209,7 +1188,29 @@ bool var_stack_t::local_scope_exports(const env_node_ref_t &n) const { void env_stack_t::push(bool new_scope) { vars_stack().push(new_scope); } -void env_stack_t::pop() { vars_stack().pop(); } +void env_stack_t::pop() { + auto &vars = vars_stack(); + auto old_node = vars.pop(); + if (old_node->contains_any_of(locale_variables)) { + init_locale(*this); + } + + if (old_node->contains_any_of(curses_variables)) { + init_curses(*this); + } + + if (old_node->new_scope && (old_node->exportv || vars.local_scope_exports(old_node->next))) { + vars.mark_changed_exported(); + } + + for (const auto &entry_pair : old_node->env) { + const env_var_t &var = entry_pair.second; + if (var.exports()) { + vars.mark_changed_exported(); + break; + } + } +} /// Function used with to insert keys of one table into a set::set. static void add_key_to_string_set(const var_table_t &envs, std::set *str_set, From 987e41de12820d1c8b1902bb49cc25882e2c0b51 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 8 Apr 2019 12:37:38 -0700 Subject: [PATCH 08/13] Remove the op from env_dispatch Environment dispatch passes strings like "ERASE" and "SET" but nobody ever looks at those. Just get rid of them. --- src/env.cpp | 4 ++-- src/env_dispatch.cpp | 49 +++++++++++++++----------------------------- src/env_dispatch.h | 2 +- 3 files changed, 19 insertions(+), 36 deletions(-) diff --git a/src/env.cpp b/src/env.cpp index b0e75f4c3..6b985ce19 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -972,7 +972,7 @@ int env_stack_t::set_internal(const wcstring &key, env_mode_flags_t input_var_mo } event_fire(event_t::variable(key, {L"VARIABLE", L"SET", key})); - env_dispatch_var_change(L"SET", key, *this); + env_dispatch_var_change(key, *this); return ENV_OK; } @@ -1058,7 +1058,7 @@ int env_stack_t::remove(const wcstring &key, int var_mode) { if (is_exported) vars_stack().mark_changed_exported(); } - env_dispatch_var_change(L"ERASE", key, *this); + env_dispatch_var_change(key, *this); return erased ? ENV_OK : ENV_NOT_FOUND; } diff --git a/src/env_dispatch.cpp b/src/env_dispatch.cpp index ed84ddc77..cb22dbfeb 100644 --- a/src/env_dispatch.cpp +++ b/src/env_dispatch.cpp @@ -83,7 +83,7 @@ extern const wcstring_list_t locale_variables({L"LANG", L"LANGUAGE", L"LC_ALL", extern const wcstring_list_t curses_variables({L"TERM", L"TERMINFO", L"TERMINFO_DIRS"}); class var_dispatch_table_t { - using named_callback_t = std::function; + using named_callback_t = std::function; std::unordered_map named_table_; using anon_callback_t = std::function; @@ -108,10 +108,10 @@ class var_dispatch_table_t { anon_table_.emplace(std::move(name), std::move(cb)); } - void dispatch(const wchar_t *op, const wcstring &key, env_stack_t &vars) const { + void dispatch(const wcstring &key, env_stack_t &vars) const { auto named = named_table_.find(key); if (named != named_table_.end()) { - named->second(op, key, vars); + named->second(key, vars); } auto anon = anon_table_.find(key); if (anon != anon_table_.end()) { @@ -192,12 +192,12 @@ static void guess_emoji_width(const environment_t &vars) { } /// React to modifying the given variable. -void env_dispatch_var_change(const wchar_t *op, const wcstring &key, env_stack_t &vars) { +void env_dispatch_var_change(const wcstring &key, env_stack_t &vars) { ASSERT_IS_MAIN_THREAD(); // Do nothing if not yet fully initialized. if (!s_var_dispatch_table) return; - s_var_dispatch_table->dispatch(op, key, vars); + s_var_dispatch_table->dispatch(key, vars); // Eww. if (string_prefixes_string(L"fish_color_", key)) { @@ -210,7 +210,7 @@ void env_dispatch_var_change(const wchar_t *op, const wcstring &key, env_stack_t static void universal_callback(env_stack_t *stack, const callback_data_t &cb) { const wchar_t *op = cb.is_erase() ? L"ERASE" : L"SET"; - env_dispatch_var_change(op, cb.key, *stack); + env_dispatch_var_change(cb.key, *stack); stack->mark_changed_exported(); event_fire(event_t::variable(cb.key, {L"VARIABLE", op, cb.key})); } @@ -221,18 +221,14 @@ void env_universal_callbacks(env_stack_t *stack, const callback_data_list_t &cal } } -static void handle_fish_term_change(const wcstring &op, const wcstring &var_name, - env_stack_t &vars) { - UNUSED(op); +static void handle_fish_term_change(const wcstring &var_name, env_stack_t &vars) { UNUSED(var_name); update_fish_color_support(vars); reader_react_to_color_change(); } -static void handle_change_ambiguous_width(const wcstring &op, const wcstring &var_name, - env_stack_t &vars) { - (void)op; - (void)var_name; +static void handle_change_ambiguous_width(const wcstring &var_name, env_stack_t &vars) { + UNUSED(var_name); int new_width = 1; if (auto width_str = vars.get(L"fish_ambiguous_width")) { new_width = fish_wcstol(width_str->as_string().c_str()); @@ -240,52 +236,39 @@ static void handle_change_ambiguous_width(const wcstring &op, const wcstring &va g_fish_ambiguous_width = std::max(0, new_width); } -static void handle_term_size_change(const wcstring &op, const wcstring &var_name, - env_stack_t &vars) { - UNUSED(op); +static void handle_term_size_change(const wcstring &var_name, env_stack_t &vars) { UNUSED(var_name); UNUSED(vars); invalidate_termsize(true); // force fish to update its idea of the terminal size plus vars } -static void handle_read_limit_change(const wcstring &op, const wcstring &var_name, - env_stack_t &vars) { - UNUSED(op); +static void handle_read_limit_change(const wcstring &var_name, env_stack_t &vars) { UNUSED(var_name); vars.set_read_limit(); } -static void handle_fish_history_change(const wcstring &op, const wcstring &var_name, - env_stack_t &vars) { - UNUSED(op); +static void handle_fish_history_change(const wcstring &var_name, env_stack_t &vars) { UNUSED(var_name); reader_change_history(history_session_id(vars)); } -static void handle_function_path_change(const wcstring &op, const wcstring &var_name, - env_stack_t &vars) { - UNUSED(op); +static void handle_function_path_change(const wcstring &var_name, env_stack_t &vars) { UNUSED(var_name); UNUSED(vars); function_invalidate_path(); } -static void handle_complete_path_change(const wcstring &op, const wcstring &var_name, - env_stack_t &vars) { - UNUSED(op); +static void handle_complete_path_change(const wcstring &var_name, env_stack_t &vars) { UNUSED(var_name); UNUSED(vars); complete_invalidate_path(); } -static void handle_tz_change(const wcstring &op, const wcstring &var_name, env_stack_t &vars) { - UNUSED(op); +static void handle_tz_change(const wcstring &var_name, env_stack_t &vars) { handle_timezone(var_name.c_str(), vars); } -static void handle_magic_colon_var_change(const wcstring &op, const wcstring &var_name, - env_stack_t &vars) { - UNUSED(op); +static void handle_magic_colon_var_change(const wcstring &var_name, env_stack_t &vars) { fix_colon_delimited_var(var_name, vars); } diff --git a/src/env_dispatch.h b/src/env_dispatch.h index ca12cf97f..50ece1df7 100644 --- a/src/env_dispatch.h +++ b/src/env_dispatch.h @@ -14,7 +14,7 @@ class environment_t; void env_dispatch_init(const environment_t &vars); class env_stack_t; -void env_dispatch_var_change(const wchar_t *op, const wcstring &key, env_stack_t &vars); +void env_dispatch_var_change(const wcstring &key, env_stack_t &vars); void guess_emoji_width(); void env_universal_callbacks(env_stack_t *stack, const callback_data_list_t &callbacks); From a4fe3c87ae81193488049635c55caa7261df8eef Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 8 Apr 2019 12:50:23 -0700 Subject: [PATCH 09/13] Switch certain environment callbacks from named to anonymous --- src/env_dispatch.cpp | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/env_dispatch.cpp b/src/env_dispatch.cpp index cb22dbfeb..016aca3dc 100644 --- a/src/env_dispatch.cpp +++ b/src/env_dispatch.cpp @@ -221,14 +221,12 @@ void env_universal_callbacks(env_stack_t *stack, const callback_data_list_t &cal } } -static void handle_fish_term_change(const wcstring &var_name, env_stack_t &vars) { - UNUSED(var_name); +static void handle_fish_term_change(env_stack_t &vars) { update_fish_color_support(vars); reader_react_to_color_change(); } -static void handle_change_ambiguous_width(const wcstring &var_name, env_stack_t &vars) { - UNUSED(var_name); +static void handle_change_ambiguous_width(env_stack_t &vars) { int new_width = 1; if (auto width_str = vars.get(L"fish_ambiguous_width")) { new_width = fish_wcstol(width_str->as_string().c_str()); @@ -236,30 +234,23 @@ static void handle_change_ambiguous_width(const wcstring &var_name, env_stack_t g_fish_ambiguous_width = std::max(0, new_width); } -static void handle_term_size_change(const wcstring &var_name, env_stack_t &vars) { - UNUSED(var_name); +static void handle_term_size_change(env_stack_t &vars) { UNUSED(vars); invalidate_termsize(true); // force fish to update its idea of the terminal size plus vars } -static void handle_read_limit_change(const wcstring &var_name, env_stack_t &vars) { - UNUSED(var_name); - vars.set_read_limit(); -} +static void handle_read_limit_change(env_stack_t &vars) { vars.set_read_limit(); } -static void handle_fish_history_change(const wcstring &var_name, env_stack_t &vars) { - UNUSED(var_name); +static void handle_fish_history_change(env_stack_t &vars) { reader_change_history(history_session_id(vars)); } -static void handle_function_path_change(const wcstring &var_name, env_stack_t &vars) { - UNUSED(var_name); +static void handle_function_path_change(env_stack_t &vars) { UNUSED(vars); function_invalidate_path(); } -static void handle_complete_path_change(const wcstring &var_name, env_stack_t &vars) { - UNUSED(var_name); +static void handle_complete_path_change(env_stack_t &vars) { UNUSED(vars); complete_invalidate_path(); } From 11651dec7aff3f36f50a8d49af0242ed003747d3 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 8 Apr 2019 13:02:29 -0700 Subject: [PATCH 10/13] Clean up env_stack_t::pop Use the new dispatch mechanism to reduce duplication --- src/env.cpp | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/env.cpp b/src/env.cpp index 6b985ce19..fb5b24399 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -1191,24 +1191,19 @@ void env_stack_t::push(bool new_scope) { vars_stack().push(new_scope); } void env_stack_t::pop() { auto &vars = vars_stack(); auto old_node = vars.pop(); - if (old_node->contains_any_of(locale_variables)) { - init_locale(*this); - } - if (old_node->contains_any_of(curses_variables)) { - init_curses(*this); - } - - if (old_node->new_scope && (old_node->exportv || vars.local_scope_exports(old_node->next))) { + // Maybe exported variables have changed. + if (old_node->exportv) { + // This node exported or unexported a variable. + vars.mark_changed_exported(); + } else if (old_node->new_scope && vars.local_scope_exports(old_node->next)) { + // This node was a local scope, so it shadowed exports from its parent. vars.mark_changed_exported(); } - for (const auto &entry_pair : old_node->env) { - const env_var_t &var = entry_pair.second; - if (var.exports()) { - vars.mark_changed_exported(); - break; - } + // TODO: we would like to coalesce locale / curses changes, so that we only re-initialize once. + for (const auto &kv : old_node->env) { + env_dispatch_var_change(kv.first, *this); } } From fa0a6ae0960177ba6e19e96ea0ad6e7585f3d30b Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 8 Apr 2019 13:32:25 -0700 Subject: [PATCH 11/13] Move locale and curses init from env to env_dispatch --- src/env.cpp | 215 +------------------------------------------ src/env.h | 5 - src/env_dispatch.cpp | 213 +++++++++++++++++++++++++++++++++++++++++- src/env_dispatch.h | 3 - 4 files changed, 210 insertions(+), 226 deletions(-) diff --git a/src/env.cpp b/src/env.cpp index fb5b24399..9fdc16cce 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include #include @@ -15,19 +14,6 @@ #include #include -#if HAVE_CURSES_H -#include -#elif HAVE_NCURSES_H -#include -#elif HAVE_NCURSES_CURSES_H -#include -#endif -#if HAVE_TERM_H -#include -#elif HAVE_NCURSES_TERM_H -#include -#endif - #include #include #include @@ -315,161 +301,6 @@ void fix_colon_delimited_var(const wcstring &var_name, env_stack_t &vars) { } } -/// Initialize the locale subsystem. -void init_locale(const environment_t &vars) { - // 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)); - - for (const auto &var_name : locale_variables) { - const auto var = vars.get(var_name, ENV_EXPORT); - const std::string &name = wcs2string(var_name); - if (var.missing_or_empty()) { - debug(5, L"locale var %s missing or empty", name.c_str()); - unsetenv(name.c_str()); - } else { - const std::string value = wcs2string(var->as_string()); - debug(5, L"locale var %s='%s'", name.c_str(), value.c_str()); - setenv(name.c_str(), value.c_str(), 1); - } - } - - char *locale = setlocale(LC_ALL, ""); - fish_setlocale(); - debug(5, L"init_locale() setlocale(): '%s'", locale); - - const char *new_msg_locale = setlocale(LC_MESSAGES, NULL); - debug(5, L"old LC_MESSAGES locale: '%s'", old_msg_locale); - debug(5, L"new LC_MESSAGES locale: '%s'", new_msg_locale); -#ifdef HAVE__NL_MSG_CAT_CNTR - if (std::strcmp(old_msg_locale, new_msg_locale)) { - // Make change known to GNU gettext. - extern int _nl_msg_cat_cntr; - _nl_msg_cat_cntr++; - } -#endif - free(old_msg_locale); -} - -/// True if we think we can set the terminal title else false. -static bool can_set_term_title = false; - -/// Returns true if we think the terminal supports setting its title. -bool term_supports_setting_title() { return can_set_term_title; } - -/// This is a pretty lame heuristic for detecting terminals that do not support setting the -/// title. If we recognise the terminal name as that of a virtual terminal, we assume it supports -/// setting the title. If we recognise it as that of a console, we assume it does not support -/// setting the title. Otherwise we check the ttyname and see if we believe it is a virtual -/// terminal. -/// -/// One situation in which this breaks down is with screen, since screen supports setting the -/// terminal title if the underlying terminal does so, but will print garbage on terminals that -/// don't. Since we can't see the underlying terminal below screen there is no way to fix this. -static const wchar_t *const title_terms[] = {L"xterm", L"screen", L"tmux", L"nxterm", L"rxvt"}; -static bool does_term_support_setting_title(const environment_t &vars) { - const auto term_var = vars.get(L"TERM"); - if (term_var.missing_or_empty()) return false; - - const wcstring term_str = term_var->as_string(); - const wchar_t *term = term_str.c_str(); - bool recognized = contains(title_terms, term_var->as_string()); - if (!recognized) recognized = !std::wcsncmp(term, L"xterm-", std::wcslen(L"xterm-")); - if (!recognized) recognized = !std::wcsncmp(term, L"screen-", std::wcslen(L"screen-")); - if (!recognized) recognized = !std::wcsncmp(term, L"tmux-", std::wcslen(L"tmux-")); - if (!recognized) { - if (std::wcscmp(term, L"linux") == 0) return false; - if (std::wcscmp(term, L"dumb") == 0) return false; - // NetBSD - if (std::wcscmp(term, L"vt100") == 0) return false; - if (std::wcscmp(term, L"wsvt25") == 0) return false; - - char buf[PATH_MAX]; - int retval = ttyname_r(STDIN_FILENO, buf, PATH_MAX); - if (retval != 0 || std::strstr(buf, "tty") || std::strstr(buf, "/vc/")) return false; - } - - return true; -} - -/// Updates our idea of whether we support term256 and term24bit (see issue #10222). -void update_fish_color_support(const environment_t &vars) { - // Detect or infer term256 support. If fish_term256 is set, we respect it; - // otherwise infer it from the TERM variable or use terminfo. - wcstring term; - bool support_term256 = false; - bool support_term24bit = false; - - if (auto term_var = vars.get(L"TERM")) term = term_var->as_string(); - - if (auto fish_term256 = vars.get(L"fish_term256")) { - // $fish_term256 - support_term256 = bool_from_string(fish_term256->as_string()); - debug(2, L"256 color support determined by '$fish_term256'"); - } else if (term.find(L"256color") != wcstring::npos) { - // TERM is *256color*: 256 colors explicitly supported - support_term256 = true; - debug(2, L"256 color support enabled for TERM=%ls", term.c_str()); - } else if (term.find(L"xterm") != wcstring::npos) { - // Assume that all 'xterm's can handle 256, except for Terminal.app from Snow Leopard - wcstring term_program; - if (auto tp = vars.get(L"TERM_PROGRAM")) term_program = tp->as_string(); - if (auto tpv = vars.get(L"TERM_PROGRAM_VERSION")) { - if (term_program == L"Apple_Terminal" && - fish_wcstod(tpv->as_string().c_str(), NULL) > 299) { - // OS X Lion is version 299+, it has 256 color support (see github Wiki) - support_term256 = true; - debug(2, L"256 color support enabled for TERM=%ls on Terminal.app", term.c_str()); - } else { - support_term256 = true; - debug(2, L"256 color support enabled for TERM=%ls", term.c_str()); - } - } - } else if (cur_term != NULL) { - // See if terminfo happens to identify 256 colors - support_term256 = (max_colors >= 256); - debug(2, L"256 color support: %d colors per terminfo entry for %ls", max_colors, term.c_str()); - } - - // Handle $fish_term24bit - if (auto fish_term24bit = vars.get(L"fish_term24bit")) { - support_term24bit = bool_from_string(fish_term24bit->as_string()); - 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. - // XXX: actually, we do, in config.fish. - // So we actually change the color mode shortly after startup - } - 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. - auto &vars = env_stack_t::globals(); - auto term_var = vars.get(L"TERM"); - if (term_var.missing_or_empty()) return false; - - auto term_env = wcs2string(term_var->as_string()); - if (term_env == DEFAULT_TERM1 || 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; -} - /// Ensure the content of the magic path env vars is reasonable. Specifically, that empty path /// elements are converted to explicit "." to make the vars easier to use in fish scripts. static void init_path_vars() { @@ -478,47 +309,6 @@ static void init_path_vars() { fix_colon_delimited_var(L"CDPATH", env_stack_t::globals()); } -/// Initialize the curses subsystem. -void init_curses(const environment_t &vars) { - for (const auto &var_name : curses_variables) { - std::string name = wcs2string(var_name); - const auto var = vars.get(var_name, ENV_EXPORT); - if (var.missing_or_empty()) { - debug(2, L"curses var %s missing or empty", name.c_str()); - unsetenv(name.c_str()); - } else { - std::string value = wcs2string(var->as_string()); - debug(2, L"curses var %s='%s'", name.c_str(), value.c_str()); - setenv(name.c_str(), value.c_str(), 1); - } - } - - int err_ret; - if (setupterm(NULL, STDOUT_FILENO, &err_ret) == ERR) { - auto term = vars.get(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->as_string().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(vars); - term_has_xn = tigetflag((char *)"xenl") == 1; // does terminal have the eat_newline_glitch - update_fish_color_support(vars); - // Invalidate the cached escape sequences since they may no longer be valid. - cached_layouts.clear(); - curses_initialized = true; -} - /// Make sure the PATH variable contains something. static void setup_path() { auto &vars = env_stack_t::globals(); @@ -656,9 +446,6 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { path_get_data(user_data_dir); vars.set_one(FISH_USER_DATA_DIR, ENV_GLOBAL, user_data_dir); - init_locale(vars); - init_curses(vars); - init_input(); init_path_vars(); // Set up the USER and PATH variables @@ -758,6 +545,8 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { // Allow changes to variables to produce events. env_dispatch_init(vars); + init_input(); + // Set up universal variables. The empty string means to use the default path. assert(s_universal_variables == NULL); s_universal_variables = new env_universal_t(L""); diff --git a/src/env.h b/src/env.h index 8bb725ebf..8de3e353c 100644 --- a/src/env.h +++ b/src/env.h @@ -320,9 +320,4 @@ wcstring env_get_runtime_path(); /// Replace empty path elements with "." - see #3914. void fix_colon_delimited_var(const wcstring &var_name, env_stack_t &vars); -// Temporararily exposed so that env_dispatch can call these. -void init_locale(const environment_t &vars); -void init_curses(const environment_t &vars); -void update_fish_color_support(const environment_t &vars); - #endif diff --git a/src/env_dispatch.cpp b/src/env_dispatch.cpp index 016aca3dc..ded6e76a4 100644 --- a/src/env_dispatch.cpp +++ b/src/env_dispatch.cpp @@ -72,7 +72,7 @@ /// List of all locale environment variable names that might trigger (re)initializing the locale /// subsystem. -extern const wcstring_list_t locale_variables({L"LANG", L"LANGUAGE", L"LC_ALL", L"LC_ADDRESS", +static const wcstring_list_t locale_variables({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", @@ -80,7 +80,7 @@ extern const wcstring_list_t locale_variables({L"LANG", L"LANGUAGE", L"LC_ALL", /// List of all curses environment variable names that might trigger (re)initializing the curses /// subsystem. -extern const wcstring_list_t curses_variables({L"TERM", L"TERMINFO", L"TERMINFO_DIRS"}); +static const wcstring_list_t curses_variables({L"TERM", L"TERMINFO", L"TERMINFO_DIRS"}); class var_dispatch_table_t { using named_callback_t = std::function; @@ -120,6 +120,14 @@ class var_dispatch_table_t { } }; +// Forward declarations. +static void init_curses(const environment_t &vars); +static void init_locale(const environment_t &vars); +static void update_fish_color_support(const environment_t &vars); + +/// True if we think we can set the terminal title. +static bool can_set_term_title = false; + // Run those dispatch functions which want to be run at startup. static void run_inits(const environment_t &vars); @@ -314,10 +322,205 @@ static std::unique_ptr create_dispatch_table() { static void run_inits(const environment_t &vars) { // This is the subset of those dispatch functions which want to be run at startup. - handle_locale_change(vars); - handle_curses_change(vars); + init_locale(vars); + init_curses(vars); + guess_emoji_width(vars); update_wait_on_escape_ms(vars); } -// Miscellaneous variables. +/// Updates our idea of whether we support term256 and term24bit (see issue #10222). +static void update_fish_color_support(const environment_t &vars) { + // Detect or infer term256 support. If fish_term256 is set, we respect it; + // otherwise infer it from the TERM variable or use terminfo. + wcstring term; + bool support_term256 = false; + bool support_term24bit = false; + + if (auto term_var = vars.get(L"TERM")) term = term_var->as_string(); + + if (auto fish_term256 = vars.get(L"fish_term256")) { + // $fish_term256 + support_term256 = bool_from_string(fish_term256->as_string()); + debug(2, L"256 color support determined by '$fish_term256'"); + } else if (term.find(L"256color") != wcstring::npos) { + // TERM is *256color*: 256 colors explicitly supported + support_term256 = true; + debug(2, L"256 color support enabled for TERM=%ls", term.c_str()); + } else if (term.find(L"xterm") != wcstring::npos) { + // Assume that all 'xterm's can handle 256, except for Terminal.app from Snow Leopard + wcstring term_program; + if (auto tp = vars.get(L"TERM_PROGRAM")) term_program = tp->as_string(); + if (auto tpv = vars.get(L"TERM_PROGRAM_VERSION")) { + if (term_program == L"Apple_Terminal" && + fish_wcstod(tpv->as_string().c_str(), NULL) > 299) { + // OS X Lion is version 299+, it has 256 color support (see github Wiki) + support_term256 = true; + debug(2, L"256 color support enabled for TERM=%ls on Terminal.app", term.c_str()); + } else { + support_term256 = true; + debug(2, L"256 color support enabled for TERM=%ls", term.c_str()); + } + } + } else if (cur_term != NULL) { + // See if terminfo happens to identify 256 colors + support_term256 = (max_colors >= 256); + debug(2, L"256 color support: %d colors per terminfo entry for %ls", max_colors, + term.c_str()); + } + + // Handle $fish_term24bit + if (auto fish_term24bit = vars.get(L"fish_term24bit")) { + support_term24bit = bool_from_string(fish_term24bit->as_string()); + 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. + // XXX: actually, we do, in config.fish. + // So we actually change the color mode shortly after startup + } + 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. + auto &vars = env_stack_t::globals(); + auto term_var = vars.get(L"TERM"); + if (term_var.missing_or_empty()) return false; + + auto term_env = wcs2string(term_var->as_string()); + if (term_env == DEFAULT_TERM1 || 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; +} + +/// This is a pretty lame heuristic for detecting terminals that do not support setting the +/// title. If we recognise the terminal name as that of a virtual terminal, we assume it supports +/// setting the title. If we recognise it as that of a console, we assume it does not support +/// setting the title. Otherwise we check the ttyname and see if we believe it is a virtual +/// terminal. +/// +/// One situation in which this breaks down is with screen, since screen supports setting the +/// terminal title if the underlying terminal does so, but will print garbage on terminals that +/// don't. Since we can't see the underlying terminal below screen there is no way to fix this. +static const wchar_t *const title_terms[] = {L"xterm", L"screen", L"tmux", L"nxterm", L"rxvt"}; +static bool does_term_support_setting_title(const environment_t &vars) { + const auto term_var = vars.get(L"TERM"); + if (term_var.missing_or_empty()) return false; + + const wcstring term_str = term_var->as_string(); + const wchar_t *term = term_str.c_str(); + bool recognized = contains(title_terms, term_var->as_string()); + if (!recognized) recognized = !std::wcsncmp(term, L"xterm-", std::wcslen(L"xterm-")); + if (!recognized) recognized = !std::wcsncmp(term, L"screen-", std::wcslen(L"screen-")); + if (!recognized) recognized = !std::wcsncmp(term, L"tmux-", std::wcslen(L"tmux-")); + if (!recognized) { + if (std::wcscmp(term, L"linux") == 0) return false; + if (std::wcscmp(term, L"dumb") == 0) return false; + // NetBSD + if (std::wcscmp(term, L"vt100") == 0) return false; + if (std::wcscmp(term, L"wsvt25") == 0) return false; + + char buf[PATH_MAX]; + int retval = ttyname_r(STDIN_FILENO, buf, PATH_MAX); + if (retval != 0 || std::strstr(buf, "tty") || std::strstr(buf, "/vc/")) return false; + } + + return true; +} + +/// Initialize the curses subsystem. +static void init_curses(const environment_t &vars) { + for (const auto &var_name : curses_variables) { + std::string name = wcs2string(var_name); + const auto var = vars.get(var_name, ENV_EXPORT); + if (var.missing_or_empty()) { + debug(2, L"curses var %s missing or empty", name.c_str()); + unsetenv(name.c_str()); + } else { + std::string value = wcs2string(var->as_string()); + debug(2, L"curses var %s='%s'", name.c_str(), value.c_str()); + setenv(name.c_str(), value.c_str(), 1); + } + } + + int err_ret; + if (setupterm(NULL, STDOUT_FILENO, &err_ret) == ERR) { + auto term = vars.get(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->as_string().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(vars); + term_has_xn = tigetflag((char *)"xenl") == 1; // does terminal have the eat_newline_glitch + update_fish_color_support(vars); + // Invalidate the cached escape sequences since they may no longer be valid. + cached_layouts.clear(); + curses_initialized = true; +} + +/// Initialize the locale subsystem. +static void init_locale(const environment_t &vars) { + // 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)); + + for (const auto &var_name : locale_variables) { + const auto var = vars.get(var_name, ENV_EXPORT); + const std::string &name = wcs2string(var_name); + if (var.missing_or_empty()) { + debug(5, L"locale var %s missing or empty", name.c_str()); + unsetenv(name.c_str()); + } else { + const std::string value = wcs2string(var->as_string()); + debug(5, L"locale var %s='%s'", name.c_str(), value.c_str()); + setenv(name.c_str(), value.c_str(), 1); + } + } + + char *locale = setlocale(LC_ALL, ""); + fish_setlocale(); + debug(5, L"init_locale() setlocale(): '%s'", locale); + + const char *new_msg_locale = setlocale(LC_MESSAGES, NULL); + debug(5, L"old LC_MESSAGES locale: '%s'", old_msg_locale); + debug(5, L"new LC_MESSAGES locale: '%s'", new_msg_locale); +#ifdef HAVE__NL_MSG_CAT_CNTR + if (std::strcmp(old_msg_locale, new_msg_locale)) { + // Make change known to GNU gettext. + extern int _nl_msg_cat_cntr; + _nl_msg_cat_cntr++; + } +#endif + free(old_msg_locale); +} + +/// Returns true if we think the terminal supports setting its title. +bool term_supports_setting_title() { return can_set_term_title; } + +/// Miscellaneous variables. bool g_use_posix_spawn = false; diff --git a/src/env_dispatch.h b/src/env_dispatch.h index 50ece1df7..6105d1a8e 100644 --- a/src/env_dispatch.h +++ b/src/env_dispatch.h @@ -19,7 +19,4 @@ void guess_emoji_width(); void env_universal_callbacks(env_stack_t *stack, const callback_data_list_t &callbacks); -extern const wcstring_list_t locale_variables; -extern const wcstring_list_t curses_variables; - #endif From 1caf20f7c3fd3b0837b91e2e37e0d4e6e6370335 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 8 Apr 2019 13:41:42 -0700 Subject: [PATCH 12/13] Migrate the read limit into env_dispatch --- src/env.cpp | 20 -------------------- src/env.h | 3 --- src/env_dispatch.cpp | 21 +++++++++++++++++++-- 3 files changed, 19 insertions(+), 25 deletions(-) diff --git a/src/env.cpp b/src/env.cpp index 9fdc16cce..ddcd25b89 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -58,11 +58,6 @@ /// At init, we read all the environment variables from this array. extern char **environ; -// Limit `read` to 10 MiB (bytes not wide chars) by default. This can be overridden by the -// fish_read_limit variable. -#define READ_BYTE_LIMIT 10 * 1024 * 1024 -size_t read_byte_limit = READ_BYTE_LIMIT; - /// The character used to delimit path and non-path variables in exporting and in string expansion. static const wchar_t PATH_ARRAY_SEP = L':'; static const wchar_t NONPATH_ARRAY_SEP = L' '; @@ -349,20 +344,6 @@ void env_stack_t::set_pwd_from_getcwd() { set_one(L"PWD", ENV_EXPORT | ENV_GLOBAL, cwd); } -/// Allow the user to override the limit on how much data the `read` command will process. -/// This is primarily for testing but could be used by users in special situations. -void env_stack_t::set_read_limit() { - auto read_byte_limit_var = this->get(L"fish_read_limit"); - if (!read_byte_limit_var.missing_or_empty()) { - size_t limit = fish_wcstoull(read_byte_limit_var->as_string().c_str()); - if (errno) { - debug(1, "Ignoring fish_read_limit since it is not valid"); - } else { - read_byte_limit = limit; - } - } -} - void env_stack_t::mark_changed_exported() { vars_stack().mark_changed_exported(); } wcstring environment_t::get_pwd_slash() const { @@ -537,7 +518,6 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { vars.set_pwd_from_getcwd(); } vars.set_termsize(); // initialize the terminal size variables - vars.set_read_limit(); // initialize the read_byte_limit // Set fish_bind_mode to "default". vars.set_one(FISH_BIND_MODE_VAR, ENV_GLOBAL, DEFAULT_BIND_MODE); diff --git a/src/env.h b/src/env.h index 8de3e353c..d3fc98085 100644 --- a/src/env.h +++ b/src/env.h @@ -268,9 +268,6 @@ class env_stack_t final : public environment_t { /// Sets up argv as the given null terminated array of strings. void set_argv(const wchar_t *const *argv); - /// Update the read_byte_limit variable. - void set_read_limit(); - /// Mark that exported variables have changed. void mark_changed_exported(); diff --git a/src/env_dispatch.cpp b/src/env_dispatch.cpp index ded6e76a4..88dfb57fc 100644 --- a/src/env_dispatch.cpp +++ b/src/env_dispatch.cpp @@ -247,8 +247,6 @@ static void handle_term_size_change(env_stack_t &vars) { invalidate_termsize(true); // force fish to update its idea of the terminal size plus vars } -static void handle_read_limit_change(env_stack_t &vars) { vars.set_read_limit(); } - static void handle_fish_history_change(env_stack_t &vars) { reader_change_history(history_session_id(vars)); } @@ -289,6 +287,20 @@ static void handle_fish_use_posix_spawn_change(const environment_t &vars) { use_posix_spawn.missing_or_empty() ? true : bool_from_string(use_posix_spawn->as_string()); } +/// Allow the user to override the limit on how much data the `read` command will process. +/// This is primarily for testing but could be used by users in special situations. +static void handle_read_limit_change(const environment_t &vars) { + auto read_byte_limit_var = vars.get(L"fish_read_limit"); + if (!read_byte_limit_var.missing_or_empty()) { + size_t limit = fish_wcstoull(read_byte_limit_var->as_string().c_str()); + if (errno) { + debug(1, "Ignoring fish_read_limit since it is not valid"); + } else { + read_byte_limit = limit; + } + } +} + /// Populate the dispatch table used by `env_dispatch_var_change()` to efficiently call the /// appropriate function to handle a change to a variable. /// Note this returns a new-allocated value that we expect to leak. @@ -326,6 +338,7 @@ static void run_inits(const environment_t &vars) { init_curses(vars); guess_emoji_width(vars); update_wait_on_escape_ms(vars); + handle_read_limit_change(vars); } /// Updates our idea of whether we support term256 and term24bit (see issue #10222). @@ -524,3 +537,7 @@ bool term_supports_setting_title() { return can_set_term_title; } /// Miscellaneous variables. bool g_use_posix_spawn = false; + +// Limit `read` to 10 MiB (bytes not wide chars) by default. This can be overridden by the +// fish_read_limit variable. +size_t read_byte_limit = 10 * 1024 * 1024; From 3e14f96d40231fb8fe60723f1852a2bdfba38b8f Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 8 Apr 2019 13:56:00 -0700 Subject: [PATCH 13/13] Eliminate string_set_contains --- src/env.cpp | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/env.cpp b/src/env.cpp index ddcd25b89..65efd30fb 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -216,25 +216,14 @@ static env_universal_t *uvars() { return s_universal_variables; } // so we don't bother to sort them. using string_set_t = const wchar_t *const[]; -template -bool string_set_contains(const T &set, const wchar_t *val) { - for (const wchar_t *entry : set) { - if (!std::wcscmp(val, entry)) return true; - } - return false; -} - /// Check if a variable may not be set using the set command. -static bool is_read_only(const wchar_t *val) { - const string_set_t env_read_only = { - L"PWD", L"SHLVL", L"history", L"pipestatus", L"status", L"version", +static bool is_read_only(const wcstring &key) { + static const string_set_t env_read_only = { + L"PWD", L"SHLVL", L"history", L"pipestatus", L"status", L"version", L"FISH_VERSION", L"fish_pid", L"hostname", L"_", L"fish_private_mode"}; - return string_set_contains(env_read_only, val) || - (in_private_mode() && std::wcscmp(L"fish_history", val) == 0); + return contains(env_read_only, key) || (in_private_mode() && key == L"fish_history"); } -static bool is_read_only(const wcstring &val) { return is_read_only(val.c_str()); } - /// Return true if a variable should become a path variable by default. See #436. static bool variable_should_auto_pathvar(const wcstring &name) { return string_suffixes_string(L"PATH", name);