From 1b3c1f6c06d73e881bfc2a46e5ee3e0b24ba96d8 Mon Sep 17 00:00:00 2001 From: bfredl Date: Mon, 27 Feb 2023 19:37:43 +0100 Subject: [PATCH] refactor(build): graduate HAVE_LOCALE_H feature Merge locale.h into os/lang.h Having a source file with the same name as a system header we use is considered an anti-pattern. --- cmake.config/CMakeLists.txt | 1 - cmake.config/config.h.in | 1 - src/nvim/cmdexpand.c | 2 +- src/nvim/eval.c | 2 +- src/nvim/generators/gen_ex_cmds.lua | 2 +- src/nvim/locale.c | 377 ---------------------------- src/nvim/locale.h | 10 - src/nvim/main.c | 4 +- src/nvim/mbyte.c | 10 +- src/nvim/option.c | 1 - src/nvim/os/lang.c | 345 ++++++++++++++++++++++++- src/nvim/os/lang.h | 3 + 12 files changed, 347 insertions(+), 411 deletions(-) delete mode 100644 src/nvim/locale.c delete mode 100644 src/nvim/locale.h diff --git a/cmake.config/CMakeLists.txt b/cmake.config/CMakeLists.txt index 7e8c7ecd16..6bf4a60cf1 100644 --- a/cmake.config/CMakeLists.txt +++ b/cmake.config/CMakeLists.txt @@ -35,7 +35,6 @@ check_symbol_exists(_NSGetEnviron crt_externs.h HAVE__NSGETENVIRON) # Headers check_include_files(langinfo.h HAVE_LANGINFO_H) -check_include_files(locale.h HAVE_LOCALE_H) check_include_files(strings.h HAVE_STRINGS_H) check_include_files(sys/utsname.h HAVE_SYS_UTSNAME_H) check_include_files(termios.h HAVE_TERMIOS_H) diff --git a/cmake.config/config.h.in b/cmake.config/config.h.in index a1b46f8cb5..87b39e8f6f 100644 --- a/cmake.config/config.h.in +++ b/cmake.config/config.h.in @@ -18,7 +18,6 @@ #cmakedefine HAVE_FD_CLOEXEC #cmakedefine HAVE_FSEEKO #cmakedefine HAVE_LANGINFO_H -#cmakedefine HAVE_LOCALE_H #cmakedefine HAVE_NL_LANGINFO_CODESET #cmakedefine HAVE_NL_MSG_CAT_CNTR #cmakedefine HAVE_PWD_FUNCS diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index fcd6a73b2d..18d35e1e20 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -40,7 +40,6 @@ #include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" #include "nvim/keycodes.h" -#include "nvim/locale.h" #include "nvim/log.h" #include "nvim/lua/executor.h" #include "nvim/macros.h" @@ -50,6 +49,7 @@ #include "nvim/menu.h" #include "nvim/message.h" #include "nvim/option.h" +#include "nvim/os/lang.h" #include "nvim/os/os.h" #include "nvim/path.h" #include "nvim/popupmenu.h" diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 0fbf31a8cd..da2a88346a 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -45,7 +45,6 @@ #include "nvim/insexpand.h" #include "nvim/keycodes.h" #include "nvim/lib/queue.h" -#include "nvim/locale.h" #include "nvim/lua/executor.h" #include "nvim/macros.h" #include "nvim/main.h" @@ -62,6 +61,7 @@ #include "nvim/optionstr.h" #include "nvim/os/fileio.h" #include "nvim/os/fs_defs.h" +#include "nvim/os/lang.h" #include "nvim/os/os.h" #include "nvim/os/shell.h" #include "nvim/os/stdpaths_defs.h" diff --git a/src/nvim/generators/gen_ex_cmds.lua b/src/nvim/generators/gen_ex_cmds.lua index 0c1051b04e..935d7b333e 100644 --- a/src/nvim/generators/gen_ex_cmds.lua +++ b/src/nvim/generators/gen_ex_cmds.lua @@ -66,7 +66,6 @@ defsfile:write(string.format([[ #include "nvim/ex_session.h" #include "nvim/help.h" #include "nvim/indent.h" -#include "nvim/locale.h" #include "nvim/lua/executor.h" #include "nvim/mapping.h" #include "nvim/mark.h" @@ -75,6 +74,7 @@ defsfile:write(string.format([[ #include "nvim/message.h" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/os/lang.h" #include "nvim/profile.h" #include "nvim/quickfix.h" #include "nvim/runtime.h" diff --git a/src/nvim/locale.c b/src/nvim/locale.c deleted file mode 100644 index c3cfd3bedb..0000000000 --- a/src/nvim/locale.c +++ /dev/null @@ -1,377 +0,0 @@ -// 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 - -// locale.c: functions for language/locale configuration - -#include -#include - -#include "auto/config.h" -#ifdef HAVE_LOCALE_H -# include -#endif - -#include "nvim/ascii.h" -#include "nvim/buffer.h" -#include "nvim/charset.h" -#include "nvim/eval.h" -#include "nvim/ex_cmds_defs.h" -#include "nvim/garray.h" -#include "nvim/gettext.h" -#include "nvim/locale.h" -#include "nvim/macros.h" -#include "nvim/memory.h" -#include "nvim/message.h" -#include "nvim/option.h" -#include "nvim/os/os.h" -#include "nvim/os/shell.h" -#include "nvim/path.h" -#include "nvim/profile.h" -#include "nvim/types.h" -#include "nvim/vim.h" - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "locale.c.generated.h" -#endif - -#if defined(HAVE_LOCALE_H) -# define HAVE_GET_LOCALE_VAL - -static char *get_locale_val(int what) -{ - // Obtain the locale value from the libraries. - char *loc = setlocale(what, NULL); - - return loc; -} -#endif - -/// @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; - -#ifdef HAVE_GET_LOCALE_VAL -# 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 -#else - p = os_getenv("LC_ALL"); - if (!is_valid_mess_lang(p)) { - p = os_getenv("LC_MESSAGES"); - if (!is_valid_mess_lang(p)) { - p = os_getenv("LANG"); - } - } -#endif - return is_valid_mess_lang(p) ? p : NULL; -} - -// Complicated #if; matches with where get_mess_env() is used below. -#ifdef HAVE_WORKING_LIBINTL -/// Get the language used for messages from the environment. -static char *get_mess_env(void) -{ - char *p; - - 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" - } -# ifdef HAVE_GET_LOCALE_VAL - if (p == NULL) { - p = get_locale_val(LC_CTYPE); - } -# endif - } - } - 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; - -#ifdef HAVE_GET_LOCALE_VAL - loc = get_locale_val(LC_CTYPE); -#else - // setlocale() not supported: use the default value - loc = "C"; -#endif - set_vim_var_string(VV_CTYPE, loc, -1); - - // When LC_MESSAGES isn't defined use the value from $LC_MESSAGES, fall - // back to LC_CTYPE if it's empty. -#ifdef HAVE_WORKING_LIBINTL - loc = get_mess_env(); -#elif defined(LC_MESSAGES) - loc = get_locale_val(LC_MESSAGES); -#else - // In Windows LC_MESSAGES is not defined fallback to LC_CTYPE - loc = get_locale_val(LC_CTYPE); -#endif - set_vim_var_string(VV_LANG, loc, -1); - -#ifdef HAVE_GET_LOCALE_VAL - loc = get_locale_val(LC_TIME); -#endif - set_vim_var_string(VV_LC_TIME, loc, -1); - -#ifdef HAVE_GET_LOCALE_VAL - loc = get_locale_val(LC_COLLATE); -#else - // setlocale() not supported: use the default value - loc = "C"; -#endif - set_vim_var_string(VV_COLLATE, loc, -1); -} - -#if defined(HAVE_LOCALE_H) -/// 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"); -} -#endif - -#ifdef HAVE_WORKING_LIBINTL - -/// ":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(_("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; - - _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]; -} - -#endif diff --git a/src/nvim/locale.h b/src/nvim/locale.h deleted file mode 100644 index 39735d371f..0000000000 --- a/src/nvim/locale.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef NVIM_LOCALE_H -#define NVIM_LOCALE_H - -#include "nvim/ex_cmds_defs.h" -#include "nvim/types.h" - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "locale.h.generated.h" -#endif -#endif // NVIM_LOCALE_H diff --git a/src/nvim/main.c b/src/nvim/main.c index 975772169b..71c5c2af46 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -41,7 +41,6 @@ #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/keycodes.h" -#include "nvim/locale.h" #include "nvim/log.h" #include "nvim/lua/executor.h" #include "nvim/macros.h" @@ -60,6 +59,7 @@ #include "nvim/optionstr.h" #include "nvim/os/fileio.h" #include "nvim/os/input.h" +#include "nvim/os/lang.h" #include "nvim/os/os.h" #include "nvim/os/stdpaths_defs.h" #include "nvim/os/time.h" @@ -192,12 +192,10 @@ void early_init(mparm_T *paramp) TIME_MSG("early init"); -#if defined(HAVE_LOCALE_H) // Setup to use the current locale (for ctype() and many other things). // NOTE: Translated messages with encodings other than latin1 will not // work until set_init_1() has been called! init_locale(); -#endif // tabpage local options (p_ch) must be set before allocating first tabpage. set_init_tablocal(); diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index e27bb003e7..d8be4f4997 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -67,10 +68,6 @@ #include "nvim/types.h" #include "nvim/vim.h" -#ifdef HAVE_LOCALE_H -# include -#endif - typedef struct { int rangeStart; int rangeEnd; @@ -2193,10 +2190,7 @@ char *enc_locale(void) if (!(s = nl_langinfo(CODESET)) || *s == NUL) #endif { -#if defined(HAVE_LOCALE_H) - if (!(s = setlocale(LC_CTYPE, NULL)) || *s == NUL) -#endif - { + if (!(s = setlocale(LC_CTYPE, NULL)) || *s == NUL) { if ((s = os_getenv("LC_ALL"))) { if ((s = os_getenv("LC_CTYPE"))) { s = os_getenv("LANG"); diff --git a/src/nvim/option.c b/src/nvim/option.c index 0303f0bccd..6ddc3b5cfb 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -62,7 +62,6 @@ #include "nvim/indent_c.h" #include "nvim/insexpand.h" #include "nvim/keycodes.h" -#include "nvim/locale.h" #include "nvim/log.h" #include "nvim/lua/executor.h" #include "nvim/macros.h" diff --git a/src/nvim/os/lang.c b/src/nvim/os/lang.c index 57c82bba86..8ca2aa3a4b 100644 --- a/src/nvim/os/lang.c +++ b/src/nvim/os/lang.c @@ -7,16 +7,347 @@ # include # undef Boolean # undef FileInfo - -# include "auto/config.h" -# ifdef HAVE_LOCALE_H -# include -# endif -# include "nvim/os/os.h" - #endif +#include +#include +#include + +#include "auto/config.h" +#include "nvim/ascii.h" +#include "nvim/buffer.h" +#include "nvim/charset.h" +#include "nvim/eval.h" +#include "nvim/ex_cmds_defs.h" +#include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/macros.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/types.h" +#include "nvim/vim.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/lang.c.generated.h" +#endif + +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; +} + +// Complicated #if; matches with where get_mess_env() is used below. +#ifdef HAVE_WORKING_LIBINTL +/// Get the language used for messages from the environment. +static char *get_mess_env(void) +{ + char *p; + + 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); + + // When LC_MESSAGES isn't defined use the value from $LC_MESSAGES, fall + // back to LC_CTYPE if it's empty. +#ifdef HAVE_WORKING_LIBINTL + loc = get_mess_env(); +#elif defined(LC_MESSAGES) + loc = get_locale_val(LC_MESSAGES); +#else + // In Windows LC_MESSAGES is not defined fallback to LC_CTYPE + loc = get_locale_val(LC_CTYPE); +#endif + 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"); +} + +#ifdef HAVE_WORKING_LIBINTL + +/// ":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(_("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; + + _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]; +} + +#endif void lang_init(void) { diff --git a/src/nvim/os/lang.h b/src/nvim/os/lang.h index f60e064f57..bb1ebfb721 100644 --- a/src/nvim/os/lang.h +++ b/src/nvim/os/lang.h @@ -1,6 +1,9 @@ #ifndef NVIM_OS_LANG_H #define NVIM_OS_LANG_H +#include "nvim/ex_cmds_defs.h" +#include "nvim/types.h" + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/lang.h.generated.h" #endif