]> granicus.if.org Git - vim/commitdiff
patch 8.2.1262: src/ex_cmds.c file is too big v8.2.1262
authorBram Moolenaar <Bram@vim.org>
Tue, 21 Jul 2020 19:07:20 +0000 (21:07 +0200)
committerBram Moolenaar <Bram@vim.org>
Tue, 21 Jul 2020 19:07:20 +0000 (21:07 +0200)
Problem:    src/ex_cmds.c file is too big.
Solution:   Move help related code to src/help.c. (Yegappan Lakshmanan,
            closes #6506)

14 files changed:
Filelist
src/Make_cyg_ming.mak
src/Make_morph.mak
src/Make_mvc.mak
src/Make_vms.mms
src/Makefile
src/README.md
src/cmdexpand.c
src/ex_cmds.c
src/help.c [new file with mode: 0644]
src/proto.h
src/proto/ex_cmds.pro
src/proto/help.pro [new file with mode: 0644]
src/version.c

index 49b65341f33c8df9434eb674329dc0bea07fe6f8..77fabbebffe9e3965ea69696fadbc3f5becb2559 100644 (file)
--- a/Filelist
+++ b/Filelist
@@ -68,6 +68,7 @@ SRC_ALL =     \
                src/gui_beval.c \
                src/hardcopy.c \
                src/hashtab.c \
+               src/help.c \
                src/highlight.c \
                src/indent.c \
                src/insexpand.c \
@@ -240,6 +241,7 @@ SRC_ALL =   \
                src/proto/gui_beval.pro \
                src/proto/hardcopy.pro \
                src/proto/hashtab.pro \
+               src/proto/help.pro \
                src/proto/highlight.pro \
                src/proto/indent.pro \
                src/proto/insexpand.pro \
index 157dc94956e5f935855294a8e57e3764852e1e5d..3e8bb04e0fb3d995b83a6b7e63e2c59ad4841c09 100644 (file)
@@ -744,6 +744,7 @@ OBJ = \
        $(OUTDIR)/gui_xim.o \
        $(OUTDIR)/hardcopy.o \
        $(OUTDIR)/hashtab.o \
+       $(OUTDIR)/help.o \
        $(OUTDIR)/highlight.o \
        $(OUTDIR)/if_cscope.o \
        $(OUTDIR)/indent.o \
index bd95115a39c55b70b675b80477c09f2c957394e3..6fc50ed1fd16eb796c941df69cb1b470617df362 100644 (file)
@@ -64,6 +64,7 @@ SRC = arabic.c                                                \
        gui_xim.c                                               \
        hardcopy.c                                              \
        hashtab.c                                               \
+       help.c                                                  \
        highlight.c                                             \
        indent.c                                                \
        insexpand.c                                             \
index b1bb8836e0363819c782209245c9213d21ddeb89..65d530f359c023ff5f185cac0a7f5e1603ee8d46 100644 (file)
@@ -766,6 +766,7 @@ OBJ = \
        $(OUTDIR)\gui_xim.obj \
        $(OUTDIR)\hardcopy.obj \
        $(OUTDIR)\hashtab.obj \
+       $(OUTDIR)\help.obj \
        $(OUTDIR)\highlight.obj \
        $(OBJDIR)\if_cscope.obj \
        $(OUTDIR)\indent.obj \
@@ -1608,6 +1609,8 @@ $(OUTDIR)/hardcopy.obj:   $(OUTDIR) hardcopy.c  $(INCL) version.h
 
 $(OUTDIR)/hashtab.obj: $(OUTDIR) hashtab.c  $(INCL)
 
+$(OUTDIR)/help.obj:    $(OUTDIR) help.c  $(INCL)
+
 $(OUTDIR)/highlight.obj:       $(OUTDIR) highlight.c  $(INCL)
 
 $(OUTDIR)/indent.obj:  $(OUTDIR) indent.c  $(INCL)
@@ -1930,6 +1933,7 @@ proto.h: \
        proto/gui_xim.pro \
        proto/hardcopy.pro \
        proto/hashtab.pro \
+       proto/help.pro \
        proto/highlight.pro \
        proto/indent.pro \
        proto/insexpand.pro \
index 78f9dab5db31ece0b4d6941fa1cba48527e33fa0..0d80c0617b598dfbce1dad42ce2e9d7f282cb238 100644 (file)
@@ -337,6 +337,7 @@ SRC = \
        gui_xim.c \
        hardcopy.c \
        hashtab.c \
+       help.c \
        highlight.c \
        if_cscope.c \
        if_xcmdsrv.c \
@@ -450,6 +451,7 @@ OBJ = \
        gui_xim.obj \
        hardcopy.obj \
        hashtab.obj \
+       help.obj \
        highlight.obj \
        if_cscope.obj \
        if_mzsch.obj \
@@ -834,6 +836,10 @@ hashtab.obj : hashtab.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
+help.obj : help.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
 highlight.obj : highlight.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 \
index 2b10024401b85aee1e2554dcd5be39385761e00c..36635bdbf5e0d7f150daf256b19574e081e46821 100644 (file)
@@ -404,7 +404,7 @@ CClink = $(CC)
 # Use --with-luajit if you want to use LuaJIT instead of Lua.
 # Set PATH environment variable to find lua or luajit executable.
 # This requires at least "normal" features, "tiny" and "small" don't work.
-#CONF_OPT_LUA = --enable-luainterp
+CONF_OPT_LUA = --enable-luainterp
 #CONF_OPT_LUA = --enable-luainterp=dynamic
 #CONF_OPT_LUA = --enable-luainterp --with-luajit
 #CONF_OPT_LUA = --enable-luainterp=dynamic --with-luajit
@@ -447,10 +447,10 @@ CClink = $(CC)
 # dlopen(), dlsym(), dlclose(), i.e. pythonX.Y.so must be available
 # However, this may still cause problems, such as "import termios" failing.
 # Build two separate versions of Vim in that case.
-#CONF_OPT_PYTHON = --enable-pythoninterp
+CONF_OPT_PYTHON = --enable-pythoninterp
 #CONF_OPT_PYTHON = --enable-pythoninterp --with-python-command=python2.7
 #CONF_OPT_PYTHON = --enable-pythoninterp=dynamic
-#CONF_OPT_PYTHON3 = --enable-python3interp
+CONF_OPT_PYTHON3 = --enable-python3interp
 #CONF_OPT_PYTHON3 = --enable-python3interp --with-python3-command=python3.6
 #CONF_OPT_PYTHON3 = --enable-python3interp=dynamic
 
@@ -472,7 +472,7 @@ CClink = $(CC)
 
 # CSCOPE
 # Uncomment this when you want to include the Cscope interface.
-#CONF_OPT_CSCOPE = --enable-cscope
+CONF_OPT_CSCOPE = --enable-cscope
 
 # NETBEANS - NetBeans interface. Only works with Motif, GTK, and gnome.
 # Motif version must have XPM libraries (see |netbeans-xpm|).
@@ -540,7 +540,7 @@ CClink = $(CC)
 #CONF_OPT_FEAT = --with-features=small
 #CONF_OPT_FEAT = --with-features=normal
 #CONF_OPT_FEAT = --with-features=big
-#CONF_OPT_FEAT = --with-features=huge
+CONF_OPT_FEAT = --with-features=huge
 
 # COMPILED BY - For including a specific e-mail address for ":version".
 #CONF_OPT_COMPBY = "--with-compiledby=John Doe <JohnDoe@yahoo.com>"
@@ -614,7 +614,7 @@ CClink = $(CC)
 # Use this with GCC to check for mistakes, unused arguments, etc.
 # Note: If you use -Wextra and get warnings in GTK code about function
 #       parameters, you can add -Wno-cast-function-type
-#CFLAGS = -g -Wall -Wextra -Wshadow -Wmissing-prototypes -Wunreachable-code -Wno-cast-function-type -Wno-deprecated-declarations -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1
+CFLAGS = -g -Wall -Wextra -Wshadow -Wmissing-prototypes -Wunreachable-code -Wno-cast-function-type -Wno-deprecated-declarations -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1
 # Add -Wpedantic to find // comments and other C99 constructs.
 # Better disable Perl and Python to avoid a lot of warnings.
 #CFLAGS = -g -Wall -Wextra -Wshadow -Wmissing-prototypes -Wpedantic -Wunreachable-code -Wunused-result -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1
@@ -709,7 +709,7 @@ SANITIZER_LIBS = $(SANITIZER_CFLAGS)
 # Configuration is in the .ccmalloc or ~/.ccmalloc file.
 # Doesn't work very well, since memory linked to from global variables
 # (in libraries) is also marked as leaked memory.
-#LEAK_CFLAGS = -DEXITFREE
+LEAK_CFLAGS = -DEXITFREE
 #LEAK_LIBS = -lccmalloc
 
 # Uncomment this line to have Vim call abort() when an internal error is
@@ -1639,6 +1639,7 @@ BASIC_SRC = \
        gui_xim.c \
        hardcopy.c \
        hashtab.c \
+       help.c \
        highlight.c \
        if_cscope.c \
        if_xcmdsrv.c \
@@ -1790,6 +1791,7 @@ OBJ_COMMON = \
        objects/gui_xim.o \
        objects/hardcopy.o \
        objects/hashtab.o \
+       objects/help.o \
        objects/highlight.o \
        objects/if_cscope.o \
        objects/if_xcmdsrv.o \
@@ -1958,6 +1960,7 @@ PRO_AUTO = \
        gui_beval.pro \
        hardcopy.pro \
        hashtab.pro \
+       help.pro \
        highlight.pro \
        if_cscope.pro \
        if_lua.pro \
@@ -3264,6 +3267,9 @@ objects/hardcopy.o: hardcopy.c
 objects/hashtab.o: hashtab.c
        $(CCC) -o $@ hashtab.c
 
+objects/help.o: help.c
+       $(CCC) -o $@ help.c
+
 objects/gui.o: gui.c
        $(CCC) -o $@ gui.c
 
@@ -3930,6 +3936,10 @@ objects/hashtab.o: hashtab.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/help.o: help.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/highlight.o: highlight.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 \
index a196f2b9547a53a775275e82a97a0e044b7b6daa..f499c4cdb21d273f830a059a1cc52478973493cb 100644 (file)
@@ -48,6 +48,7 @@ filepath.c    | dealing with file names and paths
 findfile.c     | search for files in 'path'
 fold.c         | folding
 getchar.c      | getting characters and key mapping
+help.c         | vim help related functions
 highlight.c    | syntax highlighting
 indent.c       | text indentation
 insexpand.c    | Insert mode completion
index d92366c61e75b5d07e6132cf83cd2195a05e760b..a10fff8b3d0df2b3a46f43358dfe29d72d8ec391 100644 (file)
@@ -1887,62 +1887,6 @@ expand_cmdline(
     return EXPAND_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.
- */
-    static 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
-
 /*
  * Function given to ExpandGeneric() to obtain the possible arguments of the
  * ":behave {mswin,xterm}" command.
index 11894f638759e877fc4a91276d19d1d89053e880..59c562b91752dd8e4b897368513442faddf591e6 100644 (file)
@@ -23,8 +23,6 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char_u *cmd,
 static int not_writing(void);
 static int check_readonly(int *forceit, buf_T *buf);
 static void delbuf_msg(char_u *name);
-static int help_compare(const void *s1, const void *s2);
-static void prepare_help_buffer(void);
 
 /*
  * ":ascii" and "ga".
@@ -5035,1278 +5033,6 @@ prepare_tagpreview(
 
 #endif
 
-
-/*
- * ":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 (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(_("E478: Don't 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(_("E661: Sorry, no '%s' help for %s"), lang, arg);
-       else
-#endif
-           semsg(_("E149: Sorry, no help for %s"), 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.tab != 0)
-    {
-       if (cmdmod.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.split == 0 && curwin->w_width != Columns
-                                                 && curwin->w_width < 80)
-               n |= 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.keepalt)
-               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.keepalt)
-       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;
-    static char *(mtable[]) = {"*", "g*", "[*", "]*", ":*",
-                              "/*", "/\\*", "\"*", "**",
-                              "cpo-*", "/\\(\\)", "/\\%(\\)",
-                              "?", ":?", "?<CR>", "g?", "g?g?", "g??",
-                              "-?", "q?", "v_g?",
-                              "/\\?", "/\\z(\\)", "\\=", ":s\\=",
-                              "[count]", "[quotex]",
-                              "[range]", ":[range]",
-                              "[pattern]", "\\|", "\\%$",
-                              "s/\\~", "s/\\U", "s/\\L",
-                              "s/\\1", "s/\\2", "s/\\3", "s/\\9"};
-    static char *(rtable[]) = {"star", "gstar", "[star", "]star", ":star",
-                              "/star", "/\\\\star", "quotestar", "starstar",
-                              "cpo-star", "/\\\\(\\\\)", "/\\\\%(\\\\)",
-                              "?", ":?", "?<CR>", "g?", "g?g?", "g??",
-                              "-?", "q?", "v_g?",
-                              "/\\\\?", "/\\\\z(\\\\)", "\\\\=", ":s\\\\=",
-                              "\\[count]", "\\[quotex]",
-                              "\\[range]", ":\\[range]",
-                              "\\[pattern]", "\\\\bar", "/\\\\%\\$",
-                              "s/\\\\\\~", "s/\\\\U", "s/\\\\L",
-                              "s/\\\\1", "s/\\\\2", "s/\\\\3", "s/\\\\9"};
-    static char *(expr_table[]) = {"!=?", "!~?", "<=?", "<?", "==?", "=~?",
-                               ">=?", ">?", "is?", "isnot?"};
-    int flags;
-
-    d = IObuff;                    // assume IObuff is long enough!
-
-    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)(sizeof(expr_table) / sizeof(char *)); --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
-       // '*' with "star".  Otherwise '*' is recognized as a wildcard.
-       for (i = (int)(sizeof(mtable) / sizeof(char *)); --i >= 0; )
-           if (STRCMP(arg, mtable[i]) == 0)
-           {
-               STRCPY(d, rtable[i]);
-               break;
-           }
-    }
-
-    if (i < 0) // 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)))
-       {
-           STRCPY(d, "/\\\\");
-           STRCPY(d + 3, 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 < ' ')
-               {
-#ifdef EBCDIC
-                   *d++ = CtrlChar(*s);
-#else
-                   *d++ = *s + '@';
-#endif
-                   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;
-}
-
-/*
- * Called when starting to edit a buffer for a help file.
- */
-    static 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.
-     * 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 =
-#ifdef EBCDIC
-           (char_u *)"65-255,^*,^|,^\"";
-#else
-           (char_u *)"!-~,^*,^|,^\",192-255";
-#endif
-    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((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)
-                       {
-                           for (i2 = 0; i2 < fcount; ++i2)
-                           {
-                               if (i1 == i2)
-                                   continue;
-                               if (fnames[i1] == NULL || fnames[i2] == NULL)
-                                   continue;
-                               f1 = fnames[i1];
-                               f2 = fnames[i2];
-                               t1 = gettail(f1);
-                               t2 = gettail(f2);
-                               e1 = vim_strrchr(t1, '.');
-                               e2 = vim_strrchr(t2, '.');
-                               if (e1 == NULL || e2 == NULL)
-                                   continue;
-                               if (fnamecmp(e1, ".txt") != 0
-                                   && fnamecmp(e1, fname + 4) != 0)
-                               {
-                                   // Not .txt and not .abx, remove it.
-                                   VIM_CLEAR(fnames[i1]);
-                                   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                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                mix = FALSE;    // detected mixed encodings
-
-    /*
-     * Find all *.txt files.
-     */
-    dirlen = (int)STRLEN(dir);
-    STRCPY(NameBuff, dir);
-    STRCAT(NameBuff, "/**/*");
-    STRCAT(NameBuff, ext);
-    if (gen_expand_wildcards(1, &NameBuff, &filecount, &files,
-                                                   EW_FILE|EW_SILENT) == FAIL
-           || filecount == 0)
-    {
-       if (!got_int)
-           semsg(_("E151: No match: %s"), NameBuff);
-       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(_("E152: Cannot open %s for writing"), NameBuff);
-       FreeWild(filecount, files);
-       return;
-    }
-
-    /*
-     * If using the "++t" argument or generating tags for "$VIMRUNTIME/doc"
-     * add the "help-tags" tag.
-     */
-    ga_init2(&ga, (int)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(_("E153: Unable to open %s for reading"), files[fi]);
-           continue;
-       }
-       fname = files[fi] + dirlen + 1;
-
-       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(_("E670: Mix of help file encodings within a language: %s"), files[fi]);
-                   mix = !got_int;
-                   got_int = TRUE;
-               }
-               firstline = 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;
-           }
-           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,
-                           _("E154: Duplicate tag \"%s\" in file %s/%s"),
-                                    ((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(_("E151: No match: %s"), 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)
-       {
-           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(_("E150: Not a directory: %s"), eap->arg);
-       else
-           do_helptags(dirname, add_help_tags, FALSE);
-       vim_free(dirname);
-    }
-}
-
 /*
  * Make the user happy.
  */
diff --git a/src/help.c b/src/help.c
new file mode 100644 (file)
index 0000000..4f2b6b6
--- /dev/null
@@ -0,0 +1,1295 @@
+/* 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 (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(_("E478: Don't 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(_("E661: Sorry, no '%s' help for %s"), lang, arg);
+       else
+#endif
+           semsg(_("E149: Sorry, no help for %s"), 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.tab != 0)
+    {
+       if (cmdmod.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.split == 0 && curwin->w_width != Columns
+                                                 && curwin->w_width < 80)
+               n |= 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.keepalt)
+               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.keepalt)
+       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;
+    static char *(mtable[]) = {"*", "g*", "[*", "]*", ":*",
+                              "/*", "/\\*", "\"*", "**",
+                              "cpo-*", "/\\(\\)", "/\\%(\\)",
+                              "?", ":?", "?<CR>", "g?", "g?g?", "g??",
+                              "-?", "q?", "v_g?",
+                              "/\\?", "/\\z(\\)", "\\=", ":s\\=",
+                              "[count]", "[quotex]",
+                              "[range]", ":[range]",
+                              "[pattern]", "\\|", "\\%$",
+                              "s/\\~", "s/\\U", "s/\\L",
+                              "s/\\1", "s/\\2", "s/\\3", "s/\\9"};
+    static char *(rtable[]) = {"star", "gstar", "[star", "]star", ":star",
+                              "/star", "/\\\\star", "quotestar", "starstar",
+                              "cpo-star", "/\\\\(\\\\)", "/\\\\%(\\\\)",
+                              "?", ":?", "?<CR>", "g?", "g?g?", "g??",
+                              "-?", "q?", "v_g?",
+                              "/\\\\?", "/\\\\z(\\\\)", "\\\\=", ":s\\\\=",
+                              "\\[count]", "\\[quotex]",
+                              "\\[range]", ":\\[range]",
+                              "\\[pattern]", "\\\\bar", "/\\\\%\\$",
+                              "s/\\\\\\~", "s/\\\\U", "s/\\\\L",
+                              "s/\\\\1", "s/\\\\2", "s/\\\\3", "s/\\\\9"};
+    static char *(expr_table[]) = {"!=?", "!~?", "<=?", "<?", "==?", "=~?",
+                               ">=?", ">?", "is?", "isnot?"};
+    int flags;
+
+    d = IObuff;                    // assume IObuff is long enough!
+
+    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)(sizeof(expr_table) / sizeof(char *)); --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
+       // '*' with "star".  Otherwise '*' is recognized as a wildcard.
+       for (i = (int)(sizeof(mtable) / sizeof(char *)); --i >= 0; )
+           if (STRCMP(arg, mtable[i]) == 0)
+           {
+               STRCPY(d, rtable[i]);
+               break;
+           }
+    }
+
+    if (i < 0) // 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)))
+       {
+           STRCPY(d, "/\\\\");
+           STRCPY(d + 3, 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 < ' ')
+               {
+#ifdef EBCDIC
+                   *d++ = CtrlChar(*s);
+#else
+                   *d++ = *s + '@';
+#endif
+                   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.
+    // 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 =
+#ifdef EBCDIC
+           (char_u *)"65-255,^*,^|,^\"";
+#else
+           (char_u *)"!-~,^*,^|,^\",192-255";
+#endif
+    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((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)
+                       {
+                           for (i2 = 0; i2 < fcount; ++i2)
+                           {
+                               if (i1 == i2)
+                                   continue;
+                               if (fnames[i1] == NULL || fnames[i2] == NULL)
+                                   continue;
+                               f1 = fnames[i1];
+                               f2 = fnames[i2];
+                               t1 = gettail(f1);
+                               t2 = gettail(f2);
+                               e1 = vim_strrchr(t1, '.');
+                               e2 = vim_strrchr(t2, '.');
+                               if (e1 == NULL || e2 == NULL)
+                                   continue;
+                               if (fnamecmp(e1, ".txt") != 0
+                                   && fnamecmp(e1, fname + 4) != 0)
+                               {
+                                   // Not .txt and not .abx, remove it.
+                                   VIM_CLEAR(fnames[i1]);
+                                   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                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                mix = FALSE;    // detected mixed encodings
+
+    // Find all *.txt files.
+    dirlen = (int)STRLEN(dir);
+    STRCPY(NameBuff, dir);
+    STRCAT(NameBuff, "/**/*");
+    STRCAT(NameBuff, ext);
+    if (gen_expand_wildcards(1, &NameBuff, &filecount, &files,
+                                                   EW_FILE|EW_SILENT) == FAIL
+           || filecount == 0)
+    {
+       if (!got_int)
+           semsg(_("E151: No match: %s"), NameBuff);
+       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(_("E152: Cannot open %s for writing"), NameBuff);
+       FreeWild(filecount, files);
+       return;
+    }
+
+    // If using the "++t" argument or generating tags for "$VIMRUNTIME/doc"
+    // add the "help-tags" tag.
+    ga_init2(&ga, (int)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(_("E153: Unable to open %s for reading"), files[fi]);
+           continue;
+       }
+       fname = files[fi] + dirlen + 1;
+
+       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(_("E670: Mix of help file encodings within a language: %s"), files[fi]);
+                   mix = !got_int;
+                   got_int = TRUE;
+               }
+               firstline = 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;
+           }
+           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,
+                           _("E154: Duplicate tag \"%s\" in file %s/%s"),
+                                    ((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(_("E151: No match: %s"), 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)
+       {
+           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(_("E150: Not a directory: %s"), eap->arg);
+       else
+           do_helptags(dirname, add_help_tags, FALSE);
+       vim_free(dirname);
+    }
+}
index 8c768fc00eb7bac3c388b1684afb67ebe7e6bcb9..e3a25f9882fcf90bdfb4a8e31c18f5bf0d82f208 100644 (file)
@@ -95,6 +95,7 @@ extern int _stricoll(char *a, char *b);
 # include "gui_xim.pro"
 # include "hardcopy.pro"
 # include "hashtab.pro"
+# include "help.pro"
 # include "highlight.pro"
 # include "indent.pro"
 # include "insexpand.pro"
index 92887a6cf7f7741fa1e099c732f0a7c48efdd588..a9e0658a9ba795f4eb216cda39953850a6089617 100644 (file)
@@ -35,15 +35,6 @@ char_u *get_old_sub(void);
 void set_old_sub(char_u *val);
 void free_old_sub(void);
 int prepare_tagpreview(int undo_sync, int use_previewpopup, use_popup_T use_popup);
-void ex_help(exarg_T *eap);
-void ex_helpclose(exarg_T *eap);
-char_u *check_help_lang(char_u *arg);
-int help_heuristic(char_u *matched_string, int offset, int wrong_case);
-int find_help_tags(char_u *arg, int *num_matches, char_u ***matches, int keep_lang);
-void fix_help_buffer(void);
-void ex_exusage(exarg_T *eap);
-void ex_viusage(exarg_T *eap);
-void ex_helptags(exarg_T *eap);
 void ex_smile(exarg_T *eap);
 void ex_drop(exarg_T *eap);
 char_u *skip_vimgrep_pat(char_u *p, char_u **s, int *flags);
diff --git a/src/proto/help.pro b/src/proto/help.pro
new file mode 100644 (file)
index 0000000..96f4efe
--- /dev/null
@@ -0,0 +1,14 @@
+/* help.c */
+void ex_help(exarg_T *eap);
+void ex_helpclose(exarg_T *eap);
+char_u *check_help_lang(char_u *arg);
+int help_heuristic(char_u *matched_string, int offset, int wrong_case);
+int find_help_tags(char_u *arg, int *num_matches, char_u ***matches, int keep_lang);
+void cleanup_help_tags(int num_file, char_u **file);
+void prepare_help_buffer(void);
+void fix_help_buffer(void);
+void ex_exusage(exarg_T *eap);
+void ex_viusage(exarg_T *eap);
+void ex_helptags(exarg_T *eap);
+/* vim: set ft=c : */
+
index a779d54bda0a28de603574a44f9f6344f38fa04b..f34bb8dbc09530dce38e35d15dd1027ebd1a2f5f 100644 (file)
@@ -754,6 +754,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1262,
 /**/
     1261,
 /**/