diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2023-11-29 22:40:31 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2023-11-29 22:40:31 +0000 |
commit | 339e2d15cc26fe86988ea06468d912a46c8d6f29 (patch) | |
tree | a6167fc8fcfc6ae2dc102f57b2473858eac34063 /src/nvim/os/lang.c | |
parent | 067dc73729267c0262438a6fdd66e586f8496946 (diff) | |
parent | 4a8bf24ac690004aedf5540fa440e788459e5e34 (diff) | |
download | rneovim-339e2d15cc26fe86988ea06468d912a46c8d6f29.tar.gz rneovim-339e2d15cc26fe86988ea06468d912a46c8d6f29.tar.bz2 rneovim-339e2d15cc26fe86988ea06468d912a46c8d6f29.zip |
Merge remote-tracking branch 'upstream/master' into fix_repeatcmdline
Diffstat (limited to 'src/nvim/os/lang.c')
-rw-r--r-- | src/nvim/os/lang.c | 337 |
1 files changed, 328 insertions, 9 deletions
diff --git a/src/nvim/os/lang.c b/src/nvim/os/lang.c index 57c82bba86..17d179a56a 100644 --- a/src/nvim/os/lang.c +++ b/src/nvim/os/lang.c @@ -1,22 +1,341 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - #ifdef __APPLE__ # define Boolean CFBoolean // Avoid conflict with API's Boolean # define FileInfo CSFileInfo // Avoid conflict with API's Fileinfo # include <CoreServices/CoreServices.h> + # undef Boolean # undef FileInfo +#endif -# include "auto/config.h" -# ifdef HAVE_LOCALE_H -# include <locale.h> -# endif -# include "nvim/os/os.h" +#include <locale.h> +#include <stdbool.h> +#include <stdio.h> +#include "auto/config.h" +#include "nvim/ascii_defs.h" +#include "nvim/buffer.h" +#include "nvim/charset.h" +#include "nvim/cmdexpand_defs.h" +#include "nvim/eval.h" +#include "nvim/ex_cmds_defs.h" +#include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/macros_defs.h" +#include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/option.h" +#include "nvim/os/lang.h" +#include "nvim/os/os.h" +#include "nvim/os/shell.h" +#include "nvim/path.h" +#include "nvim/profile.h" +#include "nvim/vim_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/lang.c.generated.h" #endif -#include "nvim/os/lang.h" +static char *get_locale_val(int what) +{ + // Obtain the locale value from the libraries. + char *loc = setlocale(what, NULL); + + return loc; +} + +/// @return true when "lang" starts with a valid language name. +/// Rejects NULL, empty string, "C", "C.UTF-8" and others. +static bool is_valid_mess_lang(const char *lang) +{ + return lang != NULL && ASCII_ISALPHA(lang[0]) && ASCII_ISALPHA(lang[1]); +} + +/// Obtain the current messages language. Used to set the default for +/// 'helplang'. May return NULL or an empty string. +char *get_mess_lang(void) +{ + char *p; + +#if defined(LC_MESSAGES) + p = get_locale_val(LC_MESSAGES); +#else + // This is necessary for Win32, where LC_MESSAGES is not defined and $LANG + // may be set to the LCID number. LC_COLLATE is the best guess, LC_TIME + // and LC_MONETARY may be set differently for a Japanese working in the + // US. + p = get_locale_val(LC_COLLATE); +#endif + return is_valid_mess_lang(p) ? p : NULL; +} + +/// Get the language used for messages from the environment. +/// +/// This uses LC_MESSAGES when available, which it is for most systems we build for +/// except for windows. Then fallback to get the value from the environment +/// ourselves, and use LC_CTYPE as a last resort. +static char *get_mess_env(void) +{ +#ifdef LC_MESSAGES + return get_locale_val(LC_MESSAGES); +#else + char *p = (char *)os_getenv("LC_ALL"); + if (p == NULL) { + p = (char *)os_getenv("LC_MESSAGES"); + if (p == NULL) { + p = (char *)os_getenv("LANG"); + if (p != NULL && ascii_isdigit(*p)) { + p = NULL; // ignore something like "1043" + } + if (p == NULL) { + p = get_locale_val(LC_CTYPE); + } + } + } + return p; +#endif +} + +/// Set the "v:lang" variable according to the current locale setting. +/// Also do "v:lc_time"and "v:ctype". +void set_lang_var(void) +{ + const char *loc; + + loc = get_locale_val(LC_CTYPE); + set_vim_var_string(VV_CTYPE, loc, -1); + + loc = get_mess_env(); + set_vim_var_string(VV_LANG, loc, -1); + + loc = get_locale_val(LC_TIME); + set_vim_var_string(VV_LC_TIME, loc, -1); + + loc = get_locale_val(LC_COLLATE); + set_vim_var_string(VV_COLLATE, loc, -1); +} + +/// Setup to use the current locale (for ctype() and many other things). +void init_locale(void) +{ + setlocale(LC_ALL, ""); + +#ifdef LC_NUMERIC + // Make sure strtod() uses a decimal point, not a comma. + setlocale(LC_NUMERIC, "C"); +#endif + + char localepath[MAXPATHL] = { 0 }; + snprintf(localepath, sizeof(localepath), "%s", get_vim_var_str(VV_PROGPATH)); + char *tail = path_tail_with_sep(localepath); + *tail = NUL; + tail = path_tail(localepath); + xstrlcpy(tail, "share/locale", + sizeof(localepath) - (size_t)(tail - localepath)); + bindtextdomain(PROJECT_NAME, localepath); + textdomain(PROJECT_NAME); + TIME_MSG("locale set"); +} + +/// ":language": Set the language (locale). +/// +/// @param eap +void ex_language(exarg_T *eap) +{ + char *loc; + char *p; + char *name; + int what = LC_ALL; + char *whatstr = ""; +#ifdef LC_MESSAGES +# define VIM_LC_MESSAGES LC_MESSAGES +#else +# define VIM_LC_MESSAGES 6789 +#endif + + name = eap->arg; + + // Check for "messages {name}", "ctype {name}" or "time {name}" argument. + // Allow abbreviation, but require at least 3 characters to avoid + // confusion with a two letter language name "me" or "ct". + p = skiptowhite(eap->arg); + if ((*p == NUL || ascii_iswhite(*p)) && p - eap->arg >= 3) { + if (STRNICMP(eap->arg, "messages", p - eap->arg) == 0) { + what = VIM_LC_MESSAGES; + name = skipwhite(p); + whatstr = "messages "; + } else if (STRNICMP(eap->arg, "ctype", p - eap->arg) == 0) { + what = LC_CTYPE; + name = skipwhite(p); + whatstr = "ctype "; + } else if (STRNICMP(eap->arg, "time", p - eap->arg) == 0) { + what = LC_TIME; + name = skipwhite(p); + whatstr = "time "; + } else if (STRNICMP(eap->arg, "collate", p - eap->arg) == 0) { + what = LC_COLLATE; + name = skipwhite(p); + whatstr = "collate "; + } + } + + if (*name == NUL) { + if (what == VIM_LC_MESSAGES) { + p = get_mess_env(); + } else { + p = setlocale(what, NULL); + } + if (p == NULL || *p == NUL) { + p = "Unknown"; + } + smsg(0, _("Current %slanguage: \"%s\""), whatstr, p); + } else { +#ifndef LC_MESSAGES + if (what == VIM_LC_MESSAGES) { + loc = ""; + } else { +#endif + loc = setlocale(what, name); +#ifdef LC_NUMERIC + // Make sure strtod() uses a decimal point, not a comma. + setlocale(LC_NUMERIC, "C"); +#endif +#ifndef LC_MESSAGES + } +#endif + if (loc == NULL) { + semsg(_("E197: Cannot set language to \"%s\""), name); + } else { +#ifdef HAVE_NL_MSG_CAT_CNTR + // Need to do this for GNU gettext, otherwise cached translations + // will be used again. + extern int _nl_msg_cat_cntr; // NOLINT(bugprone-reserved-identifier) + + _nl_msg_cat_cntr++; +#endif + // Reset $LC_ALL, otherwise it would overrule everything. + os_setenv("LC_ALL", "", 1); + + if (what != LC_TIME && what != LC_COLLATE) { + // Tell gettext() what to translate to. It apparently doesn't + // use the currently effective locale. + if (what == LC_ALL) { + os_setenv("LANG", name, 1); + + // Clear $LANGUAGE because GNU gettext uses it. + os_setenv("LANGUAGE", "", 1); + } + if (what != LC_CTYPE) { + os_setenv("LC_MESSAGES", name, 1); + set_helplang_default(name); + } + } + + // Set v:lang, v:lc_time, v:collate and v:ctype to the final result. + set_lang_var(); + maketitle(); + } + } +} + +static char **locales = NULL; // Array of all available locales + +#ifndef MSWIN +static bool did_init_locales = false; + +/// @return an array of strings for all available locales + NULL for the +/// last element or, +/// NULL in case of error. +static char **find_locales(void) +{ + garray_T locales_ga; + char *loc; + char *saveptr = NULL; + + // Find all available locales by running command "locale -a". If this + // doesn't work we won't have completion. + char *locale_a = get_cmd_output("locale -a", NULL, kShellOptSilent, NULL); + if (locale_a == NULL) { + return NULL; + } + ga_init(&locales_ga, sizeof(char *), 20); + + // Transform locale_a string where each locale is separated by "\n" + // into an array of locale strings. + loc = os_strtok(locale_a, "\n", &saveptr); + + while (loc != NULL) { + loc = xstrdup(loc); + GA_APPEND(char *, &locales_ga, loc); + loc = os_strtok(NULL, "\n", &saveptr); + } + xfree(locale_a); + // Guarantee that .ga_data is NULL terminated + ga_grow(&locales_ga, 1); + ((char **)locales_ga.ga_data)[locales_ga.ga_len] = NULL; + return locales_ga.ga_data; +} +#endif + +/// Lazy initialization of all available locales. +static void init_locales(void) +{ +#ifndef MSWIN + if (did_init_locales) { + return; + } + + did_init_locales = true; + locales = find_locales(); +#endif +} + +#if defined(EXITFREE) +void free_locales(void) +{ + if (locales == NULL) { + return; + } + + for (int i = 0; locales[i] != NULL; i++) { + xfree(locales[i]); + } + XFREE_CLEAR(locales); +} +#endif + +/// Function given to ExpandGeneric() to obtain the possible arguments of the +/// ":language" command. +char *get_lang_arg(expand_T *xp, int idx) +{ + if (idx == 0) { + return "messages"; + } + if (idx == 1) { + return "ctype"; + } + if (idx == 2) { + return "time"; + } + if (idx == 3) { + return "collate"; + } + + init_locales(); + if (locales == NULL) { + return NULL; + } + return locales[idx - 4]; +} + +/// Function given to ExpandGeneric() to obtain the available locales. +char *get_locales(expand_T *xp, int idx) +{ + init_locales(); + if (locales == NULL) { + return NULL; + } + return locales[idx]; +} void lang_init(void) { |