/* $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 "?" are real tags */ case '?': if (arg[1] == NUL || STRCMP(arg, ":?") == 0 || STRCMP(arg, "?") == 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; }