From 0a51b177168a1806af9743fc495bd7704fa15301 Mon Sep 17 00:00:00 2001 From: Fabian Homborg Date: Sun, 10 Jul 2016 18:34:27 +0200 Subject: [PATCH] if started without a locale read system config A common problem for users is that fish doesn't get a locale. This often happens if systemd is used with getty and fish as login shell. Fixes #277 Note that I (@krader) made editorial changes before merging this. For example, running `make style` and otherwise changing long statements to a series of shorter statements. So if there are any problems it is possible I introduced them. --- share/config.fish | 25 +++------ share/functions/__fish_set_locale.fish | 78 ++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 16 deletions(-) create mode 100644 share/functions/__fish_set_locale.fish diff --git a/share/config.fish b/share/config.fish index 2090b4421..01cf84a35 100644 --- a/share/config.fish +++ b/share/config.fish @@ -1,4 +1,3 @@ -# # Main file for fish command completions. This file contains various # common helper functions for the command completions. All actual # completions are located in the completions subdirectory. @@ -7,7 +6,6 @@ # # Set default field separators # - set -g IFS \n\ \t # @@ -18,8 +16,7 @@ function __fish_default_command_not_found_handler end if status --is-interactive - # The user has seemingly explicitly launched an old fish with - # too-new scripts installed. + # The user has seemingly explicitly launched an old fish with too-new scripts installed. if not contains "string" (builtin -n) set -g __is_launched_without_string 1 # XXX nostring - fix old fish binaries with no `string' builtin. @@ -28,7 +25,7 @@ if status --is-interactive # These "XXX nostring" hacks were added for 2.3.1 set_color --bold echo "You appear to be trying to launch an old fish binary with newer scripts " - echo "installed into" (set_color --underline)"$__fish_datadir" + echo "installed into" (set_color --underline)"$__fish_datadir" set_color normal echo -e "\nThis is an unsupported configuration.\n" set_color yellow @@ -124,7 +121,7 @@ set -g __fish_tmp_path $PATH function __fish_load_path_helper_paths # We want to rearrange the path to reflect this order. Delete that path component if it exists and then prepend it. # Since we are prepending but want to preserve the order of the input file, we reverse the array, append, and then reverse it again - set __fish_tmp_path $__fish_tmp_path[-1..1] + set __fish_tmp_path $__fish_tmp_path[-1..1] while read -l new_path_comp if test -d $new_path_comp set -l where (contains -i $new_path_comp $__fish_tmp_path) @@ -134,7 +131,7 @@ function __fish_load_path_helper_paths end set __fish_tmp_path $__fish_tmp_path[-1..1] end -test -r /etc/paths ; and __fish_load_path_helper_paths < /etc/paths +test -r /etc/paths ; and __fish_load_path_helper_paths < /etc/paths for pathfile in /etc/paths.d/* ; __fish_load_path_helper_paths < $pathfile ; end set -xg PATH $__fish_tmp_path set -e __fish_tmp_path @@ -195,6 +192,11 @@ function . --description 'Evaluate contents of file (deprecated, see "source")' end end +# Set the locale if it isn't explicitly set. Allowing the lack of locale env vars to imply the +# C/POSIX locale causes too many problems. Do this before reading the snippets because they might be +# in UTF-8 (with non-ASCII characters). +__fish_set_locale + # As last part of initialization, source the conf directories # Implement precedence (User > Admin > Extra (e.g. vendors) > Fish) by basically doing "basename" set -l sourcelist @@ -225,15 +227,6 @@ end if status --is-login - # Check for i18n information in - # /etc/sysconfig/i18n - - if test -f /etc/sysconfig/i18n - string match -r '^[a-zA-Z]*=.*' < /etc/sysconfig/i18n | while read -l line - set -gx (string split '=' -m 1 -- $line | string replace -ra '"([^"]+)"' '$1' | string replace -ra "'([^']+)'" '$1') - end - end - # # Put linux consoles in unicode mode. # diff --git a/share/functions/__fish_set_locale.fish b/share/functions/__fish_set_locale.fish new file mode 100644 index 000000000..1950dabb0 --- /dev/null +++ b/share/functions/__fish_set_locale.fish @@ -0,0 +1,78 @@ +# Try to set the locale from the system configuration if we did not inherit any. One case where this +# can happen is a linux with systemd where the user logs in via getty (e.g., on the system console). +# See https://github.com/fish-shell/fish-shell/issues/3092. This isn't actually our job, so there's +# a bunch of edge-cases we are unlikely to handle properly. If we get a value for _any_ language +# variable, we assume we've inherited something sensible so we skip this to allow the user to set it +# at runtime without mucking with config files. +# +# NOTE: This breaks the expectation that an empty LANG will be the same as LANG=POSIX, but an empty +# LANG seems more likely to be caused by a missing or misconfigured locale configuration. + +function __fish_set_locale + set -l LOCALE_VARS + set LOCALE_VARS $LOCALE_VARS LANG LANGUAGE LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE + set LOCALE_VARS $LOCALE_VARS LC_MONETARY LC_MESSAGES LC_PAPER LC_NAME LC_ADDRESS + set LOCALE_VARS $LOCALE_VARS LC_TELEPHONE LC_MEASUREMENT LC_IDENTIFICATION + + # We check LC_ALL to figure out if we have a locale but we don't set it later. That is because + # locale.conf doesn't allow it so we should not set it. + if string length -q -- $$LOCALE_VARS $LC_ALL + return 0 + end + + # Unset all variables - they are empty anyway and this makes merging easier. + for locale_var in $LOCALE_VARS + set -e $locale_var + end + + # Try to extract the locale from the kernel boot commandline. The splitting here is a bit weird, + # but we operate under the assumption that the locale can't include whitespace. Other whitespace + # shouldn't concern us, but a quoted "locale.LANG=SOMETHING" as a value to something else might. + # Here the last definition of a variable takes precedence. + if test -r /proc/cmdline + for var in (string match -ra 'locale.[^=]+=\S+' < /proc/cmdline) + set -l kv (string replace 'locale.' '' -- $var | string split '=') + # Only set locale variables, not other stuff contained in these files - this also + # automatically ignores comments. + if contains -- $kv[1] $LOCALE_VARS + and set -q kv[2] + set -gx $kv[1] (string trim -c '\'"' -- $kv[2]) + end + end + end + + # Now read the config files we know are used by various OS distros. + # + # /etc/sysconfig/i18n is for old Red Hat derivatives (and possibly of no use anymore). + # + # /etc/env.d/02locale is from OpenRC. + # + # The rest are systemd inventions but also used elsewhere (e.g. Void Linux). systemd's + # documentation is a bit unclear on this. We merge all the config files (and the commandline), + # which seems to be what systemd itself does. (I.e. the value for a variable will be taken from + # the highest-precedence source) We read the systemd files first since they are a newer + # invention and therefore the rest are likely to be accumulated cruft. + # + # NOTE: Slackware puts the locale in /etc/profile.d/lang.sh, which we can't use because it's a + # full POSIX-shell script. + set -l user_cfg_dir (set -q XDG_CONFIG_HOME; and echo $XDG_CONFIG_HOME; or echo ~/.config) + for f in $user_cfg_dir/locale.conf /etc/locale.conf /etc/env.d/02locale /etc/sysconfig/i18n + if test -r $f + while read -l kv + set kv (string split '=' -- $kv) + if contains -- $kv[1] $LOCALE_VARS + and set -q kv[2] + # Do not set already set variables again - this makes the merging happen. + if not set -q $kv[1] + set -gx $kv[1] (string trim -c '\'"' -- $kv[2]) + end + end + end <$f + end + end + + # If we really cannot get anything, at least set character encoding to UTF-8. + if not string length -q -- $$LOCALE_VARS $LC_ALL + set -gx LC_CTYPE en_US.UTF-8 + end +end