3 * Generate the help-line and help-page and GUI display them
6 * Copyright (C) 1996-2000,2009 Michael R. Elkins <me@mutt.org>
9 * This program is free software: you can redistribute it and/or modify it under
10 * the terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 2 of the License, or (at your option) any later
14 * This program is distributed in the hope that it will be useful, but WITHOUT
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
19 * You should have received a copy of the GNU General Public License along with
20 * this program. If not, see <http://www.gnu.org/licenses/>.
24 * @page help Generate the help-line and help-page and GUI display them
26 * Generate the help-line and help-page and GUI display them
36 #include "mutt/mutt.h"
40 #include "mutt_window.h"
45 static const char *HelpStrings[] = {
46 #define DEFINE_HELP_MESSAGE(opcode, help_string) help_string,
47 OPS(DEFINE_HELP_MESSAGE)
48 #undef DEFINE_HELP_MESSAGE
53 * help_lookup_function - Find a keybinding for an operation
54 * @param op Operation, e.g. OP_DELETE
55 * @param menu Current Menu, e.g. #MENU_PAGER
56 * @retval ptr Key binding
57 * @retval NULL If none
59 static const struct Binding *help_lookup_function(int op, enum MenuType menu)
61 const struct Binding *map = NULL;
63 if (menu != MENU_PAGER)
65 /* first look in the generic map for the function */
66 for (int i = 0; OpGeneric[i].name; i++)
67 if (OpGeneric[i].op == op)
71 map = km_get_table(menu);
74 for (int i = 0; map[i].name; i++)
83 * mutt_make_help - Create one entry for the help bar
84 * @param buf Buffer for the result
85 * @param buflen Length of buffer
86 * @param txt Text part, e.g. "delete"
87 * @param menu Current Menu, e.g. #MENU_PAGER
88 * @param op Operation, e.g. OP_DELETE
90 * This will return something like: "d:delete"
92 void mutt_make_help(char *buf, size_t buflen, const char *txt, enum MenuType menu, int op)
96 if (km_expand_key(tmp, sizeof(tmp), km_find_func(menu, op)) ||
97 km_expand_key(tmp, sizeof(tmp), km_find_func(MENU_GENERIC, op)))
99 snprintf(buf, buflen, "%s:%s", tmp, txt);
108 * mutt_compile_help - Create the text for the help menu
109 * @param buf Buffer for the result
110 * @param buflen Length of buffer
111 * @param menu Current Menu, e.g. #MENU_PAGER
112 * @param items Map of functions to display in the help bar
113 * @retval ptr Buffer containing result
115 char *mutt_compile_help(char *buf, size_t buflen, enum MenuType menu,
116 const struct Mapping *items)
120 for (int i = 0; items[i].name && buflen > 2; i++)
128 mutt_make_help(pbuf, buflen, _(items[i].name), menu, items[i].value);
129 const size_t len = mutt_str_strlen(pbuf);
137 * print_macro - Print a macro string to a file
138 * @param[in] fp File to write to
139 * @param[in] maxwidth Maximum width in screen columns
140 * @param[out] macro Macro string
141 * @retval num Number of screen columns used
143 * The `macro` pointer is move past the string we've printed
145 static int print_macro(FILE *fp, int maxwidth, const char **macro)
150 size_t len = mutt_str_strlen(*macro);
151 mbstate_t mbstate1, mbstate2;
153 memset(&mbstate1, 0, sizeof(mbstate1));
154 memset(&mbstate2, 0, sizeof(mbstate2));
155 for (; len && (k = mbrtowc(&wc, *macro, len, &mbstate1)); *macro += k, len -= k)
157 if ((k == (size_t)(-1)) || (k == (size_t)(-2)))
159 if (k == (size_t)(-1))
160 memset(&mbstate1, 0, sizeof(mbstate1));
161 k = (k == (size_t)(-1)) ? 1 : len;
162 wc = ReplacementChar;
164 /* glibc-2.1.3's wcwidth() returns 1 for unprintable chars! */
165 const int w = wcwidth(wc);
166 if (IsWPrint(wc) && (w >= 0))
172 char buf[MB_LEN_MAX * 2];
174 if (((n1 = wcrtomb(buf, wc, &mbstate2)) != (size_t)(-1)) &&
175 ((n2 = wcrtomb(buf + n1, 0, &mbstate2)) != (size_t)(-1)))
181 else if ((wc < 0x20) || (wc == 0x7f))
186 if (wc == '\033') // Escape
195 fprintf(fp, "^%c", (char) ((wc + '@') & 0x7f));
209 * get_wrapped_width - Wrap a string at a sensible place
210 * @param t String to wrap
211 * @param wid Maximum width
212 * @retval num Break after this many characters
214 * If the string's too long, look for some whitespace to break at.
216 static int get_wrapped_width(const char *t, size_t wid)
221 size_t len = mutt_str_strlen(t);
225 memset(&mbstate, 0, sizeof(mbstate));
226 for (m = wid, n = 0; len && (k = mbrtowc(&wc, s, len, &mbstate)) && (n <= wid);
231 if ((k == (size_t)(-1)) || (k == (size_t)(-2)))
233 if (k == (size_t)(-1))
234 memset(&mbstate, 0, sizeof(mbstate));
235 k = (k == (size_t)(-1)) ? 1 : len;
236 wc = ReplacementChar;
250 * pad - Write some padding to a file
251 * @param fp File to write to
252 * @param col Current screen column
253 * @param i Screen column to pad until
254 * @retval num `i` - Padding was added
255 * @retval num `col` - Content was already wider than col
257 static int pad(FILE *fp, int col, int i)
261 char fmt[32] = { 0 };
262 snprintf(fmt, sizeof(fmt), "%%-%ds", i - col);
263 fprintf(fp, fmt, "");
271 * format_line - Write a formatted line to a file
272 * @param fp File to write to
273 * @param ismacro Layout mode, see below
274 * @param t1 Text part 1
275 * @param t2 Text part 2
276 * @param t3 Text part 3
277 * @param wraplen Width to wrap to
279 * Assemble the three columns of text.
282 * * 1 : Macro with a description
284 * * -1 : Macro with no description
286 static void format_line(FILE *fp, int ismacro, const char *t1, const char *t2,
287 const char *t3, int wraplen)
294 /* don't try to press string into one line with less than 40 characters. */
295 bool split = (wraplen < 40);
304 const int col_a = (wraplen > 83) ? (wraplen - 32) >> 2 : 12;
305 col_b = (wraplen > 49) ? (wraplen - 10) >> 1 : 19;
306 col = pad(fp, mutt_strwidth(t1), col_a);
311 if (!C_Pager || (mutt_str_strcmp(C_Pager, "builtin") == 0))
312 fputs("_\010", fp); // Ctrl-H (backspace)
318 col += print_macro(fp, col_b - col - 4, &t2);
319 if (mutt_strwidth(t2) > col_b - col)
324 col += print_macro(fp, col_b - col - 1, &t2);
328 col = pad(fp, col, col_b);
332 print_macro(fp, 1024, &t3);
339 int n = wraplen - col;
344 n = get_wrapped_width(t3, n);
347 n = print_macro(fp, n, &t3);
351 if (mutt_str_strcmp(C_Pager, "builtin") != 0)
362 col = pad(fp, n, col_b);
371 * dump_menu - Write all the key bindings to a file
372 * @param fp File to write to
373 * @param menu Current Menu, e.g. #MENU_PAGER
374 * @param wraplen Width to wrap to
376 static void dump_menu(FILE *fp, enum MenuType menu, int wraplen)
378 struct Keymap *map = NULL;
379 const struct Binding *b = NULL;
382 /* browse through the keymap table */
383 for (map = Keymaps[menu]; map; map = map->next)
385 if (map->op != OP_NULL)
387 km_expand_key(buf, sizeof(buf), map);
389 if (map->op == OP_MACRO)
392 format_line(fp, 1, buf, map->macro, map->desc, wraplen);
394 format_line(fp, -1, buf, "macro", map->macro, wraplen);
398 b = help_lookup_function(map->op, menu);
399 format_line(fp, 0, buf, b ? b->name : "UNKNOWN",
400 b ? _(HelpStrings[b->op]) : _("ERROR: please report this bug"), wraplen);
407 * is_bound - Does a function have a keybinding?
408 * @param map Keymap to examine
409 * @param op Operation, e.g. OP_DELETE
410 * @retval true If a key is bound to that operation
412 static bool is_bound(struct Keymap *map, int op)
414 for (; map; map = map->next)
421 * dump_unbound - Write out all the operations with no key bindings
422 * @param fp File to write to
423 * @param funcs All the bindings for the current menu
424 * @param map First key map to consider
425 * @param aux Second key map to consider
426 * @param wraplen Width to wrap to
428 static void dump_unbound(FILE *fp, const struct Binding *funcs,
429 struct Keymap *map, struct Keymap *aux, int wraplen)
431 for (int i = 0; funcs[i].name; i++)
433 if (!is_bound(map, funcs[i].op) && (!aux || !is_bound(aux, funcs[i].op)))
434 format_line(fp, 0, funcs[i].name, "", _(HelpStrings[funcs[i].op]), wraplen);
439 * mutt_help - Display the help menu
440 * @param menu Current Menu
441 * @param wraplen Width to wrap to
443 void mutt_help(enum MenuType menu, int wraplen)
448 /* We don't use the buffer pool because of the extended lifetime of t */
449 struct Buffer t = mutt_buffer_make(PATH_MAX);
450 mutt_buffer_mktemp(&t);
452 const struct Binding *funcs = km_get_table(menu);
453 const char *desc = mutt_map_get_name(menu, Menus);
455 desc = _("<UNKNOWN>");
459 fp = mutt_file_fopen(mutt_b2s(&t), "w");
462 mutt_perror(mutt_b2s(&t));
466 dump_menu(fp, menu, wraplen);
467 if ((menu != MENU_EDITOR) && (menu != MENU_PAGER))
469 fprintf(fp, "\n%s\n\n", _("Generic bindings:"));
470 dump_menu(fp, MENU_GENERIC, wraplen);
473 fprintf(fp, "\n%s\n\n", _("Unbound functions:"));
475 dump_unbound(fp, funcs, Keymaps[menu], NULL, wraplen);
476 if (menu != MENU_PAGER)
477 dump_unbound(fp, OpGeneric, Keymaps[MENU_GENERIC], Keymaps[menu], wraplen);
479 mutt_file_fclose(&fp);
481 snprintf(buf, sizeof(buf), _("Help for %s"), desc);
482 } while (mutt_do_pager(buf, mutt_b2s(&t),
483 MUTT_PAGER_RETWINCH | MUTT_PAGER_MARKER | MUTT_PAGER_NSKIP | MUTT_PAGER_NOWRAP,
484 NULL) == OP_REFORMAT_WINCH);
487 mutt_buffer_dealloc(&t);