2019-03-26 11:18:00 +08:00
|
|
|
// Support for dispatching on environment changes.
|
|
|
|
#include "config.h" // IWYU pragma: keep
|
|
|
|
|
|
|
|
#include <errno.h>
|
|
|
|
#include <limits.h>
|
|
|
|
#include <locale.h>
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include <unistd.h>
|
2019-10-14 06:50:48 +08:00
|
|
|
|
2019-03-26 11:18:00 +08:00
|
|
|
#include <cstring>
|
|
|
|
#include <cwchar>
|
|
|
|
|
|
|
|
#if HAVE_CURSES_H
|
|
|
|
#include <curses.h>
|
|
|
|
#elif HAVE_NCURSES_H
|
|
|
|
#include <ncurses.h>
|
|
|
|
#elif HAVE_NCURSES_CURSES_H
|
|
|
|
#include <ncurses/curses.h>
|
|
|
|
#endif
|
|
|
|
#if HAVE_TERM_H
|
|
|
|
#include <term.h>
|
|
|
|
#elif HAVE_NCURSES_TERM_H
|
|
|
|
#include <ncurses/term.h>
|
|
|
|
#endif
|
|
|
|
|
2019-04-14 05:27:03 +08:00
|
|
|
#include <assert.h>
|
2019-10-14 06:50:48 +08:00
|
|
|
|
2019-05-05 18:09:25 +08:00
|
|
|
#include <algorithm>
|
2019-04-14 05:27:03 +08:00
|
|
|
#include <functional>
|
|
|
|
#include <memory>
|
|
|
|
#include <string>
|
2019-03-26 11:18:00 +08:00
|
|
|
#include <unordered_map>
|
|
|
|
#include <utility>
|
|
|
|
|
|
|
|
#include "common.h"
|
|
|
|
#include "complete.h"
|
|
|
|
#include "env.h"
|
|
|
|
#include "env_universal_common.h"
|
|
|
|
#include "event.h"
|
|
|
|
#include "fallback.h" // IWYU pragma: keep
|
2019-05-19 06:16:42 +08:00
|
|
|
#include "flog.h"
|
2019-03-26 11:18:00 +08:00
|
|
|
#include "function.h"
|
2019-04-29 09:13:55 +08:00
|
|
|
#include "global_safety.h"
|
2019-03-26 11:18:00 +08:00
|
|
|
#include "history.h"
|
|
|
|
#include "input_common.h"
|
2019-04-14 05:27:03 +08:00
|
|
|
#include "maybe.h"
|
2019-03-26 11:18:00 +08:00
|
|
|
#include "output.h"
|
2019-06-03 17:31:13 +08:00
|
|
|
#include "parser.h"
|
2019-03-26 11:18:00 +08:00
|
|
|
#include "proc.h"
|
|
|
|
#include "reader.h"
|
|
|
|
#include "screen.h"
|
|
|
|
#include "wutil.h" // IWYU pragma: keep
|
|
|
|
|
|
|
|
#define DEFAULT_TERM1 "ansi"
|
|
|
|
#define DEFAULT_TERM2 "dumb"
|
|
|
|
|
|
|
|
/// List of all locale environment variable names that might trigger (re)initializing the locale
|
|
|
|
/// subsystem.
|
2019-04-09 04:32:25 +08:00
|
|
|
static const wcstring_list_t locale_variables({L"LANG", L"LANGUAGE", L"LC_ALL", L"LC_ADDRESS",
|
2019-03-26 11:18:00 +08:00
|
|
|
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",
|
2019-04-13 21:58:02 +08:00
|
|
|
L"LC_TELEPHONE", L"LC_TIME", L"LOCPATH"});
|
2019-03-26 11:18:00 +08:00
|
|
|
|
|
|
|
/// List of all curses environment variable names that might trigger (re)initializing the curses
|
|
|
|
/// subsystem.
|
2019-04-09 04:32:25 +08:00
|
|
|
static const wcstring_list_t curses_variables({L"TERM", L"TERMINFO", L"TERMINFO_DIRS"});
|
2019-03-26 11:18:00 +08:00
|
|
|
|
2019-04-01 10:11:52 +08:00
|
|
|
class var_dispatch_table_t {
|
2019-04-09 03:37:38 +08:00
|
|
|
using named_callback_t = std::function<void(const wcstring &, env_stack_t &)>;
|
2019-04-01 10:11:52 +08:00
|
|
|
std::unordered_map<wcstring, named_callback_t> named_table_;
|
2019-03-26 11:18:00 +08:00
|
|
|
|
2019-04-01 10:11:52 +08:00
|
|
|
using anon_callback_t = std::function<void(env_stack_t &)>;
|
|
|
|
std::unordered_map<wcstring, anon_callback_t> anon_table_;
|
2019-03-26 11:18:00 +08:00
|
|
|
|
2019-04-01 10:11:52 +08:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2019-04-09 03:37:38 +08:00
|
|
|
void dispatch(const wcstring &key, env_stack_t &vars) const {
|
2019-04-01 10:11:52 +08:00
|
|
|
auto named = named_table_.find(key);
|
|
|
|
if (named != named_table_.end()) {
|
2019-04-09 03:37:38 +08:00
|
|
|
named->second(key, vars);
|
2019-04-01 10:11:52 +08:00
|
|
|
}
|
|
|
|
auto anon = anon_table_.find(key);
|
|
|
|
if (anon != anon_table_.end()) {
|
|
|
|
anon->second(vars);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-04-09 04:32:25 +08:00
|
|
|
// 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.
|
2019-04-29 09:13:55 +08:00
|
|
|
static relaxed_atomic_bool_t can_set_term_title{false};
|
2019-04-09 04:32:25 +08:00
|
|
|
|
2019-04-01 10:51:08 +08:00
|
|
|
// Run those dispatch functions which want to be run at startup.
|
|
|
|
static void run_inits(const environment_t &vars);
|
|
|
|
|
2019-04-01 10:11:52 +08:00
|
|
|
// return a new-ly allocated dispatch table, running those dispatch functions which should be
|
|
|
|
// initialized.
|
|
|
|
static std::unique_ptr<const var_dispatch_table_t> 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.
|
2019-04-29 09:13:55 +08:00
|
|
|
static latch_t<const var_dispatch_table_t> s_var_dispatch_table;
|
2019-04-01 10:11:52 +08:00
|
|
|
|
2019-04-01 10:51:08 +08:00
|
|
|
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.
|
2019-04-29 10:16:55 +08:00
|
|
|
s_var_dispatch_table = create_dispatch_table();
|
2019-04-01 10:51:08 +08:00
|
|
|
}
|
2019-03-26 11:18:00 +08:00
|
|
|
|
|
|
|
/// 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);
|
2020-01-19 21:42:12 +08:00
|
|
|
FLOGF(env_dispatch, L"handle_timezone() current timezone var: |%ls| => |%ls|", env_var_name,
|
2019-03-26 11:18:00 +08:00
|
|
|
!var ? L"MISSING" : var->as_string().c_str());
|
|
|
|
const std::string &name = wcs2string(env_var_name);
|
|
|
|
if (var.missing_or_empty()) {
|
2019-05-23 07:09:59 +08:00
|
|
|
unsetenv_lock(name.c_str());
|
2019-03-26 11:18:00 +08:00
|
|
|
} else {
|
|
|
|
const std::string value = wcs2string(var->as_string());
|
2019-05-23 07:09:59 +08:00
|
|
|
setenv_lock(name.c_str(), value.c_str(), 1);
|
2019-03-26 11:18:00 +08:00
|
|
|
}
|
|
|
|
tzset();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Update the value of g_guessed_fish_emoji_width
|
2019-04-01 10:51:08 +08:00
|
|
|
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);
|
2020-01-30 09:06:11 +08:00
|
|
|
FLOGF(term_support, "'fish_emoji_width' preference: %d, overwriting default",
|
|
|
|
g_fish_emoji_width);
|
2019-04-01 10:51:08 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-03-26 11:18:00 +08:00
|
|
|
wcstring term;
|
|
|
|
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());
|
2019-11-19 10:34:50 +08:00
|
|
|
version = strtod(narrow_version.c_str(), nullptr);
|
2019-03-26 11:18:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (term == L"Apple_Terminal" && version >= 400) {
|
|
|
|
// Apple Terminal on High Sierra
|
|
|
|
g_guessed_fish_emoji_width = 2;
|
2020-01-19 20:39:23 +08:00
|
|
|
FLOGF(term_support, "default emoji width: 2 for %ls", term.c_str());
|
2019-03-26 11:18:00 +08:00
|
|
|
} 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;
|
2020-01-19 20:39:23 +08:00
|
|
|
FLOGF(term_support, "default emoji width: 1");
|
2019-03-26 11:18:00 +08:00
|
|
|
} 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;
|
2020-01-19 20:39:23 +08:00
|
|
|
FLOGF(term_support, "default emoji width: %d", g_guessed_fish_emoji_width);
|
2019-03-26 11:18:00 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// React to modifying the given variable.
|
2019-04-09 03:37:38 +08:00
|
|
|
void env_dispatch_var_change(const wcstring &key, env_stack_t &vars) {
|
2019-04-01 10:11:52 +08:00
|
|
|
ASSERT_IS_MAIN_THREAD();
|
|
|
|
// Do nothing if not yet fully initialized.
|
|
|
|
if (!s_var_dispatch_table) return;
|
|
|
|
|
2019-04-09 03:37:38 +08:00
|
|
|
s_var_dispatch_table->dispatch(key, vars);
|
2019-04-01 10:11:52 +08:00
|
|
|
|
|
|
|
// Eww.
|
|
|
|
if (string_prefixes_string(L"fish_color_", key)) {
|
2019-03-26 11:18:00 +08:00
|
|
|
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";
|
|
|
|
|
2019-04-09 03:37:38 +08:00
|
|
|
env_dispatch_var_change(cb.key, *stack);
|
2019-06-03 17:31:13 +08:00
|
|
|
|
|
|
|
// TODO: eliminate this principal_parser. Need to rationalize how multiple threads work here.
|
|
|
|
event_fire(parser_t::principal_parser(), event_t::variable(cb.key, {L"VARIABLE", op, cb.key}));
|
2019-03-26 11:18:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-14 04:59:10 +08:00
|
|
|
static void handle_fish_term_change(const env_stack_t &vars) {
|
2019-03-26 11:18:00 +08:00
|
|
|
update_fish_color_support(vars);
|
|
|
|
reader_react_to_color_change();
|
|
|
|
}
|
|
|
|
|
2020-03-14 04:59:10 +08:00
|
|
|
static void handle_change_ambiguous_width(const env_stack_t &vars) {
|
2019-03-26 11:18:00 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2020-03-14 04:59:10 +08:00
|
|
|
static void handle_term_size_change(const env_stack_t &vars) {
|
2019-03-26 11:18:00 +08:00
|
|
|
UNUSED(vars);
|
|
|
|
invalidate_termsize(true); // force fish to update its idea of the terminal size plus vars
|
|
|
|
}
|
|
|
|
|
2020-03-14 04:59:10 +08:00
|
|
|
static void handle_fish_history_change(const env_stack_t &vars) {
|
2019-03-26 11:18:00 +08:00
|
|
|
reader_change_history(history_session_id(vars));
|
|
|
|
}
|
|
|
|
|
2020-03-14 04:59:10 +08:00
|
|
|
static void handle_function_path_change(const env_stack_t &vars) {
|
2019-03-26 11:18:00 +08:00
|
|
|
UNUSED(vars);
|
|
|
|
function_invalidate_path();
|
|
|
|
}
|
|
|
|
|
2020-03-14 04:59:10 +08:00
|
|
|
static void handle_complete_path_change(const env_stack_t &vars) {
|
2019-03-26 11:18:00 +08:00
|
|
|
UNUSED(vars);
|
|
|
|
complete_invalidate_path();
|
|
|
|
}
|
|
|
|
|
2020-03-14 04:59:10 +08:00
|
|
|
static void handle_tz_change(const wcstring &var_name, const env_stack_t &vars) {
|
2019-03-26 11:18:00 +08:00
|
|
|
handle_timezone(var_name.c_str(), vars);
|
|
|
|
}
|
|
|
|
|
2019-04-01 10:51:08 +08:00
|
|
|
static void handle_locale_change(const environment_t &vars) {
|
2019-03-26 11:18:00 +08:00
|
|
|
init_locale(vars);
|
|
|
|
// We need to re-guess emoji width because the locale might have changed to a multibyte one.
|
2019-04-01 10:51:08 +08:00
|
|
|
guess_emoji_width(vars);
|
2019-03-26 11:18:00 +08:00
|
|
|
}
|
|
|
|
|
2019-04-01 10:51:08 +08:00
|
|
|
static void handle_curses_change(const environment_t &vars) {
|
|
|
|
guess_emoji_width(vars);
|
2019-03-26 11:18:00 +08:00
|
|
|
init_curses(vars);
|
|
|
|
}
|
|
|
|
|
2019-04-09 03:08:03 +08:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
|
2019-04-09 04:41:42 +08:00
|
|
|
/// 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) {
|
2020-01-19 20:38:47 +08:00
|
|
|
FLOGF(warning, "Ignoring fish_read_limit since it is not valid");
|
2019-04-09 04:41:42 +08:00
|
|
|
} else {
|
|
|
|
read_byte_limit = limit;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-26 11:18:00 +08:00
|
|
|
/// Populate the dispatch table used by `env_dispatch_var_change()` to efficiently call the
|
|
|
|
/// appropriate function to handle a change to a variable.
|
2019-04-01 10:11:52 +08:00
|
|
|
/// Note this returns a new-allocated value that we expect to leak.
|
|
|
|
static std::unique_ptr<const var_dispatch_table_t> create_dispatch_table() {
|
|
|
|
auto var_dispatch_table = make_unique<var_dispatch_table_t>();
|
2019-03-26 11:18:00 +08:00
|
|
|
for (const auto &var_name : locale_variables) {
|
2019-04-01 10:11:52 +08:00
|
|
|
var_dispatch_table->add(var_name, handle_locale_change);
|
2019-03-26 11:18:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
for (const auto &var_name : curses_variables) {
|
2019-04-01 10:11:52 +08:00
|
|
|
var_dispatch_table->add(var_name, handle_curses_change);
|
2019-03-26 11:18:00 +08:00
|
|
|
}
|
|
|
|
|
2019-04-01 10:11:52 +08:00
|
|
|
var_dispatch_table->add(L"fish_term256", handle_fish_term_change);
|
|
|
|
var_dispatch_table->add(L"fish_term24bit", handle_fish_term_change);
|
2019-04-01 10:51:08 +08:00
|
|
|
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);
|
2019-04-01 10:11:52 +08:00
|
|
|
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);
|
2019-04-09 03:08:03 +08:00
|
|
|
var_dispatch_table->add(L"fish_use_posix_spawn", handle_fish_use_posix_spawn_change);
|
2019-04-10 11:42:16 +08:00
|
|
|
|
|
|
|
// This std::move is required to avoid a build error on old versions of libc++ (#5801)
|
|
|
|
return std::move(var_dispatch_table);
|
2019-03-26 11:18:00 +08:00
|
|
|
}
|
2019-04-01 10:51:08 +08:00
|
|
|
|
|
|
|
static void run_inits(const environment_t &vars) {
|
|
|
|
// This is the subset of those dispatch functions which want to be run at startup.
|
2019-04-09 04:32:25 +08:00
|
|
|
init_locale(vars);
|
|
|
|
init_curses(vars);
|
|
|
|
guess_emoji_width(vars);
|
2019-04-01 10:51:08 +08:00
|
|
|
update_wait_on_escape_ms(vars);
|
2019-04-09 04:41:42 +08:00
|
|
|
handle_read_limit_change(vars);
|
2019-09-06 02:00:52 +08:00
|
|
|
handle_fish_use_posix_spawn_change(vars);
|
2019-04-01 10:51:08 +08:00
|
|
|
}
|
2019-04-09 03:08:03 +08:00
|
|
|
|
2019-04-09 04:32:25 +08:00
|
|
|
/// 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());
|
2020-01-19 20:39:23 +08:00
|
|
|
FLOGF(term_support, L"256 color support determined by '$fish_term256'");
|
2019-04-09 04:32:25 +08:00
|
|
|
} else if (term.find(L"256color") != wcstring::npos) {
|
|
|
|
// TERM is *256color*: 256 colors explicitly supported
|
|
|
|
support_term256 = true;
|
2020-01-19 20:39:23 +08:00
|
|
|
FLOGF(term_support, L"256 color support enabled for TERM=%ls", term.c_str());
|
2019-04-09 04:32:25 +08:00
|
|
|
} 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();
|
2020-03-03 06:27:54 +08:00
|
|
|
if (term_program == L"Apple_Terminal") {
|
|
|
|
auto tpv = vars.get(L"TERM_PROGRAM_VERSION");
|
|
|
|
if (tpv && fish_wcstod(tpv->as_string().c_str(), nullptr) > 299) {
|
2019-04-09 04:32:25 +08:00
|
|
|
// OS X Lion is version 299+, it has 256 color support (see github Wiki)
|
|
|
|
support_term256 = true;
|
2020-01-30 09:06:11 +08:00
|
|
|
FLOGF(term_support, L"256 color support enabled for TERM=%ls on Terminal.app",
|
|
|
|
term.c_str());
|
2019-04-09 04:32:25 +08:00
|
|
|
}
|
2020-03-03 06:27:54 +08:00
|
|
|
} else {
|
|
|
|
support_term256 = true;
|
|
|
|
debug(2, L"256 color support enabled for TERM=%ls", term.c_str());
|
2019-04-09 04:32:25 +08:00
|
|
|
}
|
2019-11-19 10:34:50 +08:00
|
|
|
} else if (cur_term != nullptr) {
|
2019-04-09 04:32:25 +08:00
|
|
|
// See if terminfo happens to identify 256 colors
|
|
|
|
support_term256 = (max_colors >= 256);
|
2020-01-19 20:39:23 +08:00
|
|
|
FLOGF(term_support, L"256 color support: %d colors per terminfo entry for %ls", max_colors,
|
2019-04-09 04:32:25 +08:00
|
|
|
term.c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle $fish_term24bit
|
|
|
|
if (auto fish_term24bit = vars.get(L"fish_term24bit")) {
|
|
|
|
support_term24bit = bool_from_string(fish_term24bit->as_string());
|
2020-01-19 20:39:23 +08:00
|
|
|
FLOGF(term_support, L"'fish_term24bit' preference: 24-bit color %ls",
|
2019-04-09 04:32:25 +08:00
|
|
|
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;
|
|
|
|
|
2019-12-12 20:50:33 +08:00
|
|
|
if (session_interactivity() != session_interactivity_t::not_interactive)
|
2020-01-19 20:38:47 +08:00
|
|
|
FLOGF(warning, _(L"Using fallback terminal type '%s'."), term);
|
2019-04-09 04:32:25 +08:00
|
|
|
|
|
|
|
int err_ret;
|
2019-11-19 09:08:16 +08:00
|
|
|
if (setupterm(const_cast<char *>(term), STDOUT_FILENO, &err_ret) == OK) return true;
|
2019-12-12 20:50:33 +08:00
|
|
|
if (session_interactivity() != session_interactivity_t::not_interactive) {
|
2020-01-30 09:06:11 +08:00
|
|
|
FLOGF(warning, _(L"Could not set up terminal using the fallback terminal type '%s'."),
|
|
|
|
term);
|
2019-04-09 04:32:25 +08:00
|
|
|
}
|
|
|
|
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.
|
Add "alacritty" to the list of known titleable TERMs
This change is necessary to fix dynamic titles for the Alacritty
terminal. We do this by simply adding the (wchar_t *) literal
L"alacritty" to the end of the title_terms array. This variable is
ultimately used in the subsequent function
does_term_support_setting_title (dtsst) for the purposes of whitelisting
certain terminals.
If an Alacritty user does not have the terminfo for alacritty present in
their terminfo database, Alacritty sets the TERM variable to
"xterm-256color", but if the terminfo for Alacritty is present, TERM is
instead set to "alacritty".
Prior to this change, none of the "fallback patterns" in the dtsst
function (which is used to ultimately decide whether or not a given
value of the TERM environment variable is supported) would apply to a
value of "alacritty". Ordinarily, the dtsst function would return true
if nothing matches, but one of the final checks involves testing the
result of ttyname_r to see if it contains the substring "tty", which
causes dtsst to return false. In the case where TERM="alacritty", this
is erroneous, because Alacritty does, indeed, support changing its title
and will also silently ignore attempts to change the title if that
behavior has been disabled by the user [1].
The changed file, src/env_dispatch.cpp, was reformatted by clang-format
in accordance with the documented procedures for contributors.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
[1]: https://github.com/alacritty/alacritty/blob/1dacc99183373bffa3ba287aa3241f3b1da67016/alacritty_terminal/src/term/mod.rs#L896-L900
2020-06-02 05:55:19 +08:00
|
|
|
static const wchar_t *const title_terms[] = {L"xterm", L"screen", L"tmux",
|
|
|
|
L"nxterm", L"rxvt", L"alacritty"};
|
2019-04-09 04:32:25 +08:00
|
|
|
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()) {
|
2020-01-19 20:39:23 +08:00
|
|
|
FLOGF(term_support, L"curses var %s missing or empty", name.c_str());
|
2019-05-23 07:09:59 +08:00
|
|
|
unsetenv_lock(name.c_str());
|
2019-04-09 04:32:25 +08:00
|
|
|
} else {
|
|
|
|
std::string value = wcs2string(var->as_string());
|
2020-01-19 20:39:23 +08:00
|
|
|
FLOGF(term_support, L"curses var %s='%s'", name.c_str(), value.c_str());
|
2019-05-23 07:09:59 +08:00
|
|
|
setenv_lock(name.c_str(), value.c_str(), 1);
|
2019-04-09 04:32:25 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int err_ret;
|
2019-11-19 10:34:50 +08:00
|
|
|
if (setupterm(nullptr, STDOUT_FILENO, &err_ret) == ERR) {
|
2019-04-09 04:32:25 +08:00
|
|
|
auto term = vars.get(L"TERM");
|
2019-12-12 20:50:33 +08:00
|
|
|
if (session_interactivity() != session_interactivity_t::not_interactive) {
|
2020-01-19 20:38:47 +08:00
|
|
|
FLOGF(warning, _(L"Could not set up terminal."));
|
2019-04-09 04:32:25 +08:00
|
|
|
if (term.missing_or_empty()) {
|
2020-01-19 20:38:47 +08:00
|
|
|
FLOGF(warning, _(L"TERM environment variable not set."));
|
2019-04-09 04:32:25 +08:00
|
|
|
} else {
|
2020-01-30 09:06:11 +08:00
|
|
|
FLOGF(warning, _(L"TERM environment variable set to '%ls'."),
|
|
|
|
term->as_string().c_str());
|
2020-01-19 20:38:47 +08:00
|
|
|
FLOGF(warning, _(L"Check that this terminal type is supported on this system."));
|
2019-04-09 04:32:25 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!initialize_curses_using_fallback(DEFAULT_TERM1)) {
|
|
|
|
initialize_curses_using_fallback(DEFAULT_TERM2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
can_set_term_title = does_term_support_setting_title(vars);
|
2020-04-09 07:56:59 +08:00
|
|
|
term_has_xn =
|
|
|
|
tigetflag(const_cast<char *>("xenl")) == 1; // does terminal have the eat_newline_glitch
|
2019-04-09 04:32:25 +08:00
|
|
|
update_fish_color_support(vars);
|
|
|
|
// Invalidate the cached escape sequences since they may no longer be valid.
|
2020-05-31 05:59:35 +08:00
|
|
|
layout_cache_t::shared.clear();
|
2019-04-09 04:32:25 +08:00
|
|
|
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.
|
2019-11-19 10:34:50 +08:00
|
|
|
char *old_msg_locale = strdup(setlocale(LC_MESSAGES, nullptr));
|
2019-04-09 04:32:25 +08:00
|
|
|
|
|
|
|
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()) {
|
2019-05-19 06:16:42 +08:00
|
|
|
FLOGF(env_locale, L"locale var %s missing or empty", name.c_str());
|
2019-05-23 07:09:59 +08:00
|
|
|
unsetenv_lock(name.c_str());
|
2019-04-09 04:32:25 +08:00
|
|
|
} else {
|
|
|
|
const std::string value = wcs2string(var->as_string());
|
2019-05-19 06:16:42 +08:00
|
|
|
FLOGF(env_locale, L"locale var %s='%s'", name.c_str(), value.c_str());
|
2019-05-23 07:09:59 +08:00
|
|
|
setenv_lock(name.c_str(), value.c_str(), 1);
|
2019-04-09 04:32:25 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
char *locale = setlocale(LC_ALL, "");
|
|
|
|
fish_setlocale();
|
2019-05-19 06:16:42 +08:00
|
|
|
FLOGF(env_locale, L"init_locale() setlocale(): '%s'", locale);
|
2019-04-09 04:32:25 +08:00
|
|
|
|
2019-11-19 10:34:50 +08:00
|
|
|
const char *new_msg_locale = setlocale(LC_MESSAGES, nullptr);
|
2019-05-19 06:16:42 +08:00
|
|
|
FLOGF(env_locale, L"old LC_MESSAGES locale: '%s'", old_msg_locale);
|
|
|
|
FLOGF(env_locale, L"new LC_MESSAGES locale: '%s'", new_msg_locale);
|
2019-04-09 04:32:25 +08:00
|
|
|
#ifdef HAVE__NL_MSG_CAT_CNTR
|
2019-11-26 09:31:57 +08:00
|
|
|
if (std::strcmp(old_msg_locale, new_msg_locale) != 0) {
|
2019-04-09 04:32:25 +08:00
|
|
|
// 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.
|
2019-04-09 03:08:03 +08:00
|
|
|
bool g_use_posix_spawn = false;
|
2019-04-09 04:41:42 +08:00
|
|
|
|
2019-05-29 17:01:45 +08:00
|
|
|
// Limit `read` to 100 MiB (bytes not wide chars) by default. This can be overridden by the
|
2019-04-09 04:41:42 +08:00
|
|
|
// fish_read_limit variable.
|
2019-05-29 17:01:45 +08:00
|
|
|
size_t read_byte_limit = 100 * 1024 * 1024;
|