mirror of
https://github.com/vim/vim
synced 2025-03-16 06:47:52 +01:00
Problem: Coverity warns about dereferencing NULL pointer. Solution: Bail out if vim_strrchr() returns NULL. (zeertzjq) ________________________________________________________________________________________________________ *** CID 1616019: (NULL_RETURNS) /src/help.c: 834 in fix_help_buffer() 828 continue; 829 t2 = gettail(f2); 830 e2 = vim_strrchr(t2, '.'); 831 if (e1 - f1 != e2 - f2 832 || fnamencmp(f1, f2, e1 - f1) != 0) 833 continue; >>> CID 1616019: (NULL_RETURNS) >>> Dereferencing a pointer that might be "NULL" "(char_u *)e2" when calling "vim_fnamecmp". 834 if (fnamecmp(e1, ".txt") == 0 835 && fnamecmp(e2, fname + 4) == 0) 836 // use .abx instead of .txt 837 VIM_CLEAR(fnames[i1]); 838 } 839 } /src/help.c: 816 in fix_help_buffer() 810 // the same directory. 811 for (i1 = 0; i1 < fcount; ++i1) 812 { 813 f1 = fnames[i1]; 814 t1 = gettail(f1); 815 e1 = vim_strrchr(t1, '.'); >>> CID 1616019: (NULL_RETURNS) >>> Dereferencing a pointer that might be "NULL" "(char_u *)e1" when calling "vim_fnamecmp". 816 if (fnamecmp(e1, ".txt") != 0 817 && fnamecmp(e1, fname + 4) != 0) 818 { 819 // Not .txt and not .abx, remove it. 820 VIM_CLEAR(fnames[i1]); 821 continue; closes: #15420 Signed-off-by: zeertzjq <zeertzjq@outlook.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
1334 lines
33 KiB
C
1334 lines
33 KiB
C
/* 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 = ml_get_buf_len(curbuf, lnum);
|
|
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 (e1 == NULL)
|
|
continue;
|
|
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 (e2 == NULL)
|
|
continue;
|
|
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);
|
|
}
|
|
}
|