NeoMutt  2022-04-29-145-g9b6a0e
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 
55 static 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 
85 static 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 
155 static 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 
196 static 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 
225 static 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 
317 static void dump_menu(FILE *fp, enum MenuType menu, int wraplen)
318 {
319  struct Keymap *map = NULL;
320  char buf[128];
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 
353 static 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 
372 static 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 
386 void mutt_help(enum MenuType menu)
387 {
388  char banner[128];
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);
393  mutt_buffer_mktemp(&t);
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 
436 cleanup:
438 }
struct Buffer mutt_buffer_make(size_t size)
Make a new buffer on the stack.
Definition: buffer.c:63
void mutt_buffer_dealloc(struct Buffer *buf)
Release the memory allocated by a buffer.
Definition: buffer.c:292
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:904
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:120
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:152
FILE * mutt_file_fopen(const char *path, const char *mode)
Call fopen() safely.
Definition: file.c:618
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 const struct MenuFuncOp * help_lookup_function(int op, enum MenuType menu)
Find a keybinding for an operation.
Definition: help.c:55
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
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:926
const struct MenuFuncOp * km_get_table(enum MenuType mtype)
Lookup a Menu's functions.
Definition: keymap.c:1228
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:784
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:544
#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