/* * Duplicate the menu item text and then process to see if a mnemonic key * and/or accelerator text has been identified. * Returns a pointer to allocated memory, or NULL for failure. * If mnemonic != NULL, *mnemonic is set to the character after the first '&'. * If actext != NULL, *actext is set to the text after the first TAB. */ static char_u *menu_text(char_u *str, int *mnemonic, char_u **actext) { char_u *p; char_u *text; /* Locate accelerator text, after the first TAB */ p = vim_strchr(str, TAB); if (p != NULL) { if (actext != NULL) *actext = vim_strsave(p + 1); text = vim_strnsave(str, (int)(p - str)); } else text = vim_strsave(str); /* Find mnemonic characters "&a" and reduce "&&" to "&". */ for (p = text; p != NULL; ) { p = vim_strchr(p, '&'); if (p != NULL) { if (p[1] == NUL) /* trailing "&" */ break; if (mnemonic != NULL && p[1] != '&') *mnemonic = p[1]; STRMOVE(p, p + 1); p = p + 1; } } return text; }
static char * strerror_win32(int eno) { static LPVOID msgbuf = NULL; char_u *ptr; if (msgbuf) LocalFree(msgbuf); FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, eno, MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), (LPTSTR) &msgbuf, 0, NULL); /* chomp \r or \n */ for (ptr = (char_u *)msgbuf; *ptr; ptr++) switch (*ptr) { case '\r': STRMOVE(ptr, ptr + 1); ptr--; break; case '\n': if (*(ptr + 1) == '\0') *ptr = '\0'; else *ptr = ' '; break; } return msgbuf; }
/* * Unescape the name in the translate dictionary table. */ static void menu_unescape_name(char_u *name) { char_u *p; for (p = name; *p && *p != '.'; mb_ptr_adv(p)) if (*p == '\\') STRMOVE(p, p + 1); }
/* * Initialize the text for a new sign */ static int sign_define_init_text(sign_T *sp, char_u *text) { char_u *s; char_u *endp; int cells; int len; endp = text + (int)STRLEN(text); // Remove backslashes so that it is possible to use a space. for (s = text; s + 1 < endp; ++s) if (*s == '\\') { STRMOVE(s, s + 1); --endp; } // Count cells and check for non-printable chars if (has_mbyte) { cells = 0; for (s = text; s < endp; s += (*mb_ptr2len)(s)) { if (!vim_isprintc((*mb_ptr2char)(s))) break; cells += (*mb_ptr2cells)(s); } } else { for (s = text; s < endp; ++s) if (!vim_isprintc(*s)) break; cells = (int)(s - text); } // Currently sign text must be one or two display cells if (s != endp || cells < 1 || cells > 2) { semsg(_("E239: Invalid sign text: %s"), text); return FAIL; } vim_free(sp->sn_text); // Allocate one byte more if we need to pad up // with a space. len = (int)(endp - text + ((cells == 1) ? 1 : 0)); sp->sn_text = vim_strnsave(text, len); // For single character sign text, pad with a space. if (sp->sn_text != NULL && cells == 1) STRCPY(sp->sn_text + len - 1, " "); return OK; }
/* * Skip over this element of the menu path and return the start of the next * element. Any \ and ^Vs are removed from the current element. * "name" may be modified. */ char_u *menu_name_skip(char_u *name) { char_u *p; for (p = name; *p && *p != '.'; mb_ptr_adv(p)) { if (*p == '\\' || *p == Ctrl_V) { STRMOVE(p, p + 1); if (*p == NUL) break; } } if (*p) *p++ = NUL; return p; }
/* * Isolate the menu name. * Skip the menu name, and translate <Tab> into a real TAB. */ static char_u *menu_translate_tab_and_shift(char_u *arg_start) { char_u *arg = arg_start; while (*arg && !vim_iswhite(*arg)) { if ((*arg == '\\' || *arg == Ctrl_V) && arg[1] != NUL) arg++; else if (STRNICMP(arg, "<TAB>", 5) == 0) { *arg = TAB; STRMOVE(arg + 1, arg + 5); } arg++; } if (*arg != NUL) *arg++ = NUL; arg = skipwhite(arg); return arg; }
/* * Duplicate the menu item text and then process to see if a mnemonic key * and/or accelerator text has been identified. * Returns a pointer to allocated memory, or NULL for failure. * If mnemonic != NULL, *mnemonic is set to the character after the first '&'. * If actext != NULL, *actext is set to the text after the first TAB. */ static char_u *menu_text(char_u *str, int *mnemonic, char_u **actext) { char_u *p; char_u *text; /* Locate accelerator text, after the first TAB */ p = vim_strchr(str, TAB); if (p != NULL) { if (actext != NULL) *actext = vim_strsave(p + 1); text = vim_strnsave(str, (int)(p - str)); } else text = vim_strsave(str); /* Find mnemonic characters "&a" and reduce "&&" to "&". */ for (p = text; p != NULL; ) { p = vim_strchr(p, '&'); if (p != NULL) { if (p[1] == NUL) /* trailing "&" */ break; if (mnemonic != NULL && p[1] != '&') #if !defined(__MVS__) || defined(MOTIF390_MNEMONIC_FIXED) *mnemonic = p[1]; #else { /* * Well there is a bug in the Motif libraries on OS390 Unix. * The mnemonic keys needs to be converted to ASCII values * first. * This behavior has been seen in 2.8 and 2.9. */ char c = p[1]; __etoa_l(&c, 1); *mnemonic = c; } #endif STRMOVE(p, p + 1); p = p + 1; } } return text; }
/* * Get the stopdir string. Check that ';' is not escaped. */ char_u *vim_findfile_stopdir(char_u *buf) { char_u *r_ptr = buf; while (*r_ptr != NUL && *r_ptr != ';') { if (r_ptr[0] == '\\' && r_ptr[1] == ';') { /* Overwrite the escape char, * use STRLEN(r_ptr) to move the trailing '\0'. */ STRMOVE(r_ptr, r_ptr + 1); r_ptr++; } r_ptr++; } if (*r_ptr == ';') { *r_ptr = 0; r_ptr++; } else if (*r_ptr == NUL) r_ptr = NULL; return r_ptr; }
/* * Find a file in a search context. * The search context was created with vim_findfile_init() above. * Return a pointer to an allocated file name or NULL if nothing found. * To get all matching files call this function until you get NULL. * * If the passed search_context is NULL, NULL is returned. * * The search algorithm is depth first. To change this replace the * stack with a list (don't forget to leave partly searched directories on the * top of the list). */ char_u *vim_findfile(void *search_ctx_arg) { char_u *file_path; char_u *rest_of_wildcards; char_u *path_end = NULL; ff_stack_T *stackp; int len; int i; char_u *p; char_u *suf; ff_search_ctx_T *search_ctx; if (search_ctx_arg == NULL) return NULL; search_ctx = (ff_search_ctx_T *)search_ctx_arg; /* * filepath is used as buffer for various actions and as the storage to * return a found filename. */ file_path = xmalloc(MAXPATHL); /* store the end of the start dir -- needed for upward search */ if (search_ctx->ffsc_start_dir != NULL) path_end = &search_ctx->ffsc_start_dir[ STRLEN(search_ctx->ffsc_start_dir)]; /* upward search loop */ for (;; ) { /* downward search loop */ for (;; ) { /* check if user user wants to stop the search*/ ui_breakcheck(); if (got_int) break; /* get directory to work on from stack */ stackp = ff_pop(search_ctx); if (stackp == NULL) break; /* * TODO: decide if we leave this test in * * GOOD: don't search a directory(-tree) twice. * BAD: - check linked list for every new directory entered. * - check for double files also done below * * Here we check if we already searched this directory. * We already searched a directory if: * 1) The directory is the same. * 2) We would use the same wildcard string. * * Good if you have links on same directory via several ways * or you have selfreferences in directories (e.g. SuSE Linux 6.3: * /etc/rc.d/init.d is linked to /etc/rc.d -> endless loop) * * This check is only needed for directories we work on for the * first time (hence stackp->ff_filearray == NULL) */ if (stackp->ffs_filearray == NULL && ff_check_visited(&search_ctx->ffsc_dir_visited_list ->ffvl_visited_list, stackp->ffs_fix_path , stackp->ffs_wc_path ) == FAIL) { #ifdef FF_VERBOSE if (p_verbose >= 5) { verbose_enter_scroll(); smsg((char_u *)"Already Searched: %s (%s)", stackp->ffs_fix_path, stackp->ffs_wc_path); /* don't overwrite this either */ msg_puts((char_u *)"\n"); verbose_leave_scroll(); } #endif ff_free_stack_element(stackp); continue; } #ifdef FF_VERBOSE else if (p_verbose >= 5) { verbose_enter_scroll(); smsg((char_u *)"Searching: %s (%s)", stackp->ffs_fix_path, stackp->ffs_wc_path); /* don't overwrite this either */ msg_puts((char_u *)"\n"); verbose_leave_scroll(); } #endif /* check depth */ if (stackp->ffs_level <= 0) { ff_free_stack_element(stackp); continue; } file_path[0] = NUL; /* * If no filearray till now expand wildcards * The function expand_wildcards() can handle an array of paths * and all possible expands are returned in one array. We use this * to handle the expansion of '**' into an empty string. */ if (stackp->ffs_filearray == NULL) { char_u *dirptrs[2]; /* we use filepath to build the path expand_wildcards() should * expand. */ dirptrs[0] = file_path; dirptrs[1] = NULL; /* if we have a start dir copy it in */ if (!vim_isAbsName(stackp->ffs_fix_path) && search_ctx->ffsc_start_dir) { STRCPY(file_path, search_ctx->ffsc_start_dir); add_pathsep(file_path); } /* append the fix part of the search path */ STRCAT(file_path, stackp->ffs_fix_path); add_pathsep(file_path); rest_of_wildcards = stackp->ffs_wc_path; if (*rest_of_wildcards != NUL) { len = (int)STRLEN(file_path); if (STRNCMP(rest_of_wildcards, "**", 2) == 0) { /* pointer to the restrict byte * The restrict byte is not a character! */ p = rest_of_wildcards + 2; if (*p > 0) { (*p)--; file_path[len++] = '*'; } if (*p == 0) { /* remove '**<numb> from wildcards */ STRMOVE(rest_of_wildcards, rest_of_wildcards + 3); } else rest_of_wildcards += 3; if (stackp->ffs_star_star_empty == 0) { /* if not done before, expand '**' to empty */ stackp->ffs_star_star_empty = 1; dirptrs[1] = stackp->ffs_fix_path; } } /* * Here we copy until the next path separator or the end of * the path. If we stop at a path separator, there is * still something else left. This is handled below by * pushing every directory returned from expand_wildcards() * on the stack again for further search. */ while (*rest_of_wildcards && !vim_ispathsep(*rest_of_wildcards)) file_path[len++] = *rest_of_wildcards++; file_path[len] = NUL; if (vim_ispathsep(*rest_of_wildcards)) rest_of_wildcards++; } /* * Expand wildcards like "*" and "$VAR". * If the path is a URL don't try this. */ if (path_with_url(dirptrs[0])) { stackp->ffs_filearray = (char_u **)xmalloc(sizeof(char *)); stackp->ffs_filearray[0] = vim_strsave(dirptrs[0]); stackp->ffs_filearray_size = 1; } else /* Add EW_NOTWILD because the expanded path may contain * wildcard characters that are to be taken literally. * This is a bit of a hack. */ expand_wildcards((dirptrs[1] == NULL) ? 1 : 2, dirptrs, &stackp->ffs_filearray_size, &stackp->ffs_filearray, EW_DIR|EW_ADDSLASH|EW_SILENT|EW_NOTWILD); stackp->ffs_filearray_cur = 0; stackp->ffs_stage = 0; } else rest_of_wildcards = &stackp->ffs_wc_path[ STRLEN(stackp->ffs_wc_path)]; if (stackp->ffs_stage == 0) { /* this is the first time we work on this directory */ if (*rest_of_wildcards == NUL) { /* * We don't have further wildcards to expand, so we have to * check for the final file now. */ for (i = stackp->ffs_filearray_cur; i < stackp->ffs_filearray_size; ++i) { if (!path_with_url(stackp->ffs_filearray[i]) && !os_isdir(stackp->ffs_filearray[i])) continue; /* not a directory */ /* prepare the filename to be checked for existence * below */ STRCPY(file_path, stackp->ffs_filearray[i]); add_pathsep(file_path); STRCAT(file_path, search_ctx->ffsc_file_to_search); /* * Try without extra suffix and then with suffixes * from 'suffixesadd'. */ len = (int)STRLEN(file_path); if (search_ctx->ffsc_tagfile) suf = (char_u *)""; else suf = curbuf->b_p_sua; for (;; ) { /* if file exists and we didn't already find it */ if ((path_with_url(file_path) || (os_file_exists(file_path) && (search_ctx->ffsc_find_what == FINDFILE_BOTH || ((search_ctx->ffsc_find_what == FINDFILE_DIR) == os_isdir(file_path))))) #ifndef FF_VERBOSE && (ff_check_visited( &search_ctx->ffsc_visited_list->ffvl_visited_list, file_path , (char_u *)"" ) == OK) #endif ) { #ifdef FF_VERBOSE if (ff_check_visited( &search_ctx->ffsc_visited_list->ffvl_visited_list, file_path , (char_u *)"" ) == FAIL) { if (p_verbose >= 5) { verbose_enter_scroll(); smsg((char_u *)"Already: %s", file_path); /* don't overwrite this either */ msg_puts((char_u *)"\n"); verbose_leave_scroll(); } continue; } #endif /* push dir to examine rest of subdirs later */ stackp->ffs_filearray_cur = i + 1; ff_push(search_ctx, stackp); if (!path_with_url(file_path)) simplify_filename(file_path); if (os_dirname(ff_expand_buffer, MAXPATHL) == OK) { p = path_shorten_fname(file_path, ff_expand_buffer); if (p != NULL) STRMOVE(file_path, p); } #ifdef FF_VERBOSE if (p_verbose >= 5) { verbose_enter_scroll(); smsg((char_u *)"HIT: %s", file_path); /* don't overwrite this either */ msg_puts((char_u *)"\n"); verbose_leave_scroll(); } #endif return file_path; } /* Not found or found already, try next suffix. */ if (*suf == NUL) break; copy_option_part(&suf, file_path + len, MAXPATHL - len, ","); } } } else { /* * still wildcards left, push the directories for further * search */ for (i = stackp->ffs_filearray_cur; i < stackp->ffs_filearray_size; ++i) { if (!os_isdir(stackp->ffs_filearray[i])) continue; /* not a directory */ ff_push(search_ctx, ff_create_stack_element( stackp->ffs_filearray[i], rest_of_wildcards, stackp->ffs_level - 1, 0)); } } stackp->ffs_filearray_cur = 0; stackp->ffs_stage = 1; } /* * if wildcards contains '**' we have to descent till we reach the * leaves of the directory tree. */ if (STRNCMP(stackp->ffs_wc_path, "**", 2) == 0) { for (i = stackp->ffs_filearray_cur; i < stackp->ffs_filearray_size; ++i) { if (fnamecmp(stackp->ffs_filearray[i], stackp->ffs_fix_path) == 0) continue; /* don't repush same directory */ if (!os_isdir(stackp->ffs_filearray[i])) continue; /* not a directory */ ff_push(search_ctx, ff_create_stack_element(stackp->ffs_filearray[i], stackp->ffs_wc_path, stackp->ffs_level - 1, 1)); } } /* we are done with the current directory */ ff_free_stack_element(stackp); } /* If we reached this, we didn't find anything downwards. * Let's check if we should do an upward search. */ if (search_ctx->ffsc_start_dir && search_ctx->ffsc_stopdirs_v != NULL && !got_int) { ff_stack_T *sptr; /* is the last starting directory in the stop list? */ if (ff_path_in_stoplist(search_ctx->ffsc_start_dir, (int)(path_end - search_ctx->ffsc_start_dir), search_ctx->ffsc_stopdirs_v) == TRUE) break; /* cut of last dir */ while (path_end > search_ctx->ffsc_start_dir && vim_ispathsep(*path_end)) path_end--; while (path_end > search_ctx->ffsc_start_dir && !vim_ispathsep(path_end[-1])) path_end--; *path_end = 0; path_end--; if (*search_ctx->ffsc_start_dir == 0) break; STRCPY(file_path, search_ctx->ffsc_start_dir); add_pathsep(file_path); STRCAT(file_path, search_ctx->ffsc_fix_path); /* create a new stack entry */ sptr = ff_create_stack_element(file_path, search_ctx->ffsc_wc_path, search_ctx->ffsc_level, 0); ff_push(search_ctx, sptr); } else break; } free(file_path); return NULL; }
/// Convert the string "str[orglen]" to do ignore-case comparing. Uses the /// current locale. /// /// When "buf" is NULL returns an allocated string (NULL for out-of-memory). /// Otherwise puts the result in "buf[buflen]". /// /// @param str /// @param orglen /// @param buf /// @param buflen /// /// @return converted string. char_u* str_foldcase(char_u *str, int orglen, char_u *buf, int buflen) { garray_T ga; int i; int len = orglen; #define GA_CHAR(i) ((char_u *)ga.ga_data)[i] #define GA_PTR(i) ((char_u *)ga.ga_data + i) #define STR_CHAR(i) (buf == NULL ? GA_CHAR(i) : buf[i]) #define STR_PTR(i) (buf == NULL ? GA_PTR(i) : buf + i) // Copy "str" into "buf" or allocated memory, unmodified. if (buf == NULL) { ga_init(&ga, 1, 10); if (ga_grow(&ga, len + 1) == FAIL) { return NULL; } memmove(ga.ga_data, str, (size_t)len); ga.ga_len = len; } else { if (len >= buflen) { // Ugly! len = buflen - 1; } memmove(buf, str, (size_t)len); } if (buf == NULL) { GA_CHAR(len) = NUL; } else { buf[len] = NUL; } // Make each character lower case. i = 0; while (STR_CHAR(i) != NUL) { if (enc_utf8 || (has_mbyte && (MB_BYTE2LEN(STR_CHAR(i)) > 1))) { if (enc_utf8) { int c = utf_ptr2char(STR_PTR(i)); int olen = utf_ptr2len(STR_PTR(i)); int lc = utf_tolower(c); // Only replace the character when it is not an invalid // sequence (ASCII character or more than one byte) and // utf_tolower() doesn't return the original character. if (((c < 0x80) || (olen > 1)) && (c != lc)) { int nlen = utf_char2len(lc); // If the byte length changes need to shift the following // characters forward or backward. if (olen != nlen) { if (nlen > olen) { if ((buf == NULL) ? (ga_grow(&ga, nlen - olen + 1) == FAIL) : (len + nlen - olen >= buflen)) { // out of memory, keep old char lc = c; nlen = olen; } } if (olen != nlen) { if (buf == NULL) { STRMOVE(GA_PTR(i) + nlen, GA_PTR(i) + olen); ga.ga_len += nlen - olen; } else { STRMOVE(buf + i + nlen, buf + i + olen); len += nlen - olen; } } } (void)utf_char2bytes(lc, STR_PTR(i)); } } // skip to next multi-byte char i += (*mb_ptr2len)(STR_PTR(i)); } else { if (buf == NULL) { GA_CHAR(i) = TOLOWER_LOC(GA_CHAR(i)); } else { buf[i] = TOLOWER_LOC(buf[i]); } ++i; } } if (buf == NULL) { return (char_u *)ga.ga_data; } return buf; }
/* * Do the :menu command and relatives. */ void ex_menu ( exarg_T *eap /* Ex command arguments */ ) { char_u *menu_path; int modes; char_u *map_to; int noremap; int silent = FALSE; int special = FALSE; int unmenu; char_u *map_buf; char_u *arg; char_u *p; int i; int pri_tab[MENUDEPTH + 1]; int enable = MAYBE; /* TRUE for "menu enable", FALSE for "menu * disable */ vimmenu_T menuarg; modes = get_menu_cmd_modes(eap->cmd, eap->forceit, &noremap, &unmenu); arg = eap->arg; for (;; ) { if (STRNCMP(arg, "<script>", 8) == 0) { noremap = REMAP_SCRIPT; arg = skipwhite(arg + 8); continue; } if (STRNCMP(arg, "<silent>", 8) == 0) { silent = TRUE; arg = skipwhite(arg + 8); continue; } if (STRNCMP(arg, "<special>", 9) == 0) { special = TRUE; arg = skipwhite(arg + 9); continue; } break; } /* Locate an optional "icon=filename" argument. */ if (STRNCMP(arg, "icon=", 5) == 0) { arg += 5; while (*arg != NUL && *arg != ' ') { if (*arg == '\\') STRMOVE(arg, arg + 1); mb_ptr_adv(arg); } if (*arg != NUL) { *arg++ = NUL; arg = skipwhite(arg); } } /* * Fill in the priority table. */ for (p = arg; *p; ++p) if (!VIM_ISDIGIT(*p) && *p != '.') break; if (vim_iswhite(*p)) { for (i = 0; i < MENUDEPTH && !vim_iswhite(*arg); ++i) { pri_tab[i] = getdigits_int(&arg); if (pri_tab[i] == 0) pri_tab[i] = 500; if (*arg == '.') ++arg; } arg = skipwhite(arg); } else if (eap->addr_count && eap->line2 != 0) { pri_tab[0] = eap->line2; i = 1; } else i = 0; while (i < MENUDEPTH) pri_tab[i++] = 500; pri_tab[MENUDEPTH] = -1; /* mark end of the table */ /* * Check for "disable" or "enable" argument. */ if (STRNCMP(arg, "enable", 6) == 0 && vim_iswhite(arg[6])) { enable = TRUE; arg = skipwhite(arg + 6); } else if (STRNCMP(arg, "disable", 7) == 0 && vim_iswhite(arg[7])) { enable = FALSE; arg = skipwhite(arg + 7); } /* * If there is no argument, display all menus. */ if (*arg == NUL) { show_menus(arg, modes); return; } menu_path = arg; if (*menu_path == '.') { EMSG2(_(e_invarg2), menu_path); goto theend; } map_to = menu_translate_tab_and_shift(arg); /* * If there is only a menu name, display menus with that name. */ if (*map_to == NUL && !unmenu && enable == MAYBE) { show_menus(menu_path, modes); goto theend; } else if (*map_to != NUL && (unmenu || enable != MAYBE)) { EMSG(_(e_trailing)); goto theend; } if (enable != MAYBE) { /* * Change sensitivity of the menu. * For the PopUp menu, remove a menu for each mode separately. * Careful: menu_nable_recurse() changes menu_path. */ if (STRCMP(menu_path, "*") == 0) /* meaning: do all menus */ menu_path = (char_u *)""; if (menu_is_popup(menu_path)) { for (i = 0; i < MENU_INDEX_TIP; ++i) if (modes & (1 << i)) { p = popup_mode_name(menu_path, i); menu_nable_recurse(root_menu, p, MENU_ALL_MODES, enable); free(p); } } menu_nable_recurse(root_menu, menu_path, modes, enable); } else if (unmenu) { /* * Delete menu(s). */ if (STRCMP(menu_path, "*") == 0) /* meaning: remove all menus */ menu_path = (char_u *)""; /* * For the PopUp menu, remove a menu for each mode separately. */ if (menu_is_popup(menu_path)) { for (i = 0; i < MENU_INDEX_TIP; ++i) if (modes & (1 << i)) { p = popup_mode_name(menu_path, i); remove_menu(&root_menu, p, MENU_ALL_MODES, TRUE); free(p); } } /* Careful: remove_menu() changes menu_path */ remove_menu(&root_menu, menu_path, modes, FALSE); } else { /* * Add menu(s). * Replace special key codes. */ if (STRICMP(map_to, "<nop>") == 0) { /* "<Nop>" means nothing */ map_to = (char_u *)""; map_buf = NULL; } else if (modes & MENU_TIP_MODE) map_buf = NULL; /* Menu tips are plain text. */ else map_to = replace_termcodes(map_to, &map_buf, FALSE, TRUE, special); menuarg.modes = modes; menuarg.noremap[0] = noremap; menuarg.silent[0] = silent; add_menu_path(menu_path, &menuarg, pri_tab, map_to ); /* * For the PopUp menu, add a menu for each mode separately. */ if (menu_is_popup(menu_path)) { for (i = 0; i < MENU_INDEX_TIP; ++i) if (modes & (1 << i)) { p = popup_mode_name(menu_path, i); // Include all modes, to make ":amenu" work menuarg.modes = modes; add_menu_path(p, &menuarg, pri_tab, map_to); free(p); } } free(map_buf); } theend: ; }
/* * Convert the string "str[orglen]" to do ignore-case comparing. Uses the * current locale. * When "buf" is NULL returns an allocated string (NULL for out-of-memory). * Otherwise puts the result in "buf[buflen]". */ char_u * str_foldcase( char_u *str, int orglen, char_u *buf, int buflen) { garray_T ga; int i; int len = orglen; #define GA_CHAR(i) ((char_u *)ga.ga_data)[i] #define GA_PTR(i) ((char_u *)ga.ga_data + i) #define STR_CHAR(i) (buf == NULL ? GA_CHAR(i) : buf[i]) #define STR_PTR(i) (buf == NULL ? GA_PTR(i) : buf + i) /* Copy "str" into "buf" or allocated memory, unmodified. */ if (buf == NULL) { ga_init2(&ga, 1, 10); if (ga_grow(&ga, len + 1) == FAIL) return NULL; mch_memmove(ga.ga_data, str, (size_t)len); ga.ga_len = len; } else { if (len >= buflen) /* Ugly! */ len = buflen - 1; mch_memmove(buf, str, (size_t)len); } if (buf == NULL) GA_CHAR(len) = NUL; else buf[len] = NUL; /* Make each character lower case. */ i = 0; while (STR_CHAR(i) != NUL) { #ifdef FEAT_MBYTE if (enc_utf8 || (has_mbyte && MB_BYTE2LEN(STR_CHAR(i)) > 1)) { if (enc_utf8) { int c = utf_ptr2char(STR_PTR(i)); int olen = utf_ptr2len(STR_PTR(i)); int lc = utf_tolower(c); /* Only replace the character when it is not an invalid * sequence (ASCII character or more than one byte) and * utf_tolower() doesn't return the original character. */ if ((c < 0x80 || olen > 1) && c != lc) { int nlen = utf_char2len(lc); /* If the byte length changes need to shift the following * characters forward or backward. */ if (olen != nlen) { if (nlen > olen) { if (buf == NULL ? ga_grow(&ga, nlen - olen + 1) == FAIL : len + nlen - olen >= buflen) { /* out of memory, keep old char */ lc = c; nlen = olen; } } if (olen != nlen) { if (buf == NULL) { STRMOVE(GA_PTR(i) + nlen, GA_PTR(i) + olen); ga.ga_len += nlen - olen; } else { STRMOVE(buf + i + nlen, buf + i + olen); len += nlen - olen; } } } (void)utf_char2bytes(lc, STR_PTR(i)); } } /* skip to next multi-byte char */ i += (*mb_ptr2len)(STR_PTR(i)); } else #endif { if (buf == NULL) GA_CHAR(i) = TOLOWER_LOC(GA_CHAR(i)); else buf[i] = TOLOWER_LOC(buf[i]); ++i; } } if (buf == NULL) return (char_u *)ga.ga_data; return buf; }