NeoMutt  2022-04-29-247-gc6aae8
Teaching an old dog new tricks
DOXYGEN
help.c
Go to the documentation of this file.
1
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/lib.h"
37#include "config/lib.h"
38#include "core/lib.h"
39#include "gui/lib.h"
40#include "menu/lib.h"
41#include "pager/lib.h"
42#include "functions.h"
43#include "keymap.h"
44#include "muttlib.h"
45#include "opcodes.h"
46#include "protos.h" // IWYU pragma: keep
47
55static const struct MenuFuncOp *help_lookup_function(int op, enum MenuType menu)
56{
57 if (menu != MENU_PAGER && (menu != MENU_GENERIC))
58 {
59 /* first look in the generic map for the function */
60 for (int i = 0; OpGeneric[i].name; i++)
61 if (OpGeneric[i].op == op)
62 return &OpGeneric[i];
63 }
64
65 const struct MenuFuncOp *funcs = km_get_table(menu);
66 if (funcs)
67 {
68 for (int i = 0; funcs[i].name; i++)
69 if (funcs[i].op == op)
70 return &funcs[i];
71 }
72
73 return NULL;
74}
75
85static int print_macro(FILE *fp, int maxwidth, const char **macro)
86{
87 int n = maxwidth;
88 wchar_t wc = 0;
89 size_t k;
90 size_t len = mutt_str_len(*macro);
91 mbstate_t mbstate1 = { 0 };
92 mbstate_t mbstate2 = { 0 };
93
94 for (; len && (k = mbrtowc(&wc, *macro, len, &mbstate1)); *macro += k, len -= k)
95 {
96 if ((k == (size_t) (-1)) || (k == (size_t) (-2)))
97 {
98 if (k == (size_t) (-1))
99 memset(&mbstate1, 0, sizeof(mbstate1));
100 k = (k == (size_t) (-1)) ? 1 : len;
101 wc = ReplacementChar;
102 }
103 /* glibc-2.1.3's wcwidth() returns 1 for unprintable chars! */
104 const int w = wcwidth(wc);
105 if (IsWPrint(wc) && (w >= 0))
106 {
107 if (w > n)
108 break;
109 n -= w;
110 {
111 char buf[MB_LEN_MAX * 2];
112 size_t n1, n2;
113 if (((n1 = wcrtomb(buf, wc, &mbstate2)) != (size_t) (-1)) &&
114 ((n2 = wcrtomb(buf + n1, 0, &mbstate2)) != (size_t) (-1)))
115 {
116 fputs(buf, fp);
117 }
118 }
119 }
120 else if ((wc < 0x20) || (wc == 0x7f))
121 {
122 if (n < 2)
123 break;
124 n -= 2;
125 if (wc == '\033') // Escape
126 fprintf(fp, "\\e");
127 else if (wc == '\n')
128 fprintf(fp, "\\n");
129 else if (wc == '\r')
130 fprintf(fp, "\\r");
131 else if (wc == '\t')
132 fprintf(fp, "\\t");
133 else
134 fprintf(fp, "^%c", (char) ((wc + '@') & 0x7f));
135 }
136 else
137 {
138 if (n < 1)
139 break;
140 n -= 1;
141 fprintf(fp, "?");
142 }
143 }
144 return maxwidth - n;
145}
146
155static int get_wrapped_width(const char *t, size_t wid)
156{
157 wchar_t wc = 0;
158 size_t k;
159 size_t m, n;
160 size_t len = mutt_str_len(t);
161 const char *s = t;
162 mbstate_t mbstate = { 0 };
163
164 for (m = wid, n = 0; len && (k = mbrtowc(&wc, s, len, &mbstate)) && (n <= wid);
165 s += k, len -= k)
166 {
167 if (*s == ' ')
168 m = n;
169 if ((k == (size_t) (-1)) || (k == (size_t) (-2)))
170 {
171 if (k == (size_t) (-1))
172 memset(&mbstate, 0, sizeof(mbstate));
173 k = (k == (size_t) (-1)) ? 1 : len;
174 wc = ReplacementChar;
175 }
176 if (!IsWPrint(wc))
177 wc = '?';
178 n += wcwidth(wc);
179 }
180 if (n > wid)
181 n = m;
182 else
183 n = wid;
184 return n;
185}
186
196static int pad(FILE *fp, int col, int i)
197{
198 if (col < i)
199 {
200 char fmt[32] = { 0 };
201 snprintf(fmt, sizeof(fmt), "%%-%ds", i - col);
202 fprintf(fp, fmt, "");
203 return i;
204 }
205 fputc(' ', fp);
206 return col + 1;
207}
208
225static void format_line(FILE *fp, int ismacro, const char *t1, const char *t2,
226 const char *t3, int wraplen)
227{
228 int col;
229 int col_b;
230
231 fputs(t1, fp);
232
233 /* don't try to press string into one line with less than 40 characters. */
234 bool split = (wraplen < 40);
235 if (split)
236 {
237 col = 0;
238 col_b = 1024;
239 fputc('\n', fp);
240 }
241 else
242 {
243 const int col_a = (wraplen > 83) ? (wraplen - 32) >> 2 : 12;
244 col_b = (wraplen > 49) ? (wraplen - 10) >> 1 : 19;
245 col = pad(fp, mutt_strwidth(t1), col_a);
246 }
247
248 const char *const c_pager = cs_subset_string(NeoMutt->sub, "pager");
249 if (ismacro > 0)
250 {
251 if (!c_pager || mutt_str_equal(c_pager, "builtin"))
252 fputs("_\010", fp); // Ctrl-H (backspace)
253 fputs("M ", fp);
254 col += 2;
255
256 if (!split)
257 {
258 col += print_macro(fp, col_b - col - 4, &t2);
259 if (mutt_strwidth(t2) > col_b - col)
260 t2 = "...";
261 }
262 }
263
264 col += print_macro(fp, col_b - col - 1, &t2);
265 if (split)
266 fputc('\n', fp);
267 else
268 col = pad(fp, col, col_b);
269
270 if (split)
271 {
272 print_macro(fp, 1024, &t3);
273 fputc('\n', fp);
274 }
275 else
276 {
277 while (*t3)
278 {
279 int n = wraplen - col;
280
281 if (ismacro >= 0)
282 {
283 SKIPWS(t3);
284 n = get_wrapped_width(t3, n);
285 }
286
287 n = print_macro(fp, n, &t3);
288
289 if (*t3)
290 {
291 if (mutt_str_equal(c_pager, "builtin"))
292 {
293 n += col - wraplen;
294 const bool c_markers = cs_subset_bool(NeoMutt->sub, "markers");
295 if (c_markers)
296 n++;
297 }
298 else
299 {
300 fputc('\n', fp);
301 n = 0;
302 }
303 col = pad(fp, n, col_b);
304 }
305 }
306 }
307
308 fputc('\n', fp);
309}
310
317static void dump_menu(FILE *fp, enum MenuType menu, int wraplen)
318{
319 struct Keymap *map = NULL;
320 char buf[128] = { 0 };
321
322 STAILQ_FOREACH(map, &Keymaps[menu], entries)
323 {
324 if (map->op != OP_NULL)
325 {
326 km_expand_key(buf, sizeof(buf), map);
327
328 if (map->op == OP_MACRO)
329 {
330 if (map->desc)
331 format_line(fp, 1, buf, map->macro, map->desc, wraplen);
332 else
333 format_line(fp, -1, buf, "macro", map->macro, wraplen);
334 }
335 else
336 {
337 const struct MenuFuncOp *funcs = help_lookup_function(map->op, menu);
338 format_line(fp, 0, buf, funcs ? funcs->name : "UNKNOWN",
339 funcs ? _(opcodes_get_description(funcs->op)) :
340 _("ERROR: please report this bug"),
341 wraplen);
342 }
343 }
344 }
345}
346
353static bool is_bound(struct KeymapList *km_list, int op)
354{
355 struct Keymap *map = NULL;
356 STAILQ_FOREACH(map, km_list, entries)
357 {
358 if (map->op == op)
359 return true;
360 }
361 return false;
362}
363
372static void dump_unbound(FILE *fp, const struct MenuFuncOp *funcs,
373 struct KeymapList *km_list, struct KeymapList *aux, int wraplen)
374{
375 for (int i = 0; funcs[i].name; i++)
376 {
377 if (!is_bound(km_list, funcs[i].op) && (!aux || !is_bound(aux, funcs[i].op)))
378 format_line(fp, 0, funcs[i].name, "", _(opcodes_get_description(funcs[i].op)), wraplen);
379 }
380}
381
386void mutt_help(enum MenuType menu)
387{
388 char banner[128] = { 0 };
389 FILE *fp = NULL;
390
391 /* We don't use the buffer pool because of the extended lifetime of t */
392 struct Buffer t = mutt_buffer_make(PATH_MAX);
394
395 const struct MenuFuncOp *funcs = km_get_table(menu);
396 const char *desc = mutt_map_get_name(menu, MenuNames);
397 if (!desc)
398 desc = _("<UNKNOWN>");
399
400 struct PagerData pdata = { 0 };
401 struct PagerView pview = { &pdata };
402
403 pview.mode = PAGER_MODE_HELP;
405
406 do
407 {
408 fp = mutt_file_fopen(mutt_buffer_string(&t), "w");
409 if (!fp)
410 {
412 goto cleanup;
413 }
414
415 const int wraplen = AllDialogsWindow->state.cols;
416 dump_menu(fp, menu, wraplen);
417 if ((menu != MENU_EDITOR) && (menu != MENU_PAGER) && (menu != MENU_GENERIC))
418 {
419 fprintf(fp, "\n%s\n\n", _("Generic bindings:"));
420 dump_menu(fp, MENU_GENERIC, wraplen);
421 }
422
423 fprintf(fp, "\n%s\n\n", _("Unbound functions:"));
424 if (funcs)
425 dump_unbound(fp, funcs, &Keymaps[menu], NULL, wraplen);
426 if ((menu != MENU_EDITOR) && (menu != MENU_PAGER) && (menu != MENU_GENERIC))
427 dump_unbound(fp, OpGeneric, &Keymaps[MENU_GENERIC], &Keymaps[menu], wraplen);
428
429 mutt_file_fclose(&fp);
430
431 snprintf(banner, sizeof(banner), _("Help for %s"), desc);
433 pview.banner = banner;
434 } while (mutt_do_pager(&pview, NULL) == OP_REFORMAT_WINCH);
435
436cleanup:
438}
struct Buffer mutt_buffer_make(size_t size)
Make a new buffer on the stack.
Definition: buffer.c:67
void mutt_buffer_dealloc(struct Buffer *buf)
Release the memory allocated by a buffer.
Definition: buffer.c:309
static const char * mutt_buffer_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition: buffer.h:77
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:317
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:73
Convenience wrapper for the config headers.
Convenience wrapper for the core headers.
size_t mutt_strwidth(const char *s)
Measure a string's width in screen cells.
Definition: curs_lib.c:907
struct MuttWindow * AllDialogsWindow
Parent of all Dialogs.
Definition: dialog.c:74
int mutt_do_pager(struct PagerView *pview, struct Email *e)
Display some page-able text to the user (help or attachment)
Definition: do_pager.c:123
FILE * mutt_file_fopen(const char *path, const char *mode)
Call fopen() safely.
Definition: file.c:618
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:152
const struct MenuFuncOp OpGeneric[]
Functions for the Generic Menu.
Definition: functions.c:288
#define mutt_perror(...)
Definition: logging.h:88
Convenience wrapper for the gui headers.
static void dump_unbound(FILE *fp, const struct MenuFuncOp *funcs, struct KeymapList *km_list, struct KeymapList *aux, int wraplen)
Write out all the operations with no key bindings.
Definition: help.c:372
static int pad(FILE *fp, int col, int i)
Write some padding to a file.
Definition: help.c:196
static bool is_bound(struct KeymapList *km_list, int op)
Does a function have a keybinding?
Definition: help.c:353
static void format_line(FILE *fp, int ismacro, const char *t1, const char *t2, const char *t3, int wraplen)
Write a formatted line to a file.
Definition: help.c:225
static int get_wrapped_width(const char *t, size_t wid)
Wrap a string at a sensible place.
Definition: help.c:155
static int print_macro(FILE *fp, int maxwidth, const char **macro)
Print a macro string to a file.
Definition: help.c:85
static void dump_menu(FILE *fp, enum MenuType menu, int wraplen)
Write all the key bindings to a file.
Definition: help.c:317
static const struct MenuFuncOp * help_lookup_function(int op, enum MenuType menu)
Find a keybinding for an operation.
Definition: help.c:55
void mutt_help(enum MenuType menu)
Display the help menu.
Definition: help.c:386
struct KeymapList Keymaps[MENU_MAX]
Array of Keymap keybindings, one for each Menu.
Definition: keymap.c:126
int km_expand_key(char *s, size_t len, struct Keymap *map)
Get the key string bound to a Keymap.
Definition: keymap.c:928
const struct MenuFuncOp * km_get_table(enum MenuType mtype)
Lookup a Menu's functions.
Definition: keymap.c:1230
Manage keymappings.
const char * mutt_map_get_name(int val, const struct Mapping *map)
Lookup a string for a constant.
Definition: mapping.c:42
#define IsWPrint(wc)
Definition: mbyte.h:39
GUI present the user with a selectable list.
wchar_t ReplacementChar
When a Unicode character can't be displayed, use this instead.
Definition: charset.c:57
Convenience wrapper for the library headers.
#define _(a)
Definition: message.h:28
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:807
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:567
#define PATH_MAX
Definition: mutt.h:40
Some miscellaneous functions.
#define mutt_buffer_mktemp(buf)
Definition: muttlib.h:74
const char * opcodes_get_description(int op)
Get the description of an opcode.
Definition: opcodes.c:64
All user-callable functions.
GUI display a file/email/help in a viewport with paging.
#define MUTT_PAGER_RETWINCH
Need reformatting on SIGWINCH.
Definition: lib.h:69
#define MUTT_PAGER_NSKIP
Preserve whitespace with smartwrap.
Definition: lib.h:67
#define MUTT_PAGER_NOWRAP
Format for term width, ignore $wrap.
Definition: lib.h:71
#define MUTT_PAGER_MARKER
Use markers if option is set.
Definition: lib.h:68
@ PAGER_MODE_HELP
Pager is invoked via 3rd path to show help.
Definition: lib.h:138
Prototypes for many functions.
#define STAILQ_FOREACH(var, head, field)
Definition: queue.h:352
Sidebar functions.
#define SKIPWS(ch)
Definition: string2.h:46
String manipulation buffer.
Definition: buffer.h:34
A keyboard mapping.
Definition: keymap.h:49
char * macro
macro expansion (op == OP_MACRO)
Definition: keymap.h:50
char * desc
description of a macro for the help menu
Definition: keymap.h:51
short op
operation to perform
Definition: keymap.h:52
Mapping between a function and an operation.
Definition: keymap.h:92
const char * name
Name of the function.
Definition: keymap.h:93
int op
Operation, e.g. OP_DELETE.
Definition: keymap.h:94
struct WindowState state
Current state of the Window.
Definition: mutt_window.h:127
Container for Accounts, Notifications.
Definition: neomutt.h:37
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:39
Data to be displayed by PagerView.
Definition: lib.h:158
const char * fname
Name of the file to read.
Definition: lib.h:162
Paged view into some data.
Definition: lib.h:169
struct PagerData * pdata
Data that pager displays. NOTNULL.
Definition: lib.h:170
enum PagerMode mode
Pager mode.
Definition: lib.h:171
PagerFlags flags
Additional settings to tweak pager's function.
Definition: lib.h:172
const char * banner
Title to display in status bar.
Definition: lib.h:173
short cols
Number of columns, can be MUTT_WIN_SIZE_UNLIMITED.
Definition: mutt_window.h:60
const struct Mapping MenuNames[]
Menu name lookup table.
Definition: type.c:31
MenuType
Types of GUI selections.
Definition: type.h:36
@ MENU_GENERIC
Generic selection list.
Definition: type.h:45
@ MENU_PAGER
Pager pager (email viewer)
Definition: type.h:54
@ MENU_EDITOR
Text entry area.
Definition: type.h:43