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 <time.h>
|
|
|
|
#include <unistd.h>
|
2019-10-14 06:50:48 +08:00
|
|
|
|
2022-08-21 14:14:48 +08:00
|
|
|
#include <cstdlib>
|
2019-03-26 11:18:00 +08:00
|
|
|
#include <cstring>
|
|
|
|
#include <cwchar>
|
|
|
|
|
2023-04-09 18:12:13 +08:00
|
|
|
#include "ffi_init.rs.h"
|
|
|
|
|
2019-03-26 11:18:00 +08:00
|
|
|
#if HAVE_CURSES_H
|
2022-08-22 05:51:33 +08:00
|
|
|
#include <curses.h> // IWYU pragma: keep
|
2019-03-26 11:18:00 +08:00
|
|
|
#elif HAVE_NCURSES_H
|
2022-08-22 05:51:33 +08:00
|
|
|
#include <ncurses.h> // IWYU pragma: keep
|
2019-03-26 11:18:00 +08:00
|
|
|
#elif HAVE_NCURSES_CURSES_H
|
2022-08-22 05:51:33 +08:00
|
|
|
#include <ncurses/curses.h> // IWYU pragma: keep
|
2019-03-26 11:18:00 +08:00
|
|
|
#endif
|
|
|
|
#if HAVE_TERM_H
|
|
|
|
#include <term.h>
|
|
|
|
#elif HAVE_NCURSES_TERM_H
|
|
|
|
#include <ncurses/term.h>
|
|
|
|
#endif
|
|
|
|
|
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"
|
2020-09-09 04:04:44 +08:00
|
|
|
#include "env_dispatch.h"
|
2019-03-26 11:18:00 +08:00
|
|
|
#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"
|
|
|
|
#include "proc.h"
|
|
|
|
#include "reader.h"
|
|
|
|
#include "screen.h"
|
2020-06-08 10:35:47 +08:00
|
|
|
#include "termsize.h"
|
2023-03-28 23:59:51 +08:00
|
|
|
#include "trace.rs.h"
|
2022-08-21 14:14:48 +08:00
|
|
|
#include "wcstringutil.h"
|
|
|
|
#include "wutil.h"
|
2022-08-09 21:13:01 +08:00
|
|
|
|
|
|
|
// Limit `read` to 100 MiB (bytes not wide chars) by default. This can be overridden by the
|
|
|
|
// fish_read_limit variable.
|
|
|
|
constexpr size_t DEFAULT_READ_BYTE_LIMIT = 100 * 1024 * 1024;
|
|
|
|
|
2019-03-26 11:18:00 +08:00
|
|
|
/// List of all locale environment variable names that might trigger (re)initializing the locale
|
2021-12-13 18:52:17 +08:00
|
|
|
/// subsystem. These are only the variables we're possibly interested in.
|
2022-04-17 03:22:44 +08:00
|
|
|
static const wcstring locale_variables[] = {
|
|
|
|
L"LANG", L"LANGUAGE", L"LC_ALL",
|
|
|
|
L"LC_COLLATE", L"LC_CTYPE", L"LC_MESSAGES",
|
|
|
|
L"LC_NUMERIC", L"LC_TIME", L"fish_allow_singlebyte_locale",
|
|
|
|
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.
|
2021-05-23 03:50:26 +08:00
|
|
|
static const wcstring 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) {
|
2023-04-20 18:24:53 +08:00
|
|
|
const auto var = vars.get_unless_empty(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,
|
2023-04-20 18:24:53 +08:00
|
|
|
!var ? L"MISSING/EMPTY" : var->as_string().c_str());
|
2023-04-01 19:50:30 +08:00
|
|
|
std::string name = wcs2zstring(env_var_name);
|
2023-04-20 18:24:53 +08:00
|
|
|
if (!var) {
|
2019-05-23 07:09:59 +08:00
|
|
|
unsetenv_lock(name.c_str());
|
2019-03-26 11:18:00 +08:00
|
|
|
} else {
|
2023-04-01 19:50:30 +08:00
|
|
|
const std::string value = wcs2zstring(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();
|
|
|
|
}
|
|
|
|
|
2023-05-17 03:09:30 +08:00
|
|
|
/// Update the value of 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());
|
2023-05-17 03:09:30 +08:00
|
|
|
FISH_EMOJI_WIDTH = std::min(2, std::max(1, new_width));
|
2020-01-30 09:06:11 +08:00
|
|
|
FLOGF(term_support, "'fish_emoji_width' preference: %d, overwriting default",
|
2023-05-17 03:09:30 +08:00
|
|
|
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")) {
|
2023-04-01 19:50:30 +08:00
|
|
|
std::string narrow_version = wcs2zstring(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
|
2023-05-17 03:09:30 +08:00
|
|
|
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") {
|
2021-08-17 18:53:19 +08:00
|
|
|
// iTerm2 now defaults to Unicode 9 sizes for anything after macOS 10.12.
|
2023-05-17 03:09:30 +08:00
|
|
|
FISH_EMOJI_WIDTH = 2;
|
2021-08-17 18:53:19 +08:00
|
|
|
FLOGF(term_support, "default emoji width for iTerm: 2");
|
2019-03-26 11:18:00 +08:00
|
|
|
} else {
|
|
|
|
// Default to whatever system wcwidth says to U+1F603,
|
2021-09-23 21:29:21 +08:00
|
|
|
// but only if it's at least 1 and at most 2.
|
2019-03-26 11:18:00 +08:00
|
|
|
int w = wcwidth(L'😃');
|
2023-05-17 03:09:30 +08:00
|
|
|
FISH_EMOJI_WIDTH = std::min(2, std::max(1, w));
|
|
|
|
FLOGF(term_support, "default emoji width: %d", 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
|
|
|
// 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-03-26 11:18:00 +08:00
|
|
|
}
|
|
|
|
|
2023-04-30 10:58:51 +08:00
|
|
|
void env_dispatch_var_change_ffi(const wcstring &key) {
|
|
|
|
return env_dispatch_var_change(key, env_stack_t::principal());
|
|
|
|
}
|
|
|
|
|
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);
|
2020-08-24 06:12:47 +08:00
|
|
|
reader_schedule_prompt_repaint();
|
2019-03-26 11:18:00 +08:00
|
|
|
}
|
|
|
|
|
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());
|
|
|
|
}
|
2023-05-17 03:09:30 +08:00
|
|
|
FISH_AMBIGUOUS_WIDTH = std::max(0, new_width);
|
2019-03-26 11:18:00 +08:00
|
|
|
}
|
|
|
|
|
2020-03-14 04:59:10 +08:00
|
|
|
static void handle_term_size_change(const env_stack_t &vars) {
|
2023-03-20 06:50:33 +08:00
|
|
|
// Need to use a pointer to send this through cxx ffi.
|
|
|
|
const environment_t &env_vars = vars;
|
|
|
|
handle_columns_lines_var_change_ffi(reinterpret_cast<const unsigned char *>(&env_vars));
|
2019-03-26 11:18:00 +08:00
|
|
|
}
|
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2022-07-02 23:08:59 +08:00
|
|
|
static void handle_fish_cursor_selection_mode_change(const env_stack_t &vars) {
|
|
|
|
auto mode = vars.get(L"fish_cursor_selection_mode");
|
|
|
|
reader_change_cursor_selection_mode(mode && mode->as_string() == L"inclusive"
|
|
|
|
? cursor_selection_mode_t::inclusive
|
|
|
|
: cursor_selection_mode_t::exclusive);
|
|
|
|
}
|
|
|
|
|
2021-11-14 18:08:30 +08:00
|
|
|
void handle_autosuggestion_change(const env_stack_t &vars) {
|
|
|
|
reader_set_autosuggestion_enabled(vars);
|
2021-10-21 22:54:24 +08:00
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-08-23 05:05:23 +08:00
|
|
|
/// Whether to use posix_spawn when possible.
|
|
|
|
static relaxed_atomic_bool_t g_use_posix_spawn{false};
|
|
|
|
|
|
|
|
bool get_use_posix_spawn() { return g_use_posix_spawn; }
|
|
|
|
|
|
|
|
static bool allow_use_posix_spawn() {
|
2021-09-19 05:00:17 +08:00
|
|
|
// OpenBSD's posix_spawn returns status 127, instead of erroring with ENOEXEC, when faced with a
|
|
|
|
// shebangless script. Disable posix_spawn on OpenBSD.
|
|
|
|
#if defined(__OpenBSD__)
|
|
|
|
return false;
|
2022-08-22 05:51:33 +08:00
|
|
|
#elif defined(__GLIBC__) && !defined(__UCLIBC__) // uClibc defines __GLIBC__
|
2021-10-16 12:12:26 +08:00
|
|
|
// Disallow posix_spawn entirely on glibc < 2.24.
|
|
|
|
// See #8021.
|
2022-08-22 05:16:42 +08:00
|
|
|
return __GLIBC_PREREQ(2, 24) ? true : false;
|
2022-08-22 05:51:33 +08:00
|
|
|
#else // !defined(__OpenBSD__)
|
2022-08-22 05:16:42 +08:00
|
|
|
return true;
|
|
|
|
#endif
|
2022-08-23 05:05:23 +08:00
|
|
|
return true;
|
2022-08-23 04:50:06 +08:00
|
|
|
}
|
2022-08-22 07:25:26 +08:00
|
|
|
|
2019-04-09 03:08:03 +08:00
|
|
|
static void handle_fish_use_posix_spawn_change(const environment_t &vars) {
|
2022-08-23 05:05:23 +08:00
|
|
|
// Note if the variable is missing or empty, we default to true if allowed.
|
|
|
|
if (!allow_use_posix_spawn()) {
|
|
|
|
g_use_posix_spawn = false;
|
|
|
|
} else if (auto var = vars.get(L"fish_use_posix_spawn")) {
|
2021-06-01 03:34:43 +08:00
|
|
|
g_use_posix_spawn = var->empty() || bool_from_string(var->as_string());
|
|
|
|
} else {
|
|
|
|
g_use_posix_spawn = true;
|
|
|
|
}
|
2019-04-09 03:08:03 +08:00
|
|
|
}
|
|
|
|
|
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) {
|
2023-04-20 18:24:53 +08:00
|
|
|
auto read_byte_limit_var = vars.get_unless_empty(L"fish_read_limit");
|
|
|
|
if (read_byte_limit_var) {
|
2019-04-09 04:41:42 +08:00
|
|
|
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 {
|
2023-05-17 03:35:14 +08:00
|
|
|
READ_BYTE_LIMIT = limit;
|
2019-04-09 04:41:42 +08:00
|
|
|
}
|
2022-08-09 21:13:01 +08:00
|
|
|
} else {
|
2023-05-17 03:35:14 +08:00
|
|
|
READ_BYTE_LIMIT = DEFAULT_READ_BYTE_LIMIT;
|
2019-04-09 04:41:42 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-29 01:38:41 +08:00
|
|
|
static void handle_fish_trace(const environment_t &vars) {
|
2023-04-20 18:24:53 +08:00
|
|
|
trace_set_enabled(vars.get_unless_empty(L"fish_trace").has_value());
|
2021-10-29 01:38:41 +08:00
|
|
|
}
|
|
|
|
|
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);
|
2021-10-21 22:54:24 +08:00
|
|
|
var_dispatch_table->add(L"fish_autosuggestion_enabled", handle_autosuggestion_change);
|
2019-04-01 10:11:52 +08:00
|
|
|
var_dispatch_table->add(L"TZ", handle_tz_change);
|
2022-08-23 05:05:23 +08:00
|
|
|
var_dispatch_table->add(L"fish_use_posix_spawn", handle_fish_use_posix_spawn_change);
|
2021-10-29 01:38:41 +08:00
|
|
|
var_dispatch_table->add(L"fish_trace", handle_fish_trace);
|
2022-07-02 23:08:59 +08:00
|
|
|
var_dispatch_table->add(L"fish_cursor_selection_mode",
|
|
|
|
handle_fish_cursor_selection_mode_change);
|
2019-04-10 11:42:16 +08:00
|
|
|
|
2021-02-23 05:53:31 +08:00
|
|
|
// This std::move is required to avoid a build error on old versions of libc++ (#5801),
|
|
|
|
// but it causes a different warning under newer versions of GCC (observed under GCC 9.3.0,
|
|
|
|
// but not under llvm/clang 9).
|
|
|
|
#if __GNUC__ > 4
|
|
|
|
#pragma GCC diagnostic push
|
|
|
|
#pragma GCC diagnostic ignored "-Wredundant-move"
|
|
|
|
#endif
|
2019-04-10 11:42:16 +08:00
|
|
|
return std::move(var_dispatch_table);
|
2021-02-23 05:53:31 +08:00
|
|
|
#if __GNUC__ > 4
|
|
|
|
#pragma GCC diagnostic pop
|
|
|
|
#endif
|
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);
|
2022-08-23 05:05:23 +08:00
|
|
|
handle_fish_use_posix_spawn_change(vars);
|
2021-10-29 01:38:41 +08:00
|
|
|
handle_fish_trace(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) {
|
2021-10-07 10:34:43 +08:00
|
|
|
// Assume that all 'xterm's can handle 25
|
|
|
|
support_term256 = true;
|
|
|
|
FLOGF(term_support, L"256 color support enabled for TERM=%ls", term.c_str());
|
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 {
|
2020-08-29 04:37:37 +08:00
|
|
|
if (vars.get(L"STY") || string_prefixes_string(L"eterm", term)) {
|
|
|
|
// Screen and emacs' ansi-term swallow truecolor sequences,
|
|
|
|
// so we ignore them unless force-enabled.
|
|
|
|
FLOGF(term_support, L"Truecolor support: disabling for eterm/screen");
|
|
|
|
support_term24bit = false;
|
|
|
|
} else if (cur_term != nullptr && max_colors >= 32767) {
|
|
|
|
// $TERM wins, xterm-direct reports 32767 colors, we assume that's the minimum
|
|
|
|
// as xterm is weird when it comes to color.
|
2020-09-09 04:04:44 +08:00
|
|
|
FLOGF(term_support, L"Truecolor support: Enabling per terminfo for %ls with %d colors",
|
|
|
|
term.c_str(), max_colors);
|
2020-08-29 04:37:37 +08:00
|
|
|
support_term24bit = true;
|
|
|
|
} else {
|
|
|
|
if (auto ct = vars.get(L"COLORTERM")) {
|
|
|
|
// If someone set $COLORTERM, that's the sort of color they want.
|
2020-09-09 04:04:44 +08:00
|
|
|
if (ct->as_string() == L"truecolor" || ct->as_string() == L"24bit") {
|
|
|
|
FLOGF(term_support, L"Truecolor support: Enabling per $COLORTERM='%ls'",
|
|
|
|
ct->as_string().c_str());
|
2020-08-29 04:37:37 +08:00
|
|
|
support_term24bit = true;
|
|
|
|
}
|
2020-09-09 04:04:44 +08:00
|
|
|
} else if (vars.get(L"KONSOLE_VERSION") || vars.get(L"KONSOLE_PROFILE_NAME")) {
|
2020-08-29 04:37:37 +08:00
|
|
|
// All konsole versions that use $KONSOLE_VERSION are new enough to support this,
|
|
|
|
// so no check is necessary.
|
|
|
|
FLOGF(term_support, L"Truecolor support: Enabling for Konsole");
|
|
|
|
support_term24bit = true;
|
|
|
|
} else if (auto it = vars.get(L"ITERM_SESSION_ID")) {
|
|
|
|
// Supporting versions of iTerm include a colon here.
|
|
|
|
// We assume that if this is iTerm, it can't also be st, so having this check
|
|
|
|
// inside is okay.
|
2020-09-28 08:36:23 +08:00
|
|
|
if (it->as_string().find(L':') != wcstring::npos) {
|
2020-08-29 04:37:37 +08:00
|
|
|
FLOGF(term_support, L"Truecolor support: Enabling for ITERM");
|
|
|
|
support_term24bit = true;
|
|
|
|
}
|
|
|
|
} else if (string_prefixes_string(L"st-", term)) {
|
|
|
|
FLOGF(term_support, L"Truecolor support: Enabling for st");
|
|
|
|
support_term24bit = true;
|
|
|
|
} else if (auto vte = vars.get(L"VTE_VERSION")) {
|
2022-03-25 22:56:31 +08:00
|
|
|
if (fish_wcstod(vte->as_string(), nullptr) > 3600) {
|
2020-09-09 04:04:44 +08:00
|
|
|
FLOGF(term_support, L"Truecolor support: Enabling for VTE version %ls",
|
|
|
|
vte->as_string().c_str());
|
2020-08-29 04:37:37 +08:00
|
|
|
support_term24bit = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-04-09 04:32:25 +08:00
|
|
|
}
|
|
|
|
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
|
2022-06-21 00:20:15 +08:00
|
|
|
// `TERM` to our fallback. We're only doing this in the hope of getting a functional
|
2019-04-09 04:32:25 +08:00
|
|
|
// shell. If we launch an external command that uses TERM it should get the same value we were
|
|
|
|
// given, if any.
|
2022-04-17 03:43:14 +08:00
|
|
|
static void initialize_curses_using_fallbacks(const environment_t &vars) {
|
2022-06-21 00:20:15 +08:00
|
|
|
// xterm-256color is the most used terminal type by a massive margin,
|
|
|
|
// especially counting terminals that are mostly compatible.
|
|
|
|
const wchar_t *const fallbacks[] = {L"xterm-256color", L"xterm", L"ansi", L"dumb"};
|
2022-04-17 03:43:14 +08:00
|
|
|
|
2022-06-21 00:20:15 +08:00
|
|
|
wcstring termstr = L"";
|
2023-04-20 18:24:53 +08:00
|
|
|
auto term_var = vars.get_unless_empty(L"TERM");
|
|
|
|
if (term_var) {
|
2022-06-21 00:20:15 +08:00
|
|
|
termstr = term_var->as_string();
|
2022-04-17 03:22:44 +08:00
|
|
|
}
|
2019-04-09 04:32:25 +08:00
|
|
|
|
2022-04-17 03:43:14 +08:00
|
|
|
for (const wchar_t *fallback : fallbacks) {
|
|
|
|
// 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.
|
2022-06-21 00:20:15 +08:00
|
|
|
if (termstr == fallback) {
|
2022-04-17 03:43:14 +08:00
|
|
|
continue;
|
|
|
|
}
|
2019-04-09 04:32:25 +08:00
|
|
|
|
2022-04-17 03:43:14 +08:00
|
|
|
int err_ret = 0;
|
2023-04-01 19:50:30 +08:00
|
|
|
std::string term = wcs2zstring(fallback);
|
2022-04-17 03:43:14 +08:00
|
|
|
bool success = (setupterm(&term[0], STDOUT_FILENO, &err_ret) == OK);
|
2019-04-09 04:32:25 +08:00
|
|
|
|
2022-04-17 03:43:14 +08:00
|
|
|
if (is_interactive_session()) {
|
|
|
|
if (success) {
|
|
|
|
FLOGF(warning, _(L"Using fallback terminal type '%s'."), term.c_str());
|
|
|
|
} else {
|
|
|
|
FLOGF(warning,
|
|
|
|
_(L"Could not set up terminal using the fallback terminal type '%s'."),
|
|
|
|
term.c_str());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (success) {
|
|
|
|
break;
|
|
|
|
}
|
2019-04-09 04:32:25 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-17 04:00:14 +08:00
|
|
|
// Apply any platform-specific hacks to cur_term/
|
|
|
|
static void apply_term_hacks(const environment_t &vars) {
|
|
|
|
UNUSED(vars);
|
Add workaround for Midnight Commander's issue with prompt extraction
When we draw the prompt, we move the cursor to the actual
position *we* think it is by issuing a carriage return (via
`move(0,0)`), and then going forward until we hit the spot.
This helps when the terminal and fish disagree on the width of the
prompt, because we are now definitely in the correct place, so we can
only overwrite a bit of the prompt (if it renders longer than we
expected) or leave space after the prompt. Both of these are benign in
comparison to staircase effects we would otherwise get.
Unfortunately, midnight commander ("mc") tries to extract the last
line of the prompt, and does so in a way that is overly naive - it
resets everything to 0 when it sees a `\r`, and doesn't account for
cursor movement. In effect it's playing a terminal, but not committing
to the bit.
Since this has been an open request in mc for quite a while, we hack
around it, by checking the $MC_SID environment variable.
If we see it, we skip the clearing. We end up most likely doing
relative movement from where we think we are, and in most cases it
should be *fine*.
2023-02-05 01:57:41 +08:00
|
|
|
// Midnight Commander tries to extract the last line of the prompt,
|
|
|
|
// and does so in a way that is broken if you do `\r` after it,
|
|
|
|
// like we normally do.
|
|
|
|
// See https://midnight-commander.org/ticket/4258.
|
|
|
|
if (auto var = vars.get(L"MC_SID")) {
|
|
|
|
screen_set_midnight_commander_hack();
|
|
|
|
}
|
|
|
|
|
2022-04-17 04:25:38 +08:00
|
|
|
// Be careful, variables like "enter_italics_mode" are #defined to dereference through cur_term.
|
|
|
|
// See #8876.
|
|
|
|
if (!cur_term) {
|
|
|
|
return;
|
|
|
|
}
|
2022-04-17 04:00:14 +08:00
|
|
|
#ifdef __APPLE__
|
|
|
|
// Hack in missing italics and dim capabilities omitted from MacOS xterm-256color terminfo
|
|
|
|
// Helps Terminal.app/iTerm
|
|
|
|
wcstring term_prog;
|
|
|
|
if (auto var = vars.get(L"TERM_PROGRAM")) {
|
|
|
|
term_prog = var->as_string();
|
|
|
|
}
|
|
|
|
if (term_prog == L"Apple_Terminal" || term_prog == L"iTerm.app") {
|
|
|
|
const auto term = vars.get(L"TERM");
|
|
|
|
if (term && term->as_string() == L"xterm-256color") {
|
|
|
|
static char sitm_esc[] = "\x1B[3m";
|
|
|
|
static char ritm_esc[] = "\x1B[23m";
|
|
|
|
static char dim_esc[] = "\x1B[2m";
|
|
|
|
|
|
|
|
if (!enter_italics_mode) {
|
|
|
|
enter_italics_mode = sitm_esc;
|
|
|
|
}
|
|
|
|
if (!exit_italics_mode) {
|
|
|
|
exit_italics_mode = ritm_esc;
|
|
|
|
}
|
|
|
|
if (!enter_dim_mode) {
|
|
|
|
enter_dim_mode = dim_esc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2019-04-09 04:32:25 +08:00
|
|
|
/// 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.
|
2021-11-09 03:36:01 +08:00
|
|
|
static const wchar_t *const title_terms[] = {L"xterm", L"screen", L"tmux", L"nxterm",
|
|
|
|
L"rxvt", L"alacritty", L"wezterm"};
|
2019-04-09 04:32:25 +08:00
|
|
|
static bool does_term_support_setting_title(const environment_t &vars) {
|
2023-04-20 18:24:53 +08:00
|
|
|
const auto term_var = vars.get_unless_empty(L"TERM");
|
|
|
|
if (!term_var) return false;
|
2019-04-09 04:32:25 +08:00
|
|
|
|
|
|
|
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());
|
2021-02-09 05:09:10 +08:00
|
|
|
if (!recognized) recognized = !std::wcsncmp(term, L"xterm-", const_strlen(L"xterm-"));
|
|
|
|
if (!recognized) recognized = !std::wcsncmp(term, L"screen-", const_strlen(L"screen-"));
|
|
|
|
if (!recognized) recognized = !std::wcsncmp(term, L"tmux-", const_strlen(L"tmux-"));
|
2019-04-09 04:32:25 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-05-02 01:04:26 +08:00
|
|
|
extern "C" {
|
|
|
|
void env_cleanup() {
|
|
|
|
if (cur_term != nullptr) {
|
|
|
|
del_curterm(cur_term);
|
|
|
|
cur_term = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-09 04:32:25 +08:00
|
|
|
/// Initialize the curses subsystem.
|
|
|
|
static void init_curses(const environment_t &vars) {
|
|
|
|
for (const auto &var_name : curses_variables) {
|
2023-04-01 19:50:30 +08:00
|
|
|
std::string name = wcs2zstring(var_name);
|
2023-04-20 18:24:53 +08:00
|
|
|
const auto var = vars.get_unless_empty(var_name, ENV_EXPORT);
|
|
|
|
if (!var) {
|
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 {
|
2023-04-01 19:50:30 +08:00
|
|
|
std::string value = wcs2zstring(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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-02 01:04:26 +08:00
|
|
|
// init_curses() is called more than once, which can lead to a memory leak if the previous
|
|
|
|
// ncurses TERMINAL isn't freed before initializing it again with `setupterm()`.
|
|
|
|
env_cleanup();
|
|
|
|
|
2022-04-17 04:00:14 +08:00
|
|
|
int err_ret{0};
|
2019-11-19 10:34:50 +08:00
|
|
|
if (setupterm(nullptr, STDOUT_FILENO, &err_ret) == ERR) {
|
2020-12-07 05:40:45 +08:00
|
|
|
if (is_interactive_session()) {
|
2023-04-20 18:24:53 +08:00
|
|
|
auto term = vars.get_unless_empty(L"TERM");
|
2020-01-19 20:38:47 +08:00
|
|
|
FLOGF(warning, _(L"Could not set up terminal."));
|
2023-04-20 18:24:53 +08:00
|
|
|
if (!term) {
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-17 03:43:14 +08:00
|
|
|
initialize_curses_using_fallbacks(vars);
|
2019-04-09 04:32:25 +08:00
|
|
|
}
|
|
|
|
|
2022-04-17 04:00:14 +08:00
|
|
|
apply_term_hacks(vars);
|
|
|
|
|
2019-04-09 04:32:25 +08:00
|
|
|
can_set_term_title = does_term_support_setting_title(vars);
|
2023-05-17 03:35:14 +08:00
|
|
|
TERM_HAS_XN =
|
2020-04-09 07:56:59 +08:00
|
|
|
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();
|
2023-05-17 03:35:14 +08:00
|
|
|
CURSES_INITIALIZED = true;
|
2019-04-09 04:32:25 +08:00
|
|
|
}
|
|
|
|
|
2021-08-21 06:26:27 +08:00
|
|
|
static constexpr const char *utf8_locales[] = {
|
2021-11-09 03:36:01 +08:00
|
|
|
"C.UTF-8", "en_US.UTF-8", "en_GB.UTF-8", "de_DE.UTF-8", "C.utf8", "UTF-8",
|
Try to set LC_CTYPE to something UTF-8 capable (#8031)
* Try to set LC_CTYPE to something UTF-8 capable
When fish is started with LC_CTYPE=C (even just effectively, often via
LC_ALL=C!), it's basically broken. There's no way to handle non-ASCII
characters with a C locale unless we want to write our
locale-independent replacements for all of the system functions.
Since we're not going to do that, let's try to find *some locale* for
LC_CTYPE.
We already do that in __fish_setlocale, but that's
- a bit of a weird thing that reads unstandardized system
configuration files
- allows setting locale to C explicitly
So it's still easily possible to end up in a broken configuration.
Now, the issue with this is that there is (AFAICT) no portable way to
get a list of all allowed locales and C.UTF-8 is not standardized, so
we have no one locale to fall back on and are forced to try a few. The
list we have here is quite arbitrary, but it's a start.
Python does something similar and only tries C.UTF-8, C.utf8 and
"UTF-8".
Once C.UTF-8 is (hopefully) standardized, that will just start
working (tm).
Note that we do not *export* the fixed LC_CTYPE variable, so external
programs still have to deal with the C locale, but we have no real
business messing with the user's environment.
To turn it off: $fish_allow_singlebyte_locale, if set to something true (like "1"),
will re-run the locale initialization and skip the bit where we force
LC_CTYPE to be utf8-capable.
This is mainly used in our tests, but might also be useful if people
are trying to do something weird.
2021-06-06 15:28:32 +08:00
|
|
|
};
|
|
|
|
|
2019-04-09 04:32:25 +08:00
|
|
|
/// 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) {
|
2023-04-20 18:24:53 +08:00
|
|
|
const auto var = vars.get_unless_empty(var_name, ENV_EXPORT);
|
2023-04-01 19:50:30 +08:00
|
|
|
std::string name = wcs2zstring(var_name);
|
2023-04-20 18:24:53 +08:00
|
|
|
if (!var) {
|
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 {
|
2023-04-01 19:50:30 +08:00
|
|
|
const std::string value = wcs2zstring(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, "");
|
Try to set LC_CTYPE to something UTF-8 capable (#8031)
* Try to set LC_CTYPE to something UTF-8 capable
When fish is started with LC_CTYPE=C (even just effectively, often via
LC_ALL=C!), it's basically broken. There's no way to handle non-ASCII
characters with a C locale unless we want to write our
locale-independent replacements for all of the system functions.
Since we're not going to do that, let's try to find *some locale* for
LC_CTYPE.
We already do that in __fish_setlocale, but that's
- a bit of a weird thing that reads unstandardized system
configuration files
- allows setting locale to C explicitly
So it's still easily possible to end up in a broken configuration.
Now, the issue with this is that there is (AFAICT) no portable way to
get a list of all allowed locales and C.UTF-8 is not standardized, so
we have no one locale to fall back on and are forced to try a few. The
list we have here is quite arbitrary, but it's a start.
Python does something similar and only tries C.UTF-8, C.utf8 and
"UTF-8".
Once C.UTF-8 is (hopefully) standardized, that will just start
working (tm).
Note that we do not *export* the fixed LC_CTYPE variable, so external
programs still have to deal with the C locale, but we have no real
business messing with the user's environment.
To turn it off: $fish_allow_singlebyte_locale, if set to something true (like "1"),
will re-run the locale initialization and skip the bit where we force
LC_CTYPE to be utf8-capable.
This is mainly used in our tests, but might also be useful if people
are trying to do something weird.
2021-06-06 15:28:32 +08:00
|
|
|
|
|
|
|
// Try to get a multibyte-capable encoding
|
|
|
|
// A "C" locale is broken for our purposes - any wchar functions will break on it.
|
|
|
|
// So we try *really really really hard* to not have one.
|
|
|
|
bool fix_locale = true;
|
2023-04-20 18:24:53 +08:00
|
|
|
if (auto allow_c = vars.get_unless_empty(L"fish_allow_singlebyte_locale")) {
|
|
|
|
fix_locale = !bool_from_string(allow_c->as_string());
|
Try to set LC_CTYPE to something UTF-8 capable (#8031)
* Try to set LC_CTYPE to something UTF-8 capable
When fish is started with LC_CTYPE=C (even just effectively, often via
LC_ALL=C!), it's basically broken. There's no way to handle non-ASCII
characters with a C locale unless we want to write our
locale-independent replacements for all of the system functions.
Since we're not going to do that, let's try to find *some locale* for
LC_CTYPE.
We already do that in __fish_setlocale, but that's
- a bit of a weird thing that reads unstandardized system
configuration files
- allows setting locale to C explicitly
So it's still easily possible to end up in a broken configuration.
Now, the issue with this is that there is (AFAICT) no portable way to
get a list of all allowed locales and C.UTF-8 is not standardized, so
we have no one locale to fall back on and are forced to try a few. The
list we have here is quite arbitrary, but it's a start.
Python does something similar and only tries C.UTF-8, C.utf8 and
"UTF-8".
Once C.UTF-8 is (hopefully) standardized, that will just start
working (tm).
Note that we do not *export* the fixed LC_CTYPE variable, so external
programs still have to deal with the C locale, but we have no real
business messing with the user's environment.
To turn it off: $fish_allow_singlebyte_locale, if set to something true (like "1"),
will re-run the locale initialization and skip the bit where we force
LC_CTYPE to be utf8-capable.
This is mainly used in our tests, but might also be useful if people
are trying to do something weird.
2021-06-06 15:28:32 +08:00
|
|
|
}
|
|
|
|
if (fix_locale && MB_CUR_MAX == 1) {
|
|
|
|
FLOGF(env_locale, L"Have singlebyte locale, trying to fix");
|
|
|
|
for (auto loc : utf8_locales) {
|
|
|
|
setlocale(LC_CTYPE, loc);
|
|
|
|
if (MB_CUR_MAX > 1) {
|
|
|
|
FLOGF(env_locale, L"Fixed locale: '%s'", loc);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (MB_CUR_MAX == 1) {
|
|
|
|
FLOGF(env_locale, L"Failed to fix locale");
|
|
|
|
}
|
|
|
|
}
|
2021-10-14 03:09:40 +08:00
|
|
|
// We *always* use a C-locale for numbers,
|
|
|
|
// because we always want "." except for in printf.
|
|
|
|
setlocale(LC_NUMERIC, "C");
|
|
|
|
|
|
|
|
// See that we regenerate our special locale for numbers.
|
2023-04-09 18:12:13 +08:00
|
|
|
rust_invalidate_numeric_locale();
|
Try to set LC_CTYPE to something UTF-8 capable (#8031)
* Try to set LC_CTYPE to something UTF-8 capable
When fish is started with LC_CTYPE=C (even just effectively, often via
LC_ALL=C!), it's basically broken. There's no way to handle non-ASCII
characters with a C locale unless we want to write our
locale-independent replacements for all of the system functions.
Since we're not going to do that, let's try to find *some locale* for
LC_CTYPE.
We already do that in __fish_setlocale, but that's
- a bit of a weird thing that reads unstandardized system
configuration files
- allows setting locale to C explicitly
So it's still easily possible to end up in a broken configuration.
Now, the issue with this is that there is (AFAICT) no portable way to
get a list of all allowed locales and C.UTF-8 is not standardized, so
we have no one locale to fall back on and are forced to try a few. The
list we have here is quite arbitrary, but it's a start.
Python does something similar and only tries C.UTF-8, C.utf8 and
"UTF-8".
Once C.UTF-8 is (hopefully) standardized, that will just start
working (tm).
Note that we do not *export* the fixed LC_CTYPE variable, so external
programs still have to deal with the C locale, but we have no real
business messing with the user's environment.
To turn it off: $fish_allow_singlebyte_locale, if set to something true (like "1"),
will re-run the locale initialization and skip the bit where we force
LC_CTYPE to be utf8-capable.
This is mainly used in our tests, but might also be useful if people
are trying to do something weird.
2021-06-06 15:28:32 +08:00
|
|
|
|
2019-04-09 04:32:25 +08:00
|
|
|
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; }
|