Annotation of src/usr.bin/vim/help.c, Revision 1.2
1.2 ! downsj 1: /* $OpenBSD: help.c,v 1.1.1.1 1996/09/07 21:40:26 downsj Exp $ */
1.1 downsj 2: /* vi:set ts=4 sw=4:
3: *
4: * VIM - Vi IMproved by Bram Moolenaar
5: *
6: * Do ":help uganda" in Vim to read copying and usage conditions.
7: * Do ":help credits" in Vim to see a list of people who contributed.
8: */
9:
10: /*
11: * help.c: open a read-only window on the vim_help.txt file
12: */
13:
14: #include "vim.h"
15: #include "globals.h"
16: #include "proto.h"
17: #include "option.h"
18:
19: void
20: do_help(arg)
21: char_u *arg;
22: {
23: char_u *fnamep;
24: FILE *helpfd; /* file descriptor of help file */
25: int n;
26: WIN *wp;
27: int num_matches;
28: char_u **matches;
29: int need_free = FALSE;
30:
31: /*
32: * If an argument is given, check if there is a match for it.
33: */
34: if (*arg != NUL)
35: {
36: n = find_help_tags(arg, &num_matches, &matches);
37: if (num_matches == 0 || n == FAIL)
38: {
39: EMSG2("Sorry, no help for %s", arg);
40: return;
41: }
42:
1.2 ! downsj 43: /* The first match is the best match */
1.1 downsj 44: arg = strsave(matches[0]);
45: need_free = TRUE;
46: FreeWild(num_matches, matches);
47: }
48:
49: /*
50: * If there is already a help window open, use that one.
51: */
52: if (!curwin->w_buffer->b_help)
53: {
54: for (wp = firstwin; wp != NULL; wp = wp->w_next)
55: if (wp->w_buffer != NULL && wp->w_buffer->b_help)
56: break;
57: if (wp != NULL && wp->w_buffer->b_nwindows > 0)
58: win_enter(wp, TRUE);
59: else
60: {
61: /*
62: * There is no help buffer yet.
63: * Try to open the file specified by the "helpfile" option.
64: */
65: fnamep = p_hf;
66: if ((helpfd = fopen((char *)p_hf, READBIN)) == NULL)
67: {
68: #if defined(MSDOS)
69: /*
70: * for MSDOS: try the DOS search path
71: */
72: fnamep = searchpath("vim_help.txt");
73: if (fnamep == NULL ||
74: (helpfd = fopen((char *)fnamep, READBIN)) == NULL)
75: {
76: smsg((char_u *)"Sorry, help file \"%s\" and \"vim_help.txt\" not found", p_hf);
77: goto erret;
78: }
79: #else
80: smsg((char_u *)"Sorry, help file \"%s\" not found", p_hf);
81: goto erret;
82: #endif
83: }
84: fclose(helpfd);
85:
86: if (win_split(0, FALSE) == FAIL)
87: goto erret;
88:
89: if (curwin->w_height < p_hh)
90: win_setheight((int)p_hh);
91:
92: #ifdef RIGHTLEFT
93: curwin->w_p_rl = 0; /* help window is left-to-right */
94: #endif
95: curwin->w_p_nu = 0; /* no line numbers */
96:
97: /*
98: * open help file (do_ecmd() will set b_help flag, readfile() will
99: * set b_p_ro flag)
100: */
1.2 ! downsj 101: (void)do_ecmd(0, fnamep, NULL, NULL, (linenr_t)0,
! 102: ECMD_HIDE + ECMD_SET_HELP);
1.1 downsj 103:
104: /* save the values of the options we change */
105: vim_free(help_save_isk);
106: help_save_isk = strsave(curbuf->b_p_isk);
107: help_save_ts = curbuf->b_p_ts;
108:
109: /* accept all chars for keywords, except ' ', '*', '"', '|' */
110: set_string_option((char_u *)"isk", -1,
111: (char_u *)"!-~,^*,^|,^\"", TRUE);
112: curbuf->b_p_ts = 8;
113: check_buf_options(curbuf);
114: (void)init_chartab(); /* needed because 'isk' changed */
115: }
116: }
117:
118: restart_edit = 0; /* don't want insert mode in help file */
119:
120: stuffReadbuff((char_u *)":ta ");
121: if (arg != NULL && *arg != NUL)
122: stuffReadbuff(arg);
123: else
124: stuffReadbuff((char_u *)"vim_help.txt"); /* go to the index */
125: stuffcharReadbuff('\n');
126:
127: erret:
128: if (need_free)
129: vim_free(arg);
130: }
131:
132: /*
133: * Return a heuristic indicating how well the given string matches. The
134: * smaller the number, the better the match. This is the order of priorities,
135: * from best match to worst match:
136: * - Match with least alpha-numeric characters is better.
137: * - Match with least total characters is better.
138: * - Match towards the start is better.
139: * Assumption is made that the matched_string passed has already been found to
140: * match some string for which help is requested. webb.
141: */
142: int
143: help_heuristic(matched_string, offset)
144: char_u *matched_string;
145: int offset; /* offset for match */
146: {
147: int num_letters;
148: char_u *p;
149:
150: num_letters = 0;
151: for (p = matched_string; *p; p++)
152: if (isalnum(*p))
153: num_letters++;
154:
155: /*
156: * Multiply the number of letters by 100 to give it a much bigger
157: * weighting than the number of characters.
158: * If the match starts in the middle of a word, add 10000 to put it
159: * somewhere in the last half.
160: * If the match is more than 2 chars from the start, multiply by 200 to
161: * put it after matches at the start.
162: */
163: if (isalnum(matched_string[offset]) && offset > 0 &&
164: isalnum(matched_string[offset - 1]))
165: offset += 10000;
166: else if (offset > 2)
167: offset *= 200;
168: return (int)(100 * num_letters + STRLEN(matched_string) + offset);
169: }
170:
171: static int help_compare __ARGS((const void *s1, const void *s2));
172:
173: /*
174: * Compare functions for qsort() below, that checks the help heuristics number
175: * that has been put after the tagname by find_tags().
176: */
177: static int
178: help_compare(s1, s2)
179: const void *s1;
180: const void *s2;
181: {
182: char *p1;
183: char *p2;
184:
185: p1 = *(char **)s1 + strlen(*(char **)s1) + 1;
186: p2 = *(char **)s2 + strlen(*(char **)s2) + 1;
187: return strcmp(p1, p2);
188: }
189:
190: /*
191: * Find all help tags matching "arg", sort them and return in matches[], with
192: * the number of matches in num_matches.
193: * We try first with case, and then ignoring case. Then we try to choose the
194: * "best" match from the ones found.
195: */
196: int
197: find_help_tags(arg, num_matches, matches)
198: char_u *arg;
199: int *num_matches;
200: char_u ***matches;
201: {
202: char_u *s, *d;
203: regexp *prog;
204: int attempt;
205: int retval = FAIL;
1.2 ! downsj 206: int i;
! 207: static char *(mtable[]) = {"*", "g*", "[*", "]*",
! 208: "/*", "/\\*", "/\\(\\)",
! 209: "?", ":?", "?<CR>"};
! 210: static char *(rtable[]) = {"star", "gstar", "[star", "]star",
! 211: "/star", "/\\\\star", "/\\\\(\\\\)",
! 212: "?", ":?", "?<CR>"};
1.1 downsj 213:
214: reg_magic = p_magic;
215: d = IObuff; /* assume IObuff is long enough! */
216:
217: /*
1.2 ! downsj 218: * Recognize a few exceptions to the rule. Some strings that contain '*'
! 219: * with "star". Otherwise '*' is recognized as a wildcard.
1.1 downsj 220: */
1.2 ! downsj 221: for (i = sizeof(mtable) / sizeof(char *); --i >= 0; )
1.1 downsj 222: {
1.2 ! downsj 223: if (STRCMP(arg, mtable[i]) == 0)
! 224: {
! 225: STRCPY(d, rtable[i]);
! 226: break;
! 227: }
1.1 downsj 228: }
1.2 ! downsj 229:
! 230: if (i < 0) /* no match in table, replace single characters */
1.1 downsj 231: {
232: for (s = arg; *s; ++s)
233: {
1.2 ! downsj 234: /*
! 235: * Replace "|" with "bar" and """ with "quote" to match the name of
! 236: * the tags for these commands.
! 237: * Replace "*" with ".*" and "?" with "." to match command line
! 238: * completion.
! 239: * Insert a backslash before '~', '$' and '.' to avoid their
! 240: * special meaning.
! 241: */
1.1 downsj 242: if (d - IObuff > IOSIZE - 10) /* getting too long!? */
243: break;
244: switch (*s)
245: {
246: case '|': STRCPY(d, "bar");
247: d += 3;
248: continue;
249: case '\"': STRCPY(d, "quote");
250: d += 5;
251: continue;
252: case '*': *d++ = '.';
253: break;
1.2 ! downsj 254: case '?': *d++ = '.';
1.1 downsj 255: continue;
256: case '$':
257: case '.':
258: case '~': *d++ = '\\';
259: break;
260: }
1.2 ! downsj 261:
! 262: /*
! 263: * Replace "^x" by "CTRL-X". Don't do this for "^_" to make
! 264: * ":help i_^_CTRL-D" work.
! 265: */
1.1 downsj 266: if (*s < ' ' || (*s == '^' && s[1] && s[1] != '_')) /* ^x */
267: {
268: STRCPY(d, "CTRL-");
269: d += 5;
270: if (*s < ' ')
271: {
272: *d++ = *s + '@';
273: continue;
274: }
275: ++s;
276: }
277: else if (*s == '^') /* "^" or "CTRL-^" or "^_" */
278: *d++ = '\\';
1.2 ! downsj 279:
! 280: /*
! 281: * Insert a backslash before a backslash after a slash, for search
! 282: * pattern tags: "/\|" --> "/\\|".
! 283: */
! 284: else if (s[0] == '\\' && s[1] != '\\' &&
! 285: *arg == '/' && s == arg + 1)
! 286: *d++ = '\\';
! 287:
1.1 downsj 288: *d++ = *s;
1.2 ! downsj 289:
! 290: /*
! 291: * If tag starts with ', toss everything after a second '. Fixes
! 292: * CTRL-] on 'option'. (would include the trailing '.').
! 293: */
1.1 downsj 294: if (*s == '\'' && s > arg && *arg == '\'')
295: break;
296: }
1.2 ! downsj 297: *d = NUL;
1.1 downsj 298: }
299:
300: reg_ic = FALSE;
301: prog = vim_regcomp(IObuff);
302: if (prog == NULL)
303: return FAIL;
304:
305: /* First try to match with case, then without */
306: for (attempt = 0; attempt < 2; ++attempt, reg_ic = TRUE)
307: {
308: *matches = (char_u **)"";
309: *num_matches = 0;
1.2 ! downsj 310: retval = find_tags(NULL, prog, num_matches, matches, TRUE, FALSE);
1.1 downsj 311: if (retval == FAIL || *num_matches)
312: break;
313: }
314: vim_free(prog);
315:
316: #ifdef HAVE_QSORT
317: /*
318: * Sort the matches found on the heuristic number that is after the
319: * tag name. If there is no qsort, the output will be messy!
320: */
321: qsort((void *)*matches, (size_t)*num_matches,
322: sizeof(char_u *), help_compare);
323: #endif
324: return OK;
325: }