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