]> granicus.if.org Git - neomutt/blob - help.c
Convert mutt_attach_reply() to use buffer pool
[neomutt] / help.c
1 /**
2  * @file
3  * Generate the help-line and help-page and GUI display them
4  *
5  * @authors
6  * Copyright (C) 1996-2000,2009 Michael R. Elkins <me@mutt.org>
7  *
8  * @copyright
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
12  * version.
13  *
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
17  * details.
18  *
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/>.
21  */
22
23 /**
24  * @page help Generate the help-line and help-page and GUI display them
25  *
26  * Generate the help-line and help-page and GUI display them
27  */
28
29 #include "config.h"
30 #include <stddef.h>
31 #include <limits.h>
32 #include <stdbool.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <wchar.h>
36 #include "mutt/mutt.h"
37 #include "curs_lib.h"
38 #include "globals.h"
39 #include "keymap.h"
40 #include "mutt_window.h"
41 #include "muttlib.h"
42 #include "opcodes.h"
43 #include "pager.h"
44
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
49       NULL,
50 };
51
52 /**
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
58  */
59 static const struct Binding *help_lookup_function(int op, enum MenuType menu)
60 {
61   const struct Binding *map = NULL;
62
63   if (menu != MENU_PAGER)
64   {
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)
68         return &OpGeneric[i];
69   }
70
71   map = km_get_table(menu);
72   if (map)
73   {
74     for (int i = 0; map[i].name; i++)
75       if (map[i].op == op)
76         return &map[i];
77   }
78
79   return NULL;
80 }
81
82 /**
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
89  *
90  * This will return something like: "d:delete"
91  */
92 void mutt_make_help(char *buf, size_t buflen, const char *txt, enum MenuType menu, int op)
93 {
94   char tmp[128];
95
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)))
98   {
99     snprintf(buf, buflen, "%s:%s", tmp, txt);
100   }
101   else
102   {
103     buf[0] = '\0';
104   }
105 }
106
107 /**
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
114  */
115 char *mutt_compile_help(char *buf, size_t buflen, enum MenuType menu,
116                         const struct Mapping *items)
117 {
118   char *pbuf = buf;
119
120   for (int i = 0; items[i].name && buflen > 2; i++)
121   {
122     if (i)
123     {
124       *pbuf++ = ' ';
125       *pbuf++ = ' ';
126       buflen -= 2;
127     }
128     mutt_make_help(pbuf, buflen, _(items[i].name), menu, items[i].value);
129     const size_t len = mutt_str_strlen(pbuf);
130     pbuf += len;
131     buflen -= len;
132   }
133   return buf;
134 }
135
136 /**
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
142  *
143  * The `macro` pointer is move past the string we've printed
144  */
145 static int print_macro(FILE *fp, int maxwidth, const char **macro)
146 {
147   int n = maxwidth;
148   wchar_t wc;
149   size_t k;
150   size_t len = mutt_str_strlen(*macro);
151   mbstate_t mbstate1, mbstate2;
152
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)
156   {
157     if ((k == (size_t)(-1)) || (k == (size_t)(-2)))
158     {
159       if (k == (size_t)(-1))
160         memset(&mbstate1, 0, sizeof(mbstate1));
161       k = (k == (size_t)(-1)) ? 1 : len;
162       wc = ReplacementChar;
163     }
164     /* glibc-2.1.3's wcwidth() returns 1 for unprintable chars! */
165     const int w = wcwidth(wc);
166     if (IsWPrint(wc) && (w >= 0))
167     {
168       if (w > n)
169         break;
170       n -= w;
171       {
172         char buf[MB_LEN_MAX * 2];
173         size_t n1, n2;
174         if (((n1 = wcrtomb(buf, wc, &mbstate2)) != (size_t)(-1)) &&
175             ((n2 = wcrtomb(buf + n1, 0, &mbstate2)) != (size_t)(-1)))
176         {
177           fputs(buf, fp);
178         }
179       }
180     }
181     else if ((wc < 0x20) || (wc == 0x7f))
182     {
183       if (n < 2)
184         break;
185       n -= 2;
186       if (wc == '\033') // Escape
187         fprintf(fp, "\\e");
188       else if (wc == '\n')
189         fprintf(fp, "\\n");
190       else if (wc == '\r')
191         fprintf(fp, "\\r");
192       else if (wc == '\t')
193         fprintf(fp, "\\t");
194       else
195         fprintf(fp, "^%c", (char) ((wc + '@') & 0x7f));
196     }
197     else
198     {
199       if (n < 1)
200         break;
201       n -= 1;
202       fprintf(fp, "?");
203     }
204   }
205   return maxwidth - n;
206 }
207
208 /**
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
213  *
214  * If the string's too long, look for some whitespace to break at.
215  */
216 static int get_wrapped_width(const char *t, size_t wid)
217 {
218   wchar_t wc;
219   size_t k;
220   size_t m, n;
221   size_t len = mutt_str_strlen(t);
222   const char *s = t;
223   mbstate_t mbstate;
224
225   memset(&mbstate, 0, sizeof(mbstate));
226   for (m = wid, n = 0; len && (k = mbrtowc(&wc, s, len, &mbstate)) && (n <= wid);
227        s += k, len -= k)
228   {
229     if (*s == ' ')
230       m = n;
231     if ((k == (size_t)(-1)) || (k == (size_t)(-2)))
232     {
233       if (k == (size_t)(-1))
234         memset(&mbstate, 0, sizeof(mbstate));
235       k = (k == (size_t)(-1)) ? 1 : len;
236       wc = ReplacementChar;
237     }
238     if (!IsWPrint(wc))
239       wc = '?';
240     n += wcwidth(wc);
241   }
242   if (n > wid)
243     n = m;
244   else
245     n = wid;
246   return n;
247 }
248
249 /**
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
256  */
257 static int pad(FILE *fp, int col, int i)
258 {
259   if (col < i)
260   {
261     char fmt[32] = { 0 };
262     snprintf(fmt, sizeof(fmt), "%%-%ds", i - col);
263     fprintf(fp, fmt, "");
264     return i;
265   }
266   fputc(' ', fp);
267   return col + 1;
268 }
269
270 /**
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
278  *
279  * Assemble the three columns of text.
280  *
281  * `ismacro` can be:
282  * *  1 : Macro with a description
283  * *  0 : Non-macro
284  * * -1 : Macro with no description
285  */
286 static void format_line(FILE *fp, int ismacro, const char *t1, const char *t2,
287                         const char *t3, int wraplen)
288 {
289   int col;
290   int col_b;
291
292   fputs(t1, fp);
293
294   /* don't try to press string into one line with less than 40 characters. */
295   bool split = (wraplen < 40);
296   if (split)
297   {
298     col = 0;
299     col_b = 1024;
300     fputc('\n', fp);
301   }
302   else
303   {
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);
307   }
308
309   if (ismacro > 0)
310   {
311     if (!C_Pager || (mutt_str_strcmp(C_Pager, "builtin") == 0))
312       fputs("_\010", fp); // Ctrl-H (backspace)
313     fputs("M ", fp);
314     col += 2;
315
316     if (!split)
317     {
318       col += print_macro(fp, col_b - col - 4, &t2);
319       if (mutt_strwidth(t2) > col_b - col)
320         t2 = "...";
321     }
322   }
323
324   col += print_macro(fp, col_b - col - 1, &t2);
325   if (split)
326     fputc('\n', fp);
327   else
328     col = pad(fp, col, col_b);
329
330   if (split)
331   {
332     print_macro(fp, 1024, &t3);
333     fputc('\n', fp);
334   }
335   else
336   {
337     while (*t3)
338     {
339       int n = wraplen - col;
340
341       if (ismacro >= 0)
342       {
343         SKIPWS(t3);
344         n = get_wrapped_width(t3, n);
345       }
346
347       n = print_macro(fp, n, &t3);
348
349       if (*t3)
350       {
351         if (mutt_str_strcmp(C_Pager, "builtin") != 0)
352         {
353           fputc('\n', fp);
354           n = 0;
355         }
356         else
357         {
358           n += col - wraplen;
359           if (C_Markers)
360             n++;
361         }
362         col = pad(fp, n, col_b);
363       }
364     }
365   }
366
367   fputc('\n', fp);
368 }
369
370 /**
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
375  */
376 static void dump_menu(FILE *fp, enum MenuType menu, int wraplen)
377 {
378   struct Keymap *map = NULL;
379   const struct Binding *b = NULL;
380   char buf[128];
381
382   /* browse through the keymap table */
383   for (map = Keymaps[menu]; map; map = map->next)
384   {
385     if (map->op != OP_NULL)
386     {
387       km_expand_key(buf, sizeof(buf), map);
388
389       if (map->op == OP_MACRO)
390       {
391         if (map->desc)
392           format_line(fp, 1, buf, map->macro, map->desc, wraplen);
393         else
394           format_line(fp, -1, buf, "macro", map->macro, wraplen);
395       }
396       else
397       {
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);
401       }
402     }
403   }
404 }
405
406 /**
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
411  */
412 static bool is_bound(struct Keymap *map, int op)
413 {
414   for (; map; map = map->next)
415     if (map->op == op)
416       return true;
417   return false;
418 }
419
420 /**
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
427  */
428 static void dump_unbound(FILE *fp, const struct Binding *funcs,
429                          struct Keymap *map, struct Keymap *aux, int wraplen)
430 {
431   for (int i = 0; funcs[i].name; i++)
432   {
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);
435   }
436 }
437
438 /**
439  * mutt_help - Display the help menu
440  * @param menu    Current Menu
441  * @param wraplen Width to wrap to
442  */
443 void mutt_help(enum MenuType menu, int wraplen)
444 {
445   char buf[128];
446   FILE *fp = NULL;
447
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);
451
452   const struct Binding *funcs = km_get_table(menu);
453   const char *desc = mutt_map_get_name(menu, Menus);
454   if (!desc)
455     desc = _("<UNKNOWN>");
456
457   do
458   {
459     fp = mutt_file_fopen(mutt_b2s(&t), "w");
460     if (!fp)
461     {
462       mutt_perror(mutt_b2s(&t));
463       goto cleanup;
464     }
465
466     dump_menu(fp, menu, wraplen);
467     if ((menu != MENU_EDITOR) && (menu != MENU_PAGER))
468     {
469       fprintf(fp, "\n%s\n\n", _("Generic bindings:"));
470       dump_menu(fp, MENU_GENERIC, wraplen);
471     }
472
473     fprintf(fp, "\n%s\n\n", _("Unbound functions:"));
474     if (funcs)
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);
478
479     mutt_file_fclose(&fp);
480
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);
485
486 cleanup:
487   mutt_buffer_dealloc(&t);
488 }