Patch 8.2.1262
Problem:    src/ex_cmds.c file is too big.
Solution:   Move help related code to src/help.c. (Yegappan Lakshmanan,
            closes #6506)
Files:      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, src/proto.h,
            src/proto/ex_cmds.pro, src/proto/help.pro


*** ../vim-8.2.1261/Filelist    2020-07-14 23:02:38.176781070 +0200
--- Filelist    2020-07-21 21:01:42.561234898 +0200
***************
*** 68,73 ****
--- 68,74 ----
                src/gui_beval.c \
                src/hardcopy.c \
                src/hashtab.c \
+               src/help.c \
                src/highlight.c \
                src/indent.c \
                src/insexpand.c \
***************
*** 240,245 ****
--- 241,247 ----
                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 \
*** ../vim-8.2.1261/src/Make_cyg_ming.mak       2020-06-28 13:17:07.547811047 
+0200
--- src/Make_cyg_ming.mak       2020-07-21 21:01:42.561234898 +0200
***************
*** 744,749 ****
--- 744,750 ----
        $(OUTDIR)/gui_xim.o \
        $(OUTDIR)/hardcopy.o \
        $(OUTDIR)/hashtab.o \
+       $(OUTDIR)/help.o \
        $(OUTDIR)/highlight.o \
        $(OUTDIR)/if_cscope.o \
        $(OUTDIR)/indent.o \
*** ../vim-8.2.1261/src/Make_morph.mak  2020-06-28 13:17:07.547811047 +0200
--- src/Make_morph.mak  2020-07-21 21:01:42.561234898 +0200
***************
*** 64,69 ****
--- 64,70 ----
        gui_xim.c                                               \
        hardcopy.c                                              \
        hashtab.c                                               \
+       help.c                                                  \
        highlight.c                                             \
        indent.c                                                \
        insexpand.c                                             \
*** ../vim-8.2.1261/src/Make_mvc.mak    2020-06-28 13:17:07.547811047 +0200
--- src/Make_mvc.mak    2020-07-21 21:01:42.561234898 +0200
***************
*** 766,771 ****
--- 766,772 ----
        $(OUTDIR)\gui_xim.obj \
        $(OUTDIR)\hardcopy.obj \
        $(OUTDIR)\hashtab.obj \
+       $(OUTDIR)\help.obj \
        $(OUTDIR)\highlight.obj \
        $(OBJDIR)\if_cscope.obj \
        $(OUTDIR)\indent.obj \
***************
*** 1608,1613 ****
--- 1609,1616 ----
  
  $(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,1935 ****
--- 1933,1939 ----
        proto/gui_xim.pro \
        proto/hardcopy.pro \
        proto/hashtab.pro \
+       proto/help.pro \
        proto/highlight.pro \
        proto/indent.pro \
        proto/insexpand.pro \
*** ../vim-8.2.1261/src/Make_vms.mms    2020-06-28 13:17:07.547811047 +0200
--- src/Make_vms.mms    2020-07-21 21:01:42.561234898 +0200
***************
*** 337,342 ****
--- 337,343 ----
        gui_xim.c \
        hardcopy.c \
        hashtab.c \
+       help.c \
        highlight.c \
        if_cscope.c \
        if_xcmdsrv.c \
***************
*** 450,455 ****
--- 451,457 ----
        gui_xim.obj \
        hardcopy.obj \
        hashtab.obj \
+       help.obj \
        highlight.obj \
        if_cscope.obj \
        if_mzsch.obj \
***************
*** 834,839 ****
--- 836,845 ----
   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 \
*** ../vim-8.2.1261/src/Makefile        2020-07-03 21:17:31.339914463 +0200
--- src/Makefile        2020-07-21 21:01:42.561234898 +0200
***************
*** 404,410 ****
  # 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=dynamic
  #CONF_OPT_LUA = --enable-luainterp --with-luajit
  #CONF_OPT_LUA = --enable-luainterp=dynamic --with-luajit
--- 404,410 ----
  # 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=dynamic
  #CONF_OPT_LUA = --enable-luainterp --with-luajit
  #CONF_OPT_LUA = --enable-luainterp=dynamic --with-luajit
***************
*** 447,456 ****
  # 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 --with-python-command=python2.7
  #CONF_OPT_PYTHON = --enable-pythoninterp=dynamic
! #CONF_OPT_PYTHON3 = --enable-python3interp
  #CONF_OPT_PYTHON3 = --enable-python3interp --with-python3-command=python3.6
  #CONF_OPT_PYTHON3 = --enable-python3interp=dynamic
  
--- 447,456 ----
  # 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 --with-python-command=python2.7
  #CONF_OPT_PYTHON = --enable-pythoninterp=dynamic
! CONF_OPT_PYTHON3 = --enable-python3interp
  #CONF_OPT_PYTHON3 = --enable-python3interp --with-python3-command=python3.6
  #CONF_OPT_PYTHON3 = --enable-python3interp=dynamic
  
***************
*** 472,478 ****
  
  # CSCOPE
  # Uncomment this when you want to include the Cscope interface.
! #CONF_OPT_CSCOPE = --enable-cscope
  
  # NETBEANS - NetBeans interface. Only works with Motif, GTK, and gnome.
  # Motif version must have XPM libraries (see |netbeans-xpm|).
--- 472,478 ----
  
  # CSCOPE
  # Uncomment this when you want to include the Cscope interface.
! 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,546 ****
  #CONF_OPT_FEAT = --with-features=small
  #CONF_OPT_FEAT = --with-features=normal
  #CONF_OPT_FEAT = --with-features=big
! #CONF_OPT_FEAT = --with-features=huge
  
  # COMPILED BY - For including a specific e-mail address for ":version".
  #CONF_OPT_COMPBY = "--with-compiledby=John Doe <[email protected]>"
--- 540,546 ----
  #CONF_OPT_FEAT = --with-features=small
  #CONF_OPT_FEAT = --with-features=normal
  #CONF_OPT_FEAT = --with-features=big
! CONF_OPT_FEAT = --with-features=huge
  
  # COMPILED BY - For including a specific e-mail address for ":version".
  #CONF_OPT_COMPBY = "--with-compiledby=John Doe <[email protected]>"
***************
*** 614,620 ****
  # 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
  # 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
--- 614,620 ----
  # 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
  # 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,715 ****
  # 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_LIBS = -lccmalloc
  
  # Uncomment this line to have Vim call abort() when an internal error is
--- 709,715 ----
  # 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_LIBS = -lccmalloc
  
  # Uncomment this line to have Vim call abort() when an internal error is
***************
*** 1639,1644 ****
--- 1639,1645 ----
        gui_xim.c \
        hardcopy.c \
        hashtab.c \
+       help.c \
        highlight.c \
        if_cscope.c \
        if_xcmdsrv.c \
***************
*** 1790,1795 ****
--- 1791,1797 ----
        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,1963 ****
--- 1960,1966 ----
        gui_beval.pro \
        hardcopy.pro \
        hashtab.pro \
+       help.pro \
        highlight.pro \
        if_cscope.pro \
        if_lua.pro \
***************
*** 3264,3269 ****
--- 3267,3275 ----
  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,3935 ****
--- 3936,3945 ----
   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 \
*** ../vim-8.2.1261/src/README.md       2020-06-28 13:17:07.547811047 +0200
--- src/README.md       2020-07-21 21:01:42.561234898 +0200
***************
*** 48,53 ****
--- 48,54 ----
  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
*** ../vim-8.2.1261/src/cmdexpand.c     2020-07-03 18:15:02.830048103 +0200
--- src/cmdexpand.c     2020-07-21 21:01:42.561234898 +0200
***************
*** 1887,1948 ****
      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.
--- 1887,1892 ----
*** ../vim-8.2.1261/src/ex_cmds.c       2020-06-12 22:59:07.262097216 +0200
--- src/ex_cmds.c       2020-07-21 21:01:42.565234890 +0200
***************
*** 23,30 ****
  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".
--- 23,28 ----
***************
*** 5035,6312 ****
  
  #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.
   */
--- 5033,5038 ----
*** ../vim-8.2.1261/src/help.c  2020-07-21 21:06:55.524509388 +0200
--- src/help.c  2020-07-21 21:01:42.565234890 +0200
***************
*** 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);
+     }
+ }
*** ../vim-8.2.1261/src/proto.h 2020-06-28 13:17:07.551811006 +0200
--- src/proto.h 2020-07-21 21:01:42.565234890 +0200
***************
*** 95,100 ****
--- 95,101 ----
  # include "gui_xim.pro"
  # include "hardcopy.pro"
  # include "hashtab.pro"
+ # include "help.pro"
  # include "highlight.pro"
  # include "indent.pro"
  # include "insexpand.pro"
*** ../vim-8.2.1261/src/proto/ex_cmds.pro       2020-02-14 13:21:55.646197062 
+0100
--- src/proto/ex_cmds.pro       2020-07-21 21:01:42.565234890 +0200
***************
*** 35,49 ****
  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);
--- 35,40 ----
*** ../vim-8.2.1261/src/proto/help.pro  2020-07-21 21:06:55.536509360 +0200
--- src/proto/help.pro  2020-07-21 21:01:42.565234890 +0200
***************
*** 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 : */
+ 
*** ../vim-8.2.1261/src/version.c       2020-07-21 20:55:46.873920166 +0200
--- src/version.c       2020-07-21 21:03:21.513014522 +0200
***************
*** 756,757 ****
--- 756,759 ----
  {   /* Add new patch number below this line */
+ /**/
+     1262,
  /**/

-- 
"I can't complain, but sometimes I still do."   (Joe Walsh)

 /// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net   \\\
///        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\
\\\  an exciting new programming language -- http://www.Zimbu.org        ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/202007211907.06LJ7tTS814219%40masaka.moolenaar.net.

Raspunde prin e-mail lui