# HG changeset patch # User Bram Moolenaar # Date 1595438104 -7200 # Node ID b32b67a108f2c66990bd60a66c3b7f5f37f68cea # Parent 31a3d49932c151c679d0c60731e5c81ec49d4fe0 patch 8.2.1269: language and locale code spread out Commit: https://github.com/vim/vim/commit/054f14bbe58fece17f1a74ca63f0b37518f0b4de Author: Bram Moolenaar Date: Wed Jul 22 19:11:19 2020 +0200 patch 8.2.1269: language and locale code spread out Problem: Language and locale code spread out. Solution: Move relevant code to src/locale.c. (Yegappan Lakshmanan, closes #6509) diff --git a/Filelist b/Filelist --- a/Filelist +++ b/Filelist @@ -76,6 +76,7 @@ SRC_ALL = \ src/json_test.c \ src/kword_test.c \ src/list.c \ + src/locale.c \ src/keymap.h \ src/macros.h \ src/main.c \ @@ -247,6 +248,7 @@ SRC_ALL = \ src/proto/insexpand.pro \ src/proto/json.pro \ src/proto/list.pro \ + src/proto/locale.pro \ src/proto/main.pro \ src/proto/map.pro \ src/proto/mark.pro \ diff --git a/src/Make_cyg_ming.mak b/src/Make_cyg_ming.mak --- a/src/Make_cyg_ming.mak +++ b/src/Make_cyg_ming.mak @@ -751,6 +751,7 @@ OBJ = \ $(OUTDIR)/insexpand.o \ $(OUTDIR)/json.o \ $(OUTDIR)/list.o \ + $(OUTDIR)/locale.o \ $(OUTDIR)/main.o \ $(OUTDIR)/map.o \ $(OUTDIR)/mark.o \ diff --git a/src/Make_morph.mak b/src/Make_morph.mak --- a/src/Make_morph.mak +++ b/src/Make_morph.mak @@ -70,6 +70,7 @@ SRC = arabic.c \ insexpand.c \ json.c \ list.c \ + locale.c \ main.c \ map.c \ mark.c \ diff --git a/src/Make_mvc.mak b/src/Make_mvc.mak --- a/src/Make_mvc.mak +++ b/src/Make_mvc.mak @@ -773,6 +773,7 @@ OBJ = \ $(OUTDIR)\insexpand.obj \ $(OUTDIR)\json.obj \ $(OUTDIR)\list.obj \ + $(OUTDIR)\locale.obj \ $(OUTDIR)\main.obj \ $(OUTDIR)\map.obj \ $(OUTDIR)\mark.obj \ @@ -1669,6 +1670,8 @@ lib$(MZSCHEME_MAIN_LIB)$(MZSCHEME_VER).l $(OUTDIR)/list.obj: $(OUTDIR) list.c $(INCL) +$(OUTDIR)/locale.obj: $(OUTDIR) locale.c $(INCL) + $(OUTDIR)/main.obj: $(OUTDIR) main.c $(INCL) $(CUI_INCL) $(OUTDIR)/map.obj: $(OUTDIR) map.c $(INCL) @@ -1939,6 +1942,7 @@ proto.h: \ proto/insexpand.pro \ proto/json.pro \ proto/list.pro \ + proto/locale.pro \ proto/main.pro \ proto/map.pro \ proto/mark.pro \ diff --git a/src/Make_vms.mms b/src/Make_vms.mms --- a/src/Make_vms.mms +++ b/src/Make_vms.mms @@ -345,6 +345,7 @@ SRC = \ insexpand.c \ json.c \ list.c \ + locale.c \ main.c \ map.c \ mark.c \ @@ -460,6 +461,7 @@ OBJ = \ insexpand.obj \ json.obj \ list.obj \ + locale.obj \ main.obj \ map.obj \ mark.obj \ @@ -865,6 +867,10 @@ list.obj : list.c vim.h [.auto]config.h ascii.h keymap.h term.h macros.h option.h structs.h regexp.h gui.h \ beval.h [.proto]gui_beval.pro alloc.h ex_cmds.h spell.h proto.h \ globals.h +locale.obj : locale.c vim.h [.auto]config.h feature.h os_unix.h \ + ascii.h keymap.h term.h macros.h option.h structs.h regexp.h gui.h \ + beval.h [.proto]gui_beval.pro alloc.h ex_cmds.h spell.h proto.h \ + globals.h main.obj : main.c vim.h [.auto]config.h feature.h os_unix.h \ ascii.h keymap.h term.h macros.h structs.h regexp.h gui.h beval.h \ [.proto]gui_beval.pro option.h ex_cmds.h proto.h globals.h \ diff --git a/src/Makefile b/src/Makefile --- a/src/Makefile +++ b/src/Makefile @@ -1647,6 +1647,7 @@ BASIC_SRC = \ insexpand.c \ json.c \ list.c \ + locale.c \ main.c \ map.c \ mark.c \ @@ -1798,6 +1799,7 @@ OBJ_COMMON = \ objects/indent.o \ objects/insexpand.o \ objects/list.o \ + objects/locale.o \ objects/map.o \ objects/mark.o \ objects/match.o \ @@ -1973,6 +1975,7 @@ PRO_AUTO = \ insexpand.pro \ json.pro \ list.pro \ + locale.pro \ main.pro \ map.pro \ mark.pro \ @@ -3378,6 +3381,9 @@ objects/kword_test.o: kword_test.c objects/list.o: list.c $(CCC) -o $@ list.c +objects/locale.o: locale.c + $(CCC) -o $@ locale.c + objects/main.o: main.c $(CCC) -o $@ main.c @@ -3968,6 +3974,10 @@ objects/list.o: list.c vim.h protodef.h auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ proto.h globals.h +objects/locale.o: locale.c vim.h protodef.h auto/config.h feature.h os_unix.h \ + auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \ + proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ + proto.h globals.h objects/main.o: main.c vim.h protodef.h auto/config.h feature.h os_unix.h \ auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ diff --git a/src/README.md b/src/README.md --- a/src/README.md +++ b/src/README.md @@ -52,6 +52,7 @@ help.c | vim help related functions highlight.c | syntax highlighting indent.c | text indentation insexpand.c | Insert mode completion +locale.c | locale/language handling map.c | mapping and abbreviations mark.c | marks match.c | highlight matching diff --git a/src/ex_cmds2.c b/src/ex_cmds2.c --- a/src/ex_cmds2.c +++ b/src/ex_cmds2.c @@ -996,505 +996,3 @@ ex_checktime(exarg_T *eap) } no_check_timestamps = save_no_check_timestamps; } - -#if (defined(HAVE_LOCALE_H) || defined(X_LOCALE)) \ - && (defined(FEAT_EVAL) || defined(FEAT_MULTI_LANG)) -# define HAVE_GET_LOCALE_VAL - static char_u * -get_locale_val(int what) -{ - char_u *loc; - - // Obtain the locale value from the libraries. - loc = (char_u *)setlocale(what, NULL); - -# ifdef MSWIN - if (loc != NULL) - { - char_u *p; - - // setocale() returns something like "LC_COLLATE=;LC_..." when - // one of the values (e.g., LC_CTYPE) differs. - p = vim_strchr(loc, '='); - if (p != NULL) - { - loc = ++p; - while (*p != NUL) // remove trailing newline - { - if (*p < ' ' || *p == ';') - { - *p = NUL; - break; - } - ++p; - } - } - } -# endif - - return loc; -} -#endif - - -#ifdef MSWIN -/* - * On MS-Windows locale names are strings like "German_Germany.1252", but - * gettext expects "de". Try to translate one into another here for a few - * supported languages. - */ - static char_u * -gettext_lang(char_u *name) -{ - int i; - static char *(mtable[]) = { - "afrikaans", "af", - "czech", "cs", - "dutch", "nl", - "german", "de", - "english_united kingdom", "en_GB", - "spanish", "es", - "french", "fr", - "italian", "it", - "japanese", "ja", - "korean", "ko", - "norwegian", "no", - "polish", "pl", - "russian", "ru", - "slovak", "sk", - "swedish", "sv", - "ukrainian", "uk", - "chinese_china", "zh_CN", - "chinese_taiwan", "zh_TW", - NULL}; - - for (i = 0; mtable[i] != NULL; i += 2) - if (STRNICMP(mtable[i], name, STRLEN(mtable[i])) == 0) - return (char_u *)mtable[i + 1]; - return name; -} -#endif - -#if defined(FEAT_MULTI_LANG) || defined(PROTO) -/* - * Return TRUE when "lang" starts with a valid language name. - * Rejects NULL, empty string, "C", "C.UTF-8" and others. - */ - static int -is_valid_mess_lang(char_u *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_u * -get_mess_lang(void) -{ - char_u *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 = mch_getenv((char_u *)"LC_ALL"); - if (!is_valid_mess_lang(p)) - { - p = mch_getenv((char_u *)"LC_MESSAGES"); - if (!is_valid_mess_lang(p)) - p = mch_getenv((char_u *)"LANG"); - } -# endif -# ifdef MSWIN - p = gettext_lang(p); -# endif - return is_valid_mess_lang(p) ? p : NULL; -} -#endif - -// Complicated #if; matches with where get_mess_env() is used below. -#if (defined(FEAT_EVAL) && !((defined(HAVE_LOCALE_H) || defined(X_LOCALE)) \ - && defined(LC_MESSAGES))) \ - || ((defined(HAVE_LOCALE_H) || defined(X_LOCALE)) \ - && !defined(LC_MESSAGES)) -/* - * Get the language used for messages from the environment. - */ - static char_u * -get_mess_env(void) -{ - char_u *p; - - p = mch_getenv((char_u *)"LC_ALL"); - if (p == NULL || *p == NUL) - { - p = mch_getenv((char_u *)"LC_MESSAGES"); - if (p == NULL || *p == NUL) - { - p = mch_getenv((char_u *)"LANG"); - if (p != NULL && VIM_ISDIGIT(*p)) - p = NULL; // ignore something like "1043" -# ifdef HAVE_GET_LOCALE_VAL - if (p == NULL || *p == NUL) - p = get_locale_val(LC_CTYPE); -# endif - } - } - return p; -} -#endif - -#if defined(FEAT_EVAL) || defined(PROTO) - -/* - * Set the "v:lang" variable according to the current locale setting. - * Also do "v:lc_time"and "v:ctype". - */ - void -set_lang_var(void) -{ - char_u *loc; - -# ifdef HAVE_GET_LOCALE_VAL - loc = get_locale_val(LC_CTYPE); -# else - // setlocale() not supported: use the default value - loc = (char_u *)"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. -# if defined(HAVE_GET_LOCALE_VAL) && defined(LC_MESSAGES) - loc = get_locale_val(LC_MESSAGES); -# else - loc = get_mess_env(); -# 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 = (char_u *)"C"; -# endif - set_vim_var_string(VV_COLLATE, loc, -1); -} -#endif - -#if defined(HAVE_LOCALE_H) || defined(X_LOCALE) -/* - * ":language": Set the language (locale). - */ - void -ex_language(exarg_T *eap) -{ - char *loc; - char_u *p; - char_u *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 || VIM_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) - { -# ifndef LC_MESSAGES - if (what == VIM_LC_MESSAGES) - p = get_mess_env(); - else -# endif - p = (char_u *)setlocale(what, NULL); - if (p == NULL || *p == NUL) - p = (char_u *)"Unknown"; - smsg(_("Current %slanguage: \"%s\""), whatstr, p); - } - else - { -# ifndef LC_MESSAGES - if (what == VIM_LC_MESSAGES) - loc = ""; - else -# endif - { - loc = setlocale(what, (char *)name); -# if defined(FEAT_FLOAT) && defined(LC_NUMERIC) - // Make sure strtod() uses a decimal point, not a comma. - setlocale(LC_NUMERIC, "C"); -# 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. - vim_setenv((char_u *)"LC_ALL", (char_u *)""); - - if (what != LC_TIME && what != LC_COLLATE) - { - // Tell gettext() what to translate to. It apparently doesn't - // use the currently effective locale. Also do this when - // FEAT_GETTEXT isn't defined, so that shell commands use this - // value. - if (what == LC_ALL) - { - vim_setenv((char_u *)"LANG", name); - - // Clear $LANGUAGE because GNU gettext uses it. - vim_setenv((char_u *)"LANGUAGE", (char_u *)""); -# ifdef MSWIN - // Apparently MS-Windows printf() may cause a crash when - // we give it 8-bit text while it's expecting text in the - // current locale. This call avoids that. - setlocale(LC_CTYPE, "C"); -# endif - } - if (what != LC_CTYPE) - { - char_u *mname; -# ifdef MSWIN - mname = gettext_lang(name); -# else - mname = name; -# endif - vim_setenv((char_u *)"LC_MESSAGES", mname); -# ifdef FEAT_MULTI_LANG - set_helplang_default(mname); -# endif - } - } - -# ifdef FEAT_EVAL - // Set v:lang, v:lc_time, v:collate and v:ctype to the final result. - set_lang_var(); -# endif -# ifdef FEAT_TITLE - maketitle(); -# endif - } - } -} - -static char_u **locales = NULL; // Array of all available locales - -static int did_init_locales = FALSE; - -/* - * Return an array of strings for all available locales + NULL for the - * last element. Return NULL in case of error. - */ - static char_u ** -find_locales(void) -{ - garray_T locales_ga; - char_u *loc; - char_u *locale_list; -# ifdef MSWIN - size_t len = 0; -# endif - - // Find all available locales by running command "locale -a". If this - // doesn't work we won't have completion. -# ifndef MSWIN - locale_list = get_cmd_output((char_u *)"locale -a", - NULL, SHELL_SILENT, NULL); -# else - // Find all available locales by examining the directories in - // $VIMRUNTIME/lang/ - { - int options = WILD_SILENT|WILD_USE_NL|WILD_KEEP_ALL; - expand_T xpc; - char_u *p; - - ExpandInit(&xpc); - xpc.xp_context = EXPAND_DIRECTORIES; - locale_list = ExpandOne(&xpc, (char_u *)"$VIMRUNTIME/lang/*", - NULL, options, WILD_ALL); - ExpandCleanup(&xpc); - if (locale_list == NULL) - // Add a dummy input, that will be skipped lated but we need to - // have something in locale_list so that the C locale is added at - // the end. - locale_list = vim_strsave((char_u *)".\n"); - p = locale_list; - // find the last directory delimiter - while (p != NULL && *p != NUL) - { - if (*p == '\n') - break; - if (*p == '\\') - len = p - locale_list; - p++; - } - } -# endif - if (locale_list == NULL) - return NULL; - ga_init2(&locales_ga, sizeof(char_u *), 20); - - // Transform locale_list string where each locale is separated by "\n" - // into an array of locale strings. - loc = (char_u *)strtok((char *)locale_list, "\n"); - - while (loc != NULL) - { - int ignore = FALSE; - -# ifdef MSWIN - if (len > 0) - loc += len + 1; - // skip locales with a dot (which indicates the charset) - if (vim_strchr(loc, '.') != NULL) - ignore = TRUE; -# endif - if (!ignore) - { - if (ga_grow(&locales_ga, 1) == FAIL) - break; - - loc = vim_strsave(loc); - if (loc == NULL) - break; - - ((char_u **)locales_ga.ga_data)[locales_ga.ga_len++] = loc; - } - loc = (char_u *)strtok(NULL, "\n"); - } - -# ifdef MSWIN - // Add the C locale - if (ga_grow(&locales_ga, 1) == OK) - ((char_u **)locales_ga.ga_data)[locales_ga.ga_len++] = - vim_strsave((char_u *)"C"); -# endif - - vim_free(locale_list); - if (ga_grow(&locales_ga, 1) == FAIL) - { - ga_clear(&locales_ga); - return NULL; - } - ((char_u **)locales_ga.ga_data)[locales_ga.ga_len] = NULL; - return (char_u **)locales_ga.ga_data; -} - -/* - * Lazy initialization of all available locales. - */ - static void -init_locales(void) -{ - if (!did_init_locales) - { - did_init_locales = TRUE; - locales = find_locales(); - } -} - -# if defined(EXITFREE) || defined(PROTO) - void -free_locales(void) -{ - int i; - if (locales != NULL) - { - for (i = 0; locales[i] != NULL; i++) - vim_free(locales[i]); - VIM_CLEAR(locales); - } -} -# endif - -/* - * Function given to ExpandGeneric() to obtain the possible arguments of the - * ":language" command. - */ - char_u * -get_lang_arg(expand_T *xp UNUSED, int idx) -{ - if (idx == 0) - return (char_u *)"messages"; - if (idx == 1) - return (char_u *)"ctype"; - if (idx == 2) - return (char_u *)"time"; - if (idx == 3) - return (char_u *)"collate"; - - init_locales(); - if (locales == NULL) - return NULL; - return locales[idx - 4]; -} - -/* - * Function given to ExpandGeneric() to obtain the available locales. - */ - char_u * -get_locales(expand_T *xp UNUSED, int idx) -{ - init_locales(); - if (locales == NULL) - return NULL; - return locales[idx]; -} - -#endif diff --git a/src/locale.c b/src/locale.c new file mode 100644 --- /dev/null +++ b/src/locale.c @@ -0,0 +1,564 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * locale.c: functions for language/locale configuration + */ + +#include "vim.h" + +#if (defined(HAVE_LOCALE_H) || defined(X_LOCALE)) \ + && (defined(FEAT_EVAL) || defined(FEAT_MULTI_LANG)) +# define HAVE_GET_LOCALE_VAL + static char_u * +get_locale_val(int what) +{ + char_u *loc; + + // Obtain the locale value from the libraries. + loc = (char_u *)setlocale(what, NULL); + +# ifdef MSWIN + if (loc != NULL) + { + char_u *p; + + // setocale() returns something like "LC_COLLATE=;LC_..." when + // one of the values (e.g., LC_CTYPE) differs. + p = vim_strchr(loc, '='); + if (p != NULL) + { + loc = ++p; + while (*p != NUL) // remove trailing newline + { + if (*p < ' ' || *p == ';') + { + *p = NUL; + break; + } + ++p; + } + } + } +# endif + + return loc; +} +#endif + + +#ifdef MSWIN +/* + * On MS-Windows locale names are strings like "German_Germany.1252", but + * gettext expects "de". Try to translate one into another here for a few + * supported languages. + */ + static char_u * +gettext_lang(char_u *name) +{ + int i; + static char *(mtable[]) = { + "afrikaans", "af", + "czech", "cs", + "dutch", "nl", + "german", "de", + "english_united kingdom", "en_GB", + "spanish", "es", + "french", "fr", + "italian", "it", + "japanese", "ja", + "korean", "ko", + "norwegian", "no", + "polish", "pl", + "russian", "ru", + "slovak", "sk", + "swedish", "sv", + "ukrainian", "uk", + "chinese_china", "zh_CN", + "chinese_taiwan", "zh_TW", + NULL}; + + for (i = 0; mtable[i] != NULL; i += 2) + if (STRNICMP(mtable[i], name, STRLEN(mtable[i])) == 0) + return (char_u *)mtable[i + 1]; + return name; +} +#endif + +#if defined(FEAT_MULTI_LANG) || defined(PROTO) +/* + * Return TRUE when "lang" starts with a valid language name. + * Rejects NULL, empty string, "C", "C.UTF-8" and others. + */ + static int +is_valid_mess_lang(char_u *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_u * +get_mess_lang(void) +{ + char_u *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 = mch_getenv((char_u *)"LC_ALL"); + if (!is_valid_mess_lang(p)) + { + p = mch_getenv((char_u *)"LC_MESSAGES"); + if (!is_valid_mess_lang(p)) + p = mch_getenv((char_u *)"LANG"); + } +# endif +# ifdef MSWIN + p = gettext_lang(p); +# endif + return is_valid_mess_lang(p) ? p : NULL; +} +#endif + +// Complicated #if; matches with where get_mess_env() is used below. +#if (defined(FEAT_EVAL) && !((defined(HAVE_LOCALE_H) || defined(X_LOCALE)) \ + && defined(LC_MESSAGES))) \ + || ((defined(HAVE_LOCALE_H) || defined(X_LOCALE)) \ + && !defined(LC_MESSAGES)) +/* + * Get the language used for messages from the environment. + */ + static char_u * +get_mess_env(void) +{ + char_u *p; + + p = mch_getenv((char_u *)"LC_ALL"); + if (p == NULL || *p == NUL) + { + p = mch_getenv((char_u *)"LC_MESSAGES"); + if (p == NULL || *p == NUL) + { + p = mch_getenv((char_u *)"LANG"); + if (p != NULL && VIM_ISDIGIT(*p)) + p = NULL; // ignore something like "1043" +# ifdef HAVE_GET_LOCALE_VAL + if (p == NULL || *p == NUL) + p = get_locale_val(LC_CTYPE); +# endif + } + } + return p; +} +#endif + +#if defined(FEAT_EVAL) || defined(PROTO) + +/* + * Set the "v:lang" variable according to the current locale setting. + * Also do "v:lc_time"and "v:ctype". + */ + void +set_lang_var(void) +{ + char_u *loc; + +# ifdef HAVE_GET_LOCALE_VAL + loc = get_locale_val(LC_CTYPE); +# else + // setlocale() not supported: use the default value + loc = (char_u *)"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. +# if defined(HAVE_GET_LOCALE_VAL) && defined(LC_MESSAGES) + loc = get_locale_val(LC_MESSAGES); +# else + loc = get_mess_env(); +# 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 = (char_u *)"C"; +# endif + set_vim_var_string(VV_COLLATE, loc, -1); +} +#endif + +#if defined(HAVE_LOCALE_H) || defined(X_LOCALE) +/* + * Setup to use the current locale (for ctype() and many other things). + */ + void +init_locale(void) +{ + setlocale(LC_ALL, ""); + +# ifdef FEAT_GUI_GTK + // Tell Gtk not to change our locale settings. + gtk_disable_setlocale(); +# endif +# if defined(FEAT_FLOAT) && defined(LC_NUMERIC) + // Make sure strtod() uses a decimal point, not a comma. + setlocale(LC_NUMERIC, "C"); +# endif + +# ifdef MSWIN + // Apparently MS-Windows printf() may cause a crash when we give it 8-bit + // text while it's expecting text in the current locale. This call avoids + // that. + setlocale(LC_CTYPE, "C"); +# endif + +# ifdef FEAT_GETTEXT + { + int mustfree = FALSE; + char_u *p; + +# ifdef DYNAMIC_GETTEXT + // Initialize the gettext library + dyn_libintl_init(); +# endif + // expand_env() doesn't work yet, because g_chartab[] is not + // initialized yet, call vim_getenv() directly + p = vim_getenv((char_u *)"VIMRUNTIME", &mustfree); + if (p != NULL && *p != NUL) + { + vim_snprintf((char *)NameBuff, MAXPATHL, "%s/lang", p); + bindtextdomain(VIMPACKAGE, (char *)NameBuff); + } + if (mustfree) + vim_free(p); + textdomain(VIMPACKAGE); + } +# endif +} + +/* + * ":language": Set the language (locale). + */ + void +ex_language(exarg_T *eap) +{ + char *loc; + char_u *p; + char_u *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 || VIM_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) + { +# ifndef LC_MESSAGES + if (what == VIM_LC_MESSAGES) + p = get_mess_env(); + else +# endif + p = (char_u *)setlocale(what, NULL); + if (p == NULL || *p == NUL) + p = (char_u *)"Unknown"; + smsg(_("Current %slanguage: \"%s\""), whatstr, p); + } + else + { +# ifndef LC_MESSAGES + if (what == VIM_LC_MESSAGES) + loc = ""; + else +# endif + { + loc = setlocale(what, (char *)name); +# if defined(FEAT_FLOAT) && defined(LC_NUMERIC) + // Make sure strtod() uses a decimal point, not a comma. + setlocale(LC_NUMERIC, "C"); +# 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. + vim_setenv((char_u *)"LC_ALL", (char_u *)""); + + if (what != LC_TIME && what != LC_COLLATE) + { + // Tell gettext() what to translate to. It apparently doesn't + // use the currently effective locale. Also do this when + // FEAT_GETTEXT isn't defined, so that shell commands use this + // value. + if (what == LC_ALL) + { + vim_setenv((char_u *)"LANG", name); + + // Clear $LANGUAGE because GNU gettext uses it. + vim_setenv((char_u *)"LANGUAGE", (char_u *)""); +# ifdef MSWIN + // Apparently MS-Windows printf() may cause a crash when + // we give it 8-bit text while it's expecting text in the + // current locale. This call avoids that. + setlocale(LC_CTYPE, "C"); +# endif + } + if (what != LC_CTYPE) + { + char_u *mname; +# ifdef MSWIN + mname = gettext_lang(name); +# else + mname = name; +# endif + vim_setenv((char_u *)"LC_MESSAGES", mname); +# ifdef FEAT_MULTI_LANG + set_helplang_default(mname); +# endif + } + } + +# ifdef FEAT_EVAL + // Set v:lang, v:lc_time, v:collate and v:ctype to the final result. + set_lang_var(); +# endif +# ifdef FEAT_TITLE + maketitle(); +# endif + } + } +} + +static char_u **locales = NULL; // Array of all available locales + +static int did_init_locales = FALSE; + +/* + * Return an array of strings for all available locales + NULL for the + * last element. Return NULL in case of error. + */ + static char_u ** +find_locales(void) +{ + garray_T locales_ga; + char_u *loc; + char_u *locale_list; +# ifdef MSWIN + size_t len = 0; +# endif + + // Find all available locales by running command "locale -a". If this + // doesn't work we won't have completion. +# ifndef MSWIN + locale_list = get_cmd_output((char_u *)"locale -a", + NULL, SHELL_SILENT, NULL); +# else + // Find all available locales by examining the directories in + // $VIMRUNTIME/lang/ + { + int options = WILD_SILENT|WILD_USE_NL|WILD_KEEP_ALL; + expand_T xpc; + char_u *p; + + ExpandInit(&xpc); + xpc.xp_context = EXPAND_DIRECTORIES; + locale_list = ExpandOne(&xpc, (char_u *)"$VIMRUNTIME/lang/*", + NULL, options, WILD_ALL); + ExpandCleanup(&xpc); + if (locale_list == NULL) + // Add a dummy input, that will be skipped lated but we need to + // have something in locale_list so that the C locale is added at + // the end. + locale_list = vim_strsave((char_u *)".\n"); + p = locale_list; + // find the last directory delimiter + while (p != NULL && *p != NUL) + { + if (*p == '\n') + break; + if (*p == '\\') + len = p - locale_list; + p++; + } + } +# endif + if (locale_list == NULL) + return NULL; + ga_init2(&locales_ga, sizeof(char_u *), 20); + + // Transform locale_list string where each locale is separated by "\n" + // into an array of locale strings. + loc = (char_u *)strtok((char *)locale_list, "\n"); + + while (loc != NULL) + { + int ignore = FALSE; + +# ifdef MSWIN + if (len > 0) + loc += len + 1; + // skip locales with a dot (which indicates the charset) + if (vim_strchr(loc, '.') != NULL) + ignore = TRUE; +# endif + if (!ignore) + { + if (ga_grow(&locales_ga, 1) == FAIL) + break; + + loc = vim_strsave(loc); + if (loc == NULL) + break; + + ((char_u **)locales_ga.ga_data)[locales_ga.ga_len++] = loc; + } + loc = (char_u *)strtok(NULL, "\n"); + } + +# ifdef MSWIN + // Add the C locale + if (ga_grow(&locales_ga, 1) == OK) + ((char_u **)locales_ga.ga_data)[locales_ga.ga_len++] = + vim_strsave((char_u *)"C"); +# endif + + vim_free(locale_list); + if (ga_grow(&locales_ga, 1) == FAIL) + { + ga_clear(&locales_ga); + return NULL; + } + ((char_u **)locales_ga.ga_data)[locales_ga.ga_len] = NULL; + return (char_u **)locales_ga.ga_data; +} + +/* + * Lazy initialization of all available locales. + */ + static void +init_locales(void) +{ + if (!did_init_locales) + { + did_init_locales = TRUE; + locales = find_locales(); + } +} + +# if defined(EXITFREE) || defined(PROTO) + void +free_locales(void) +{ + int i; + if (locales != NULL) + { + for (i = 0; locales[i] != NULL; i++) + vim_free(locales[i]); + VIM_CLEAR(locales); + } +} +# endif + +/* + * Function given to ExpandGeneric() to obtain the possible arguments of the + * ":language" command. + */ + char_u * +get_lang_arg(expand_T *xp UNUSED, int idx) +{ + if (idx == 0) + return (char_u *)"messages"; + if (idx == 1) + return (char_u *)"ctype"; + if (idx == 2) + return (char_u *)"time"; + if (idx == 3) + return (char_u *)"collate"; + + init_locales(); + if (locales == NULL) + return NULL; + return locales[idx - 4]; +} + +/* + * Function given to ExpandGeneric() to obtain the available locales. + */ + char_u * +get_locales(expand_T *xp UNUSED, int idx) +{ + init_locales(); + if (locales == NULL) + return NULL; + return locales[idx]; +} + +#endif diff --git a/src/main.c b/src/main.c --- a/src/main.c +++ b/src/main.c @@ -34,9 +34,6 @@ static int file_owned(char *fname); #endif static void mainerr(int, char_u *); -# if defined(HAVE_LOCALE_H) || defined(X_LOCALE) -static void init_locale(void); -# endif static void early_arg_scan(mparm_T *parmp); #ifndef NO_VIM_MAIN static void usage(void); @@ -1716,56 +1713,6 @@ getout(int exitval) mch_exit(exitval); } -#if defined(HAVE_LOCALE_H) || defined(X_LOCALE) -/* - * Setup to use the current locale (for ctype() and many other things). - */ - static void -init_locale(void) -{ - setlocale(LC_ALL, ""); - -# ifdef FEAT_GUI_GTK - // Tell Gtk not to change our locale settings. - gtk_disable_setlocale(); -# endif -# if defined(FEAT_FLOAT) && defined(LC_NUMERIC) - // Make sure strtod() uses a decimal point, not a comma. - setlocale(LC_NUMERIC, "C"); -# endif - -# ifdef MSWIN - // Apparently MS-Windows printf() may cause a crash when we give it 8-bit - // text while it's expecting text in the current locale. This call avoids - // that. - setlocale(LC_CTYPE, "C"); -# endif - -# ifdef FEAT_GETTEXT - { - int mustfree = FALSE; - char_u *p; - -# ifdef DYNAMIC_GETTEXT - // Initialize the gettext library - dyn_libintl_init(); -# endif - // expand_env() doesn't work yet, because g_chartab[] is not - // initialized yet, call vim_getenv() directly - p = vim_getenv((char_u *)"VIMRUNTIME", &mustfree); - if (p != NULL && *p != NUL) - { - vim_snprintf((char *)NameBuff, MAXPATHL, "%s/lang", p); - bindtextdomain(VIMPACKAGE, (char *)NameBuff); - } - if (mustfree) - vim_free(p); - textdomain(VIMPACKAGE); - } -# endif -} -#endif - /* * Get the name of the display, before gui_prepare() removes it from * argv[]. Used for the xterm-clipboard display. diff --git a/src/proto.h b/src/proto.h --- a/src/proto.h +++ b/src/proto.h @@ -101,6 +101,7 @@ extern int _stricoll(char *a, char *b); # include "insexpand.pro" # include "json.pro" # include "list.pro" +# include "locale.pro" # include "blob.pro" # include "main.pro" # include "map.pro" diff --git a/src/proto/ex_cmds2.pro b/src/proto/ex_cmds2.pro --- a/src/proto/ex_cmds2.pro +++ b/src/proto/ex_cmds2.pro @@ -15,10 +15,4 @@ void ex_pyxfile(exarg_T *eap); void ex_pyx(exarg_T *eap); void ex_pyxdo(exarg_T *eap); void ex_checktime(exarg_T *eap); -char_u *get_mess_lang(void); -void set_lang_var(void); -void ex_language(exarg_T *eap); -void free_locales(void); -char_u *get_lang_arg(expand_T *xp, int idx); -char_u *get_locales(expand_T *xp, int idx); /* vim: set ft=c : */ diff --git a/src/proto/locale.pro b/src/proto/locale.pro new file mode 100644 --- /dev/null +++ b/src/proto/locale.pro @@ -0,0 +1,9 @@ +/* locale.c */ +char_u *get_mess_lang(void); +void set_lang_var(void); +void init_locale(void); +void ex_language(exarg_T *eap); +void free_locales(void); +char_u *get_lang_arg(expand_T *xp, int idx); +char_u *get_locales(expand_T *xp, int idx); +/* vim: set ft=c : */ diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -755,6 +755,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1269, +/**/ 1268, /**/ 1267,