To: vim_dev@googlegroups.com Subject: Patch 8.2.1262 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ 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 " --- 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 " *************** *** 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-*", "/\\(\\)", "/\\%(\\)", - "?", ":?", "?", "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", "/\\\\(\\\\)", "/\\\\%(\\\\)", - "?", ":?", "?", "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 /. - 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-*", "/\\(\\)", "/\\%(\\)", + "?", ":?", "?", "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", "/\\\\(\\\\)", "/\\\\%(\\\\)", + "?", ":?", "?", "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 /. + 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 -- Bram@Moolenaar.net -- 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 ///