Mercurial > vim
view src/help.c @ 32968:1902f8e697ed
runtime(menu): define shortcut for File->Open Tab (#12895)
Commit: https://github.com/vim/vim/commit/e059fae100448fee4b581dd5d90ee853ea18de7e
Author: Christian Brabandt <cb@256bit.org>
Date: Wed Aug 23 17:07:55 2023 +0100
runtime(menu): define shortcut for File->Open Tab (https://github.com/vim/vim/issues/12895)
Seems missing as noted by Antonio Giovanni Colombo. So add it and use
the 'T' as shortcut, which does not seem to be used in the File dialog.
Verified on Windows.
Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Antonio Giovanni Colombo <azc100@gmail.com>
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Wed, 23 Aug 2023 18:15:04 +0200 |
parents | 61389a392fe8 |
children | 9e093c96dff6 |
line wrap: on
line source
/* 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. */ /* * help.c: functions for Vim help */ #include "vim.h" /* * ":help": open a read-only window on a help file */ void ex_help(exarg_T *eap) { char_u *arg; char_u *tag; FILE *helpfd; // file descriptor of help file int n; int i; win_T *wp; int num_matches; char_u **matches; char_u *p; int empty_fnum = 0; int alt_fnum = 0; buf_T *buf; #ifdef FEAT_MULTI_LANG int len; char_u *lang; #endif #ifdef FEAT_FOLDING int old_KeyTyped = KeyTyped; #endif if (ERROR_IF_ANY_POPUP_WINDOW) return; if (eap != NULL) { // A ":help" command ends at the first LF, or at a '|' that is // followed by some text. Set nextcmd to the following command. for (arg = eap->arg; *arg; ++arg) { if (*arg == '\n' || *arg == '\r' || (*arg == '|' && arg[1] != NUL && arg[1] != '|')) { *arg++ = NUL; eap->nextcmd = arg; break; } } arg = eap->arg; if (eap->forceit && *arg == NUL && !curbuf->b_help) { emsg(_(e_dont_panic)); return; } if (eap->skip) // not executing commands return; } else arg = (char_u *)""; // remove trailing blanks p = arg + STRLEN(arg) - 1; while (p > arg && VIM_ISWHITE(*p) && p[-1] != '\\') *p-- = NUL; #ifdef FEAT_MULTI_LANG // Check for a specified language lang = check_help_lang(arg); #endif // When no argument given go to the index. if (*arg == NUL) arg = (char_u *)"help.txt"; // Check if there is a match for the argument. n = find_help_tags(arg, &num_matches, &matches, eap != NULL && eap->forceit); i = 0; #ifdef FEAT_MULTI_LANG if (n != FAIL && lang != NULL) // Find first item with the requested language. for (i = 0; i < num_matches; ++i) { len = (int)STRLEN(matches[i]); if (len > 3 && matches[i][len - 3] == '@' && STRICMP(matches[i] + len - 2, lang) == 0) break; } #endif if (i >= num_matches || n == FAIL) { #ifdef FEAT_MULTI_LANG if (lang != NULL) semsg(_(e_sorry_no_str_help_for_str), lang, arg); else #endif semsg(_(e_sorry_no_help_for_str), arg); if (n != FAIL) FreeWild(num_matches, matches); return; } // The first match (in the requested language) is the best match. tag = vim_strsave(matches[i]); FreeWild(num_matches, matches); #ifdef FEAT_GUI need_mouse_correct = TRUE; #endif // Re-use an existing help window or open a new one. // Always open a new one for ":tab help". if (!bt_help(curwin->w_buffer) || cmdmod.cmod_tab != 0) { if (cmdmod.cmod_tab != 0) wp = NULL; else FOR_ALL_WINDOWS(wp) if (bt_help(wp->w_buffer)) break; if (wp != NULL && wp->w_buffer->b_nwindows > 0) win_enter(wp, TRUE); else { // There is no help window yet. // Try to open the file specified by the "helpfile" option. if ((helpfd = mch_fopen((char *)p_hf, READBIN)) == NULL) { smsg(_("Sorry, help file \"%s\" not found"), p_hf); goto erret; } fclose(helpfd); // Split off help window; put it at far top if no position // specified, the current window is vertically split and // narrow. n = WSP_HELP; if (cmdmod.cmod_split == 0 && curwin->w_width != Columns && curwin->w_width < 80) n |= p_sb ? WSP_BOT : WSP_TOP; if (win_split(0, n) == FAIL) goto erret; if (curwin->w_height < p_hh) win_setheight((int)p_hh); // Open help file (do_ecmd() will set b_help flag, readfile() will // set b_p_ro flag). // Set the alternate file to the previously edited file. alt_fnum = curbuf->b_fnum; (void)do_ecmd(0, NULL, NULL, NULL, ECMD_LASTL, ECMD_HIDE + ECMD_SET_HELP, NULL); // buffer is still open, don't store info if ((cmdmod.cmod_flags & CMOD_KEEPALT) == 0) curwin->w_alt_fnum = alt_fnum; empty_fnum = curbuf->b_fnum; } } if (!p_im) restart_edit = 0; // don't want insert mode in help file #ifdef FEAT_FOLDING // Restore KeyTyped, setting 'filetype=help' may reset it. // It is needed for do_tag top open folds under the cursor. KeyTyped = old_KeyTyped; #endif if (tag != NULL) do_tag(tag, DT_HELP, 1, FALSE, TRUE); // Delete the empty buffer if we're not using it. Careful: autocommands // may have jumped to another window, check that the buffer is not in a // window. if (empty_fnum != 0 && curbuf->b_fnum != empty_fnum) { buf = buflist_findnr(empty_fnum); if (buf != NULL && buf->b_nwindows == 0) wipe_buffer(buf, TRUE); } // keep the previous alternate file if (alt_fnum != 0 && curwin->w_alt_fnum == empty_fnum && (cmdmod.cmod_flags & CMOD_KEEPALT) == 0) curwin->w_alt_fnum = alt_fnum; erret: vim_free(tag); } /* * ":helpclose": Close one help window */ void ex_helpclose(exarg_T *eap UNUSED) { win_T *win; FOR_ALL_WINDOWS(win) { if (bt_help(win->w_buffer)) { win_close(win, FALSE); return; } } } #if defined(FEAT_MULTI_LANG) || defined(PROTO) /* * In an argument search for a language specifiers in the form "@xx". * Changes the "@" to NUL if found, and returns a pointer to "xx". * Returns NULL if not found. */ char_u * check_help_lang(char_u *arg) { int len = (int)STRLEN(arg); if (len >= 3 && arg[len - 3] == '@' && ASCII_ISALPHA(arg[len - 2]) && ASCII_ISALPHA(arg[len - 1])) { arg[len - 3] = NUL; // remove the '@' return arg + len - 2; } return NULL; } #endif /* * Return a heuristic indicating how well the given string matches. The * smaller the number, the better the match. This is the order of priorities, * from best match to worst match: * - Match with least alphanumeric characters is better. * - Match with least total characters is better. * - Match towards the start is better. * - Match starting with "+" is worse (feature instead of command) * Assumption is made that the matched_string passed has already been found to * match some string for which help is requested. webb. */ int help_heuristic( char_u *matched_string, int offset, // offset for match int wrong_case) // no matching case { int num_letters; char_u *p; num_letters = 0; for (p = matched_string; *p; p++) if (ASCII_ISALNUM(*p)) num_letters++; // Multiply the number of letters by 100 to give it a much bigger // weighting than the number of characters. // If there only is a match while ignoring case, add 5000. // If the match starts in the middle of a word, add 10000 to put it // somewhere in the last half. // If the match is more than 2 chars from the start, multiply by 200 to // put it after matches at the start. if (ASCII_ISALNUM(matched_string[offset]) && offset > 0 && ASCII_ISALNUM(matched_string[offset - 1])) offset += 10000; else if (offset > 2) offset *= 200; if (wrong_case) offset += 5000; // Features are less interesting than the subjects themselves, but "+" // alone is not a feature. if (matched_string[0] == '+' && matched_string[1] != NUL) offset += 100; return (int)(100 * num_letters + STRLEN(matched_string) + offset); } /* * Compare functions for qsort() below, that checks the help heuristics number * that has been put after the tagname by find_tags(). */ static int help_compare(const void *s1, const void *s2) { char *p1; char *p2; int cmp; p1 = *(char **)s1 + strlen(*(char **)s1) + 1; p2 = *(char **)s2 + strlen(*(char **)s2) + 1; // Compare by help heuristic number first. cmp = strcmp(p1, p2); if (cmp != 0) return cmp; // Compare by strings as tie-breaker when same heuristic number. return strcmp(*(char **)s1, *(char **)s2); } /* * Find all help tags matching "arg", sort them and return in matches[], with * the number of matches in num_matches. * The matches will be sorted with a "best" match algorithm. * When "keep_lang" is TRUE try keeping the language of the current buffer. */ int find_help_tags( char_u *arg, int *num_matches, char_u ***matches, int keep_lang) { char_u *s, *d; int i; // Specific tags that either have a specific replacement or won't go // through the generic rules. static char *(except_tbl[][2]) = { {"*", "star"}, {"g*", "gstar"}, {"[*", "[star"}, {"]*", "]star"}, {":*", ":star"}, {"/*", "/star"}, {"/\\*", "/\\\\star"}, {"\"*", "quotestar"}, {"**", "starstar"}, {"cpo-*", "cpo-star"}, {"/\\(\\)", "/\\\\(\\\\)"}, {"/\\%(\\)", "/\\\\%(\\\\)"}, {"?", "?"}, {"??", "??"}, {":?", ":?"}, {"?<CR>", "?<CR>"}, {"g?", "g?"}, {"g?g?", "g?g?"}, {"g??", "g??"}, {"-?", "-?"}, {"q?", "q?"}, {"v_g?", "v_g?"}, {"/\\?", "/\\\\?"}, {"/\\z(\\)", "/\\\\z(\\\\)"}, {"\\=", "\\\\="}, {":s\\=", ":s\\\\="}, {"[count]", "\\[count]"}, {"[quotex]", "\\[quotex]"}, {"[range]", "\\[range]"}, {":[range]", ":\\[range]"}, {"[pattern]", "\\[pattern]"}, {"\\|", "\\\\bar"}, {"\\%$", "/\\\\%\\$"}, {"s/\\~", "s/\\\\\\~"}, {"s/\\U", "s/\\\\U"}, {"s/\\L", "s/\\\\L"}, {"s/\\1", "s/\\\\1"}, {"s/\\2", "s/\\\\2"}, {"s/\\3", "s/\\\\3"}, {"s/\\9", "s/\\\\9"}, {NULL, NULL} }; static char *(expr_table[]) = {"!=?", "!~?", "<=?", "<?", "==?", "=~?", ">=?", ">?", "is?", "isnot?"}; int flags; d = IObuff; // assume IObuff is long enough! d[0] = NUL; if (STRNICMP(arg, "expr-", 5) == 0) { // When the string starting with "expr-" and containing '?' and matches // the table, it is taken literally (but ~ is escaped). Otherwise '?' // is recognized as a wildcard. for (i = (int)ARRAY_LENGTH(expr_table); --i >= 0; ) if (STRCMP(arg + 5, expr_table[i]) == 0) { int si = 0, di = 0; for (;;) { if (arg[si] == '~') d[di++] = '\\'; d[di++] = arg[si]; if (arg[si] == NUL) break; ++si; } break; } } else { // Recognize a few exceptions to the rule. Some strings that contain // '*'are changed to "star", otherwise '*' is recognized as a wildcard. for (i = 0; except_tbl[i][0] != NULL; ++i) if (STRCMP(arg, except_tbl[i][0]) == 0) { STRCPY(d, except_tbl[i][1]); break; } } if (d[0] == NUL) // no match in table { // Replace "\S" with "/\\S", etc. Otherwise every tag is matched. // Also replace "\%^" and "\%(", they match every tag too. // Also "\zs", "\z1", etc. // Also "\@<", "\@=", "\@<=", etc. // And also "\_$" and "\_^". if (arg[0] == '\\' && ((arg[1] != NUL && arg[2] == NUL) || (vim_strchr((char_u *)"%_z@", arg[1]) != NULL && arg[2] != NUL))) { vim_snprintf((char *)d, IOSIZE, "/\\\\%s", arg + 1); // Check for "/\\_$", should be "/\\_\$" if (d[3] == '_' && d[4] == '$') STRCPY(d + 4, "\\$"); } else { // Replace: // "[:...:]" with "\[:...:]" // "[++...]" with "\[++...]" // "\{" with "\\{" -- matching "} \}" if ((arg[0] == '[' && (arg[1] == ':' || (arg[1] == '+' && arg[2] == '+'))) || (arg[0] == '\\' && arg[1] == '{')) *d++ = '\\'; // If tag starts with "('", skip the "(". Fixes CTRL-] on ('option'. if (*arg == '(' && arg[1] == '\'') arg++; for (s = arg; *s; ++s) { // Replace "|" with "bar" and '"' with "quote" to match the name of // the tags for these commands. // Replace "*" with ".*" and "?" with "." to match command line // completion. // Insert a backslash before '~', '$' and '.' to avoid their // special meaning. if (d - IObuff > IOSIZE - 10) // getting too long!? break; switch (*s) { case '|': STRCPY(d, "bar"); d += 3; continue; case '"': STRCPY(d, "quote"); d += 5; continue; case '*': *d++ = '.'; break; case '?': *d++ = '.'; continue; case '$': case '.': case '~': *d++ = '\\'; break; } // Replace "^x" by "CTRL-X". Don't do this for "^_" to make // ":help i_^_CTRL-D" work. // Insert '-' before and after "CTRL-X" when applicable. if (*s < ' ' || (*s == '^' && s[1] && (ASCII_ISALPHA(s[1]) || vim_strchr((char_u *)"?@[\\]^", s[1]) != NULL))) { if (d > IObuff && d[-1] != '_' && d[-1] != '\\') *d++ = '_'; // prepend a '_' to make x_CTRL-x STRCPY(d, "CTRL-"); d += 5; if (*s < ' ') { *d++ = *s + '@'; if (d[-1] == '\\') *d++ = '\\'; // double a backslash } else *d++ = *++s; if (s[1] != NUL && s[1] != '_') *d++ = '_'; // append a '_' continue; } else if (*s == '^') // "^" or "CTRL-^" or "^_" *d++ = '\\'; // Insert a backslash before a backslash after a slash, for search // pattern tags: "/\|" --> "/\\|". else if (s[0] == '\\' && s[1] != '\\' && *arg == '/' && s == arg + 1) *d++ = '\\'; // "CTRL-\_" -> "CTRL-\\_" to avoid the special meaning of "\_" in // "CTRL-\_CTRL-N" if (STRNICMP(s, "CTRL-\\_", 7) == 0) { STRCPY(d, "CTRL-\\\\"); d += 7; s += 6; } *d++ = *s; // If tag contains "({" or "([", tag terminates at the "(". // This is for help on functions, e.g.: abs({expr}). if (*s == '(' && (s[1] == '{' || s[1] =='[')) break; // If tag starts with ', toss everything after a second '. Fixes // CTRL-] on 'option'. (would include the trailing '.'). if (*s == '\'' && s > arg && *arg == '\'') break; // Also '{' and '}'. if (*s == '}' && s > arg && *arg == '{') break; } *d = NUL; if (*IObuff == '`') { if (d > IObuff + 2 && d[-1] == '`') { // remove the backticks from `command` mch_memmove(IObuff, IObuff + 1, STRLEN(IObuff)); d[-2] = NUL; } else if (d > IObuff + 3 && d[-2] == '`' && d[-1] == ',') { // remove the backticks and comma from `command`, mch_memmove(IObuff, IObuff + 1, STRLEN(IObuff)); d[-3] = NUL; } else if (d > IObuff + 4 && d[-3] == '`' && d[-2] == '\\' && d[-1] == '.') { // remove the backticks and dot from `command`\. mch_memmove(IObuff, IObuff + 1, STRLEN(IObuff)); d[-4] = NUL; } } } } *matches = (char_u **)""; *num_matches = 0; flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE | TAG_NO_TAGFUNC; if (keep_lang) flags |= TAG_KEEP_LANG; if (find_tags(IObuff, num_matches, matches, flags, (int)MAXCOL, NULL) == OK && *num_matches > 0) { // Sort the matches found on the heuristic number that is after the // tag name. qsort((void *)*matches, (size_t)*num_matches, sizeof(char_u *), help_compare); // Delete more than TAG_MANY to reduce the size of the listing. while (*num_matches > TAG_MANY) vim_free((*matches)[--*num_matches]); } return OK; } #ifdef FEAT_MULTI_LANG /* * Cleanup matches for help tags: * Remove "@ab" if the top of 'helplang' is "ab" and the language of the first * tag matches it. Otherwise remove "@en" if "en" is the only language. */ void cleanup_help_tags(int num_file, char_u **file) { int i, j; int len; char_u buf[4]; char_u *p = buf; if (p_hlg[0] != NUL && (p_hlg[0] != 'e' || p_hlg[1] != 'n')) { *p++ = '@'; *p++ = p_hlg[0]; *p++ = p_hlg[1]; } *p = NUL; for (i = 0; i < num_file; ++i) { len = (int)STRLEN(file[i]) - 3; if (len <= 0) continue; if (STRCMP(file[i] + len, "@en") == 0) { // Sorting on priority means the same item in another language may // be anywhere. Search all items for a match up to the "@en". for (j = 0; j < num_file; ++j) if (j != i && (int)STRLEN(file[j]) == len + 3 && STRNCMP(file[i], file[j], len + 1) == 0) break; if (j == num_file) // item only exists with @en, remove it file[i][len] = NUL; } } if (*buf != NUL) for (i = 0; i < num_file; ++i) { len = (int)STRLEN(file[i]) - 3; if (len <= 0) continue; if (STRCMP(file[i] + len, buf) == 0) { // remove the default language file[i][len] = NUL; } } } #endif /* * Called when starting to edit a buffer for a help file. */ void prepare_help_buffer(void) { char_u *p; curbuf->b_help = TRUE; #ifdef FEAT_QUICKFIX set_string_option_direct((char_u *)"buftype", -1, (char_u *)"help", OPT_FREE|OPT_LOCAL, 0); #endif // Always set these options after jumping to a help tag, because the // user may have an autocommand that gets in the way. // When adding an option here, also update the help file helphelp.txt. // Accept all ASCII chars for keywords, except ' ', '*', '"', '|', and // latin1 word characters (for translated help files). // Only set it when needed, buf_init_chartab() is some work. p = (char_u *)"!-~,^*,^|,^\",192-255"; if (STRCMP(curbuf->b_p_isk, p) != 0) { set_string_option_direct((char_u *)"isk", -1, p, OPT_FREE|OPT_LOCAL, 0); check_buf_options(curbuf); (void)buf_init_chartab(curbuf, FALSE); } #ifdef FEAT_FOLDING // Don't use the global foldmethod. set_string_option_direct((char_u *)"fdm", -1, (char_u *)"manual", OPT_FREE|OPT_LOCAL, 0); #endif curbuf->b_p_ts = 8; // 'tabstop' is 8 curwin->w_p_list = FALSE; // no list mode curbuf->b_p_ma = FALSE; // not modifiable curbuf->b_p_bin = FALSE; // reset 'bin' before reading file curwin->w_p_nu = 0; // no line numbers curwin->w_p_rnu = 0; // no relative line numbers RESET_BINDING(curwin); // no scroll or cursor binding #ifdef FEAT_ARABIC curwin->w_p_arab = FALSE; // no arabic mode #endif #ifdef FEAT_RIGHTLEFT curwin->w_p_rl = FALSE; // help window is left-to-right #endif #ifdef FEAT_FOLDING curwin->w_p_fen = FALSE; // No folding in the help window #endif #ifdef FEAT_DIFF curwin->w_p_diff = FALSE; // No 'diff' #endif #ifdef FEAT_SPELL curwin->w_p_spell = FALSE; // No spell checking #endif set_buflisted(FALSE); } /* * After reading a help file: May cleanup a help buffer when syntax * highlighting is not used. */ void fix_help_buffer(void) { linenr_T lnum; char_u *line; int in_example = FALSE; int len; char_u *fname; char_u *p; char_u *rt; int mustfree; // Set filetype to "help" if still needed. if (STRCMP(curbuf->b_p_ft, "help") != 0) { ++curbuf_lock; set_option_value_give_err((char_u *)"ft", 0L, (char_u *)"help", OPT_LOCAL); --curbuf_lock; } #ifdef FEAT_SYN_HL if (!syntax_present(curwin)) #endif { for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; ++lnum) { line = ml_get_buf(curbuf, lnum, FALSE); len = (int)STRLEN(line); if (in_example && len > 0 && !VIM_ISWHITE(line[0])) { // End of example: non-white or '<' in first column. if (line[0] == '<') { // blank-out a '<' in the first column line = ml_get_buf(curbuf, lnum, TRUE); line[0] = ' '; } in_example = FALSE; } if (!in_example && len > 0) { if (line[len - 1] == '>' && (len == 1 || line[len - 2] == ' ')) { // blank-out a '>' in the last column (start of example) line = ml_get_buf(curbuf, lnum, TRUE); line[len - 1] = ' '; in_example = TRUE; } else if (line[len - 1] == '~') { // blank-out a '~' at the end of line (header marker) line = ml_get_buf(curbuf, lnum, TRUE); line[len - 1] = ' '; } } } } // In the "help.txt" and "help.abx" file, add the locally added help // files. This uses the very first line in the help file. fname = gettail(curbuf->b_fname); if (fnamecmp(fname, "help.txt") == 0 #ifdef FEAT_MULTI_LANG || (fnamencmp(fname, "help.", 5) == 0 && ASCII_ISALPHA(fname[5]) && ASCII_ISALPHA(fname[6]) && TOLOWER_ASC(fname[7]) == 'x' && fname[8] == NUL) #endif ) { for (lnum = 1; lnum < curbuf->b_ml.ml_line_count; ++lnum) { line = ml_get_buf(curbuf, lnum, FALSE); if (strstr((char *)line, "*local-additions*") == NULL) continue; // Go through all directories in 'runtimepath', skipping // $VIMRUNTIME. p = p_rtp; while (*p != NUL) { copy_option_part(&p, NameBuff, MAXPATHL, ","); mustfree = FALSE; rt = vim_getenv((char_u *)"VIMRUNTIME", &mustfree); if (rt != NULL && fullpathcmp(rt, NameBuff, FALSE, TRUE) != FPC_SAME) { int fcount; char_u **fnames; FILE *fd; char_u *s; int fi; vimconv_T vc; char_u *cp; // Find all "doc/ *.txt" files in this directory. add_pathsep(NameBuff); #ifdef FEAT_MULTI_LANG STRCAT(NameBuff, "doc/*.??[tx]"); #else STRCAT(NameBuff, "doc/*.txt"); #endif if (gen_expand_wildcards(1, &NameBuff, &fcount, &fnames, EW_FILE|EW_SILENT) == OK && fcount > 0) { #ifdef FEAT_MULTI_LANG int i1, i2; char_u *f1, *f2; char_u *t1, *t2; char_u *e1, *e2; // If foo.abx is found use it instead of foo.txt in // the same directory. for (i1 = 0; i1 < fcount; ++i1) { f1 = fnames[i1]; t1 = gettail(f1); e1 = vim_strrchr(t1, '.'); if (fnamecmp(e1, ".txt") != 0 && fnamecmp(e1, fname + 4) != 0) { // Not .txt and not .abx, remove it. VIM_CLEAR(fnames[i1]); continue; } for (i2 = i1 + 1; i2 < fcount; ++i2) { f2 = fnames[i2]; if (f2 == NULL) continue; t2 = gettail(f2); e2 = vim_strrchr(t2, '.'); if (e1 - f1 != e2 - f2 || fnamencmp(f1, f2, e1 - f1) != 0) continue; if (fnamecmp(e1, ".txt") == 0 && fnamecmp(e2, fname + 4) == 0) // use .abx instead of .txt VIM_CLEAR(fnames[i1]); } } #endif for (fi = 0; fi < fcount; ++fi) { if (fnames[fi] == NULL) continue; fd = mch_fopen((char *)fnames[fi], "r"); if (fd != NULL) { vim_fgets(IObuff, IOSIZE, fd); if (IObuff[0] == '*' && (s = vim_strchr(IObuff + 1, '*')) != NULL) { int this_utf = MAYBE; // Change tag definition to a // reference and remove <CR>/<NL>. IObuff[0] = '|'; *s = '|'; while (*s != NUL) { if (*s == '\r' || *s == '\n') *s = NUL; // The text is utf-8 when a byte // above 127 is found and no // illegal byte sequence is found. if (*s >= 0x80 && this_utf != FALSE) { int l; this_utf = TRUE; l = utf_ptr2len(s); if (l == 1) this_utf = FALSE; s += l - 1; } ++s; } // The help file is latin1 or utf-8; // conversion to the current // 'encoding' may be required. vc.vc_type = CONV_NONE; convert_setup(&vc, (char_u *)( this_utf == TRUE ? "utf-8" : "latin1"), p_enc); if (vc.vc_type == CONV_NONE) // No conversion needed. cp = IObuff; else { // Do the conversion. If it fails // use the unconverted text. cp = string_convert(&vc, IObuff, NULL); if (cp == NULL) cp = IObuff; } convert_setup(&vc, NULL, NULL); ml_append(lnum, cp, (colnr_T)0, FALSE); if (cp != IObuff) vim_free(cp); ++lnum; } fclose(fd); } } FreeWild(fcount, fnames); } } if (mustfree) vim_free(rt); } break; } } } /* * ":exusage" */ void ex_exusage(exarg_T *eap UNUSED) { do_cmdline_cmd((char_u *)"help ex-cmd-index"); } /* * ":viusage" */ void ex_viusage(exarg_T *eap UNUSED) { do_cmdline_cmd((char_u *)"help normal-index"); } /* * Generate tags in one help directory. */ static void helptags_one( char_u *dir, // doc directory char_u *ext, // suffix, ".txt", ".itx", ".frx", etc. char_u *tagfname, // "tags" for English, "tags-fr" for French. int add_help_tags, // add "help-tags" tag int ignore_writeerr) // ignore write error { FILE *fd_tags; FILE *fd; garray_T ga; int res; int filecount; char_u **files; char_u *p1, *p2; int fi; char_u *s; int i; char_u *fname; int dirlen; int utf8 = MAYBE; int this_utf8; int firstline; int in_example; int len; int mix = FALSE; // detected mixed encodings // Find all *.txt files. dirlen = (int)STRLEN(dir); STRCPY(NameBuff, dir); STRCAT(NameBuff, "/**/*"); STRCAT(NameBuff, ext); res = gen_expand_wildcards(1, &NameBuff, &filecount, &files, EW_FILE|EW_SILENT); if (res == FAIL || filecount == 0) { if (!got_int) semsg(_(e_no_match_str_1), NameBuff); if (res != FAIL) FreeWild(filecount, files); return; } // Open the tags file for writing. // Do this before scanning through all the files. STRCPY(NameBuff, dir); add_pathsep(NameBuff); STRCAT(NameBuff, tagfname); fd_tags = mch_fopen((char *)NameBuff, "w"); if (fd_tags == NULL) { if (!ignore_writeerr) semsg(_(e_cannot_open_str_for_writing_1), NameBuff); FreeWild(filecount, files); return; } // If using the "++t" argument or generating tags for "$VIMRUNTIME/doc" // add the "help-tags" tag. ga_init2(&ga, sizeof(char_u *), 100); if (add_help_tags || fullpathcmp((char_u *)"$VIMRUNTIME/doc", dir, FALSE, TRUE) == FPC_SAME) { if (ga_grow(&ga, 1) == FAIL) got_int = TRUE; else { s = alloc(18 + (unsigned)STRLEN(tagfname)); if (s == NULL) got_int = TRUE; else { sprintf((char *)s, "help-tags\t%s\t1\n", tagfname); ((char_u **)ga.ga_data)[ga.ga_len] = s; ++ga.ga_len; } } } // Go over all the files and extract the tags. for (fi = 0; fi < filecount && !got_int; ++fi) { fd = mch_fopen((char *)files[fi], "r"); if (fd == NULL) { semsg(_(e_unable_to_open_str_for_reading), files[fi]); continue; } fname = files[fi] + dirlen + 1; in_example = FALSE; firstline = TRUE; while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) { if (firstline) { // Detect utf-8 file by a non-ASCII char in the first line. this_utf8 = MAYBE; for (s = IObuff; *s != NUL; ++s) if (*s >= 0x80) { int l; this_utf8 = TRUE; l = utf_ptr2len(s); if (l == 1) { // Illegal UTF-8 byte sequence. this_utf8 = FALSE; break; } s += l - 1; } if (this_utf8 == MAYBE) // only ASCII characters found this_utf8 = FALSE; if (utf8 == MAYBE) // first file utf8 = this_utf8; else if (utf8 != this_utf8) { semsg(_(e_mix_of_help_file_encodings_within_language_str), files[fi]); mix = !got_int; got_int = TRUE; } firstline = FALSE; } if (in_example) { // skip over example; a non-white in the first column ends it if (vim_strchr((char_u *)" \t\n\r", IObuff[0])) continue; in_example = FALSE; } p1 = vim_strchr(IObuff, '*'); // find first '*' while (p1 != NULL) { // Use vim_strbyte() instead of vim_strchr() so that when // 'encoding' is dbcs it still works, don't find '*' in the // second byte. p2 = vim_strbyte(p1 + 1, '*'); // find second '*' if (p2 != NULL && p2 > p1 + 1) // skip "*" and "**" { for (s = p1 + 1; s < p2; ++s) if (*s == ' ' || *s == '\t' || *s == '|') break; // Only accept a *tag* when it consists of valid // characters, there is white space before it and is // followed by a white character or end-of-line. if (s == p2 && (p1 == IObuff || p1[-1] == ' ' || p1[-1] == '\t') && (vim_strchr((char_u *)" \t\n\r", s[1]) != NULL || s[1] == '\0')) { *p2 = '\0'; ++p1; if (ga_grow(&ga, 1) == FAIL) { got_int = TRUE; break; } s = alloc(p2 - p1 + STRLEN(fname) + 2); if (s == NULL) { got_int = TRUE; break; } ((char_u **)ga.ga_data)[ga.ga_len] = s; ++ga.ga_len; sprintf((char *)s, "%s\t%s", p1, fname); // find next '*' p2 = vim_strchr(p2 + 1, '*'); } } p1 = p2; } len = (int)STRLEN(IObuff); if ((len == 2 && STRCMP(&IObuff[len - 2], ">\n") == 0) || (len >= 3 && STRCMP(&IObuff[len - 3], " >\n") == 0)) in_example = TRUE; line_breakcheck(); } fclose(fd); } FreeWild(filecount, files); if (!got_int) { // Sort the tags. if (ga.ga_data != NULL) sort_strings((char_u **)ga.ga_data, ga.ga_len); // Check for duplicates. for (i = 1; i < ga.ga_len; ++i) { p1 = ((char_u **)ga.ga_data)[i - 1]; p2 = ((char_u **)ga.ga_data)[i]; while (*p1 == *p2) { if (*p2 == '\t') { *p2 = NUL; vim_snprintf((char *)NameBuff, MAXPATHL, _(e_duplicate_tag_str_in_file_str_str), ((char_u **)ga.ga_data)[i], dir, p2 + 1); emsg((char *)NameBuff); *p2 = '\t'; break; } ++p1; ++p2; } } if (utf8 == TRUE) fprintf(fd_tags, "!_TAG_FILE_ENCODING\tutf-8\t//\n"); // Write the tags into the file. for (i = 0; i < ga.ga_len; ++i) { s = ((char_u **)ga.ga_data)[i]; if (STRNCMP(s, "help-tags\t", 10) == 0) // help-tags entry was added in formatted form fputs((char *)s, fd_tags); else { fprintf(fd_tags, "%s\t/*", s); for (p1 = s; *p1 != '\t'; ++p1) { // insert backslash before '\\' and '/' if (*p1 == '\\' || *p1 == '/') putc('\\', fd_tags); putc(*p1, fd_tags); } fprintf(fd_tags, "*\n"); } } } if (mix) got_int = FALSE; // continue with other languages for (i = 0; i < ga.ga_len; ++i) vim_free(((char_u **)ga.ga_data)[i]); ga_clear(&ga); fclose(fd_tags); // there is no check for an error... } /* * Generate tags in one help directory, taking care of translations. */ static void do_helptags(char_u *dirname, int add_help_tags, int ignore_writeerr) { #ifdef FEAT_MULTI_LANG int len; int i, j; garray_T ga; char_u lang[2]; char_u ext[5]; char_u fname[8]; int filecount; char_u **files; // Get a list of all files in the help directory and in subdirectories. STRCPY(NameBuff, dirname); add_pathsep(NameBuff); STRCAT(NameBuff, "**"); if (gen_expand_wildcards(1, &NameBuff, &filecount, &files, EW_FILE|EW_SILENT) == FAIL || filecount == 0) { semsg(_(e_no_match_str_1), NameBuff); return; } // Go over all files in the directory to find out what languages are // present. ga_init2(&ga, 1, 10); for (i = 0; i < filecount; ++i) { len = (int)STRLEN(files[i]); if (len <= 4) continue; if (STRICMP(files[i] + len - 4, ".txt") == 0) { // ".txt" -> language "en" lang[0] = 'e'; lang[1] = 'n'; } else if (files[i][len - 4] == '.' && ASCII_ISALPHA(files[i][len - 3]) && ASCII_ISALPHA(files[i][len - 2]) && TOLOWER_ASC(files[i][len - 1]) == 'x') { // ".abx" -> language "ab" lang[0] = TOLOWER_ASC(files[i][len - 3]); lang[1] = TOLOWER_ASC(files[i][len - 2]); } else continue; // Did we find this language already? for (j = 0; j < ga.ga_len; j += 2) if (STRNCMP(lang, ((char_u *)ga.ga_data) + j, 2) == 0) break; if (j == ga.ga_len) { // New language, add it. if (ga_grow(&ga, 2) == FAIL) break; ((char_u *)ga.ga_data)[ga.ga_len++] = lang[0]; ((char_u *)ga.ga_data)[ga.ga_len++] = lang[1]; } } // Loop over the found languages to generate a tags file for each one. for (j = 0; j < ga.ga_len; j += 2) { STRCPY(fname, "tags-xx"); fname[5] = ((char_u *)ga.ga_data)[j]; fname[6] = ((char_u *)ga.ga_data)[j + 1]; if (fname[5] == 'e' && fname[6] == 'n') { // English is an exception: use ".txt" and "tags". fname[4] = NUL; STRCPY(ext, ".txt"); } else { // Language "ab" uses ".abx" and "tags-ab". STRCPY(ext, ".xxx"); ext[1] = fname[5]; ext[2] = fname[6]; } helptags_one(dirname, ext, fname, add_help_tags, ignore_writeerr); } ga_clear(&ga); FreeWild(filecount, files); #else // No language support, just use "*.txt" and "tags". helptags_one(dirname, (char_u *)".txt", (char_u *)"tags", add_help_tags, ignore_writeerr); #endif } static void helptags_cb(char_u *fname, void *cookie) { do_helptags(fname, *(int *)cookie, TRUE); } /* * ":helptags" */ void ex_helptags(exarg_T *eap) { expand_T xpc; char_u *dirname; int add_help_tags = FALSE; // Check for ":helptags ++t {dir}". if (STRNCMP(eap->arg, "++t", 3) == 0 && VIM_ISWHITE(eap->arg[3])) { add_help_tags = TRUE; eap->arg = skipwhite(eap->arg + 3); } if (STRCMP(eap->arg, "ALL") == 0) { do_in_path(p_rtp, "", (char_u *)"doc", DIP_ALL + DIP_DIR, helptags_cb, &add_help_tags); } else { ExpandInit(&xpc); xpc.xp_context = EXPAND_DIRECTORIES; dirname = ExpandOne(&xpc, eap->arg, NULL, WILD_LIST_NOTFOUND|WILD_SILENT, WILD_EXPAND_FREE); if (dirname == NULL || !mch_isdir(dirname)) semsg(_(e_not_a_directory_str), eap->arg); else do_helptags(dirname, add_help_tags, FALSE); vim_free(dirname); } }