diff options
Diffstat (limited to 'usr.bin/vim/help.c')
-rw-r--r-- | usr.bin/vim/help.c | 302 |
1 files changed, 302 insertions, 0 deletions
diff --git a/usr.bin/vim/help.c b/usr.bin/vim/help.c new file mode 100644 index 00000000000..b3d3a8cd4de --- /dev/null +++ b/usr.bin/vim/help.c @@ -0,0 +1,302 @@ +/* $OpenBSD: help.c,v 1.1 1996/09/07 21:40:26 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * 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. + */ + +/* + * help.c: open a read-only window on the vim_help.txt file + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" + + void +do_help(arg) + char_u *arg; +{ + char_u *fnamep; + FILE *helpfd; /* file descriptor of help file */ + int n; + WIN *wp; + int num_matches; + char_u **matches; + int need_free = FALSE; + + /* + * If an argument is given, check if there is a match for it. + */ + if (*arg != NUL) + { + n = find_help_tags(arg, &num_matches, &matches); + if (num_matches == 0 || n == FAIL) + { + EMSG2("Sorry, no help for %s", arg); + return; + } + + /* The first file is the best match */ + arg = strsave(matches[0]); + need_free = TRUE; + FreeWild(num_matches, matches); + } + + /* + * If there is already a help window open, use that one. + */ + if (!curwin->w_buffer->b_help) + { + for (wp = firstwin; wp != NULL; wp = wp->w_next) + if (wp->w_buffer != NULL && wp->w_buffer->b_help) + break; + if (wp != NULL && wp->w_buffer->b_nwindows > 0) + win_enter(wp, TRUE); + else + { + /* + * There is no help buffer yet. + * Try to open the file specified by the "helpfile" option. + */ + fnamep = p_hf; + if ((helpfd = fopen((char *)p_hf, READBIN)) == NULL) + { +#if defined(MSDOS) + /* + * for MSDOS: try the DOS search path + */ + fnamep = searchpath("vim_help.txt"); + if (fnamep == NULL || + (helpfd = fopen((char *)fnamep, READBIN)) == NULL) + { + smsg((char_u *)"Sorry, help file \"%s\" and \"vim_help.txt\" not found", p_hf); + goto erret; + } +#else + smsg((char_u *)"Sorry, help file \"%s\" not found", p_hf); + goto erret; +#endif + } + fclose(helpfd); + + if (win_split(0, FALSE) == FAIL) + goto erret; + + if (curwin->w_height < p_hh) + win_setheight((int)p_hh); + +#ifdef RIGHTLEFT + curwin->w_p_rl = 0; /* help window is left-to-right */ +#endif + curwin->w_p_nu = 0; /* no line numbers */ + + /* + * open help file (do_ecmd() will set b_help flag, readfile() will + * set b_p_ro flag) + */ + (void)do_ecmd(0, fnamep, NULL, NULL, TRUE, (linenr_t)0, TRUE); + + /* save the values of the options we change */ + vim_free(help_save_isk); + help_save_isk = strsave(curbuf->b_p_isk); + help_save_ts = curbuf->b_p_ts; + + /* accept all chars for keywords, except ' ', '*', '"', '|' */ + set_string_option((char_u *)"isk", -1, + (char_u *)"!-~,^*,^|,^\"", TRUE); + curbuf->b_p_ts = 8; + check_buf_options(curbuf); + (void)init_chartab(); /* needed because 'isk' changed */ + } + } + + restart_edit = 0; /* don't want insert mode in help file */ + + stuffReadbuff((char_u *)":ta "); + if (arg != NULL && *arg != NUL) + stuffReadbuff(arg); + else + stuffReadbuff((char_u *)"vim_help.txt"); /* go to the index */ + stuffcharReadbuff('\n'); + +erret: + if (need_free) + vim_free(arg); +} + +/* + * 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 alpha-numeric characters is better. + * - Match with least total characters is better. + * - Match towards the start is better. + * 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(matched_string, offset) + char_u *matched_string; + int offset; /* offset for match */ +{ + int num_letters; + char_u *p; + + num_letters = 0; + for (p = matched_string; *p; p++) + if (isalnum(*p)) + num_letters++; + + /* + * Multiply the number of letters by 100 to give it a much bigger + * weighting than the number of characters. + * 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 (isalnum(matched_string[offset]) && offset > 0 && + isalnum(matched_string[offset - 1])) + offset += 10000; + else if (offset > 2) + offset *= 200; + return (int)(100 * num_letters + STRLEN(matched_string) + offset); +} + +static int help_compare __ARGS((const void *s1, const void *s2)); + +/* + * 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(s1, s2) + const void *s1; + const void *s2; +{ + char *p1; + char *p2; + + p1 = *(char **)s1 + strlen(*(char **)s1) + 1; + p2 = *(char **)s2 + strlen(*(char **)s2) + 1; + return strcmp(p1, p2); +} + +/* + * Find all help tags matching "arg", sort them and return in matches[], with + * the number of matches in num_matches. + * We try first with case, and then ignoring case. Then we try to choose the + * "best" match from the ones found. + */ + int +find_help_tags(arg, num_matches, matches) + char_u *arg; + int *num_matches; + char_u ***matches; +{ + char_u *s, *d; + regexp *prog; + int attempt; + int retval = FAIL; + + reg_magic = p_magic; + d = IObuff; /* assume IObuff is long enough! */ + + /* + * Replace "|" with "bar", """ with "quote" and "*" with "star" 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. + * Replace "^x" by "CTRL-X". Don't do this for "^_" to make + * ":help i_^_CTRL-D" work. + * If tag starts with ', toss everything after a second '. Fixes + * CTRL-] on 'option'. (would include the trailing '.'). + */ + if (STRCMP(arg, "*") == 0 || STRCMP(arg, "[*") == 0 || + STRCMP(arg, "]*") == 0) + { + if (*arg != '*') + *d++ = *arg; + STRCPY(d, "star"); + d += 4; + } + else + { + for (s = arg; *s; ++s) + { + 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; + /* "?", ":?" and "?<CR>" are real tags */ + case '?': if (arg[1] == NUL || + STRCMP(arg, ":?") == 0 || + STRCMP(arg, "?<CR>") == 0) + break; + *d++ = '.'; + continue; + case '$': + case '.': + case '~': *d++ = '\\'; + break; + } + if (*s < ' ' || (*s == '^' && s[1] && s[1] != '_')) /* ^x */ + { + STRCPY(d, "CTRL-"); + d += 5; + if (*s < ' ') + { + *d++ = *s + '@'; + continue; + } + ++s; + } + else if (*s == '^') /* "^" or "CTRL-^" or "^_" */ + *d++ = '\\'; + *d++ = *s; + if (*s == '\'' && s > arg && *arg == '\'') + break; + } + } + *d = NUL; + + reg_ic = FALSE; + prog = vim_regcomp(IObuff); + if (prog == NULL) + return FAIL; + + /* First try to match with case, then without */ + for (attempt = 0; attempt < 2; ++attempt, reg_ic = TRUE) + { + *matches = (char_u **)""; + *num_matches = 0; + retval = find_tags(NULL, prog, num_matches, matches, TRUE); + if (retval == FAIL || *num_matches) + break; + } + vim_free(prog); + +#ifdef HAVE_QSORT + /* + * Sort the matches found on the heuristic number that is after the + * tag name. If there is no qsort, the output will be messy! + */ + qsort((void *)*matches, (size_t)*num_matches, + sizeof(char_u *), help_compare); +#endif + return OK; +} |