mutt stable branch with some hacks
1/*
2 * Copyright (C) 1996-2000,2009 Michael R. Elkins <me@mutt.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19#define HELP_C
20
21#if HAVE_CONFIG_H
22# include "config.h"
23#endif
24
25#include "mutt.h"
26#include "mutt_curses.h"
27#include "keymap.h"
28#include "pager.h"
29#include "mapping.h"
30
31#include <ctype.h>
32#include <string.h>
33
34static const struct binding_t *help_lookupFunction (int op, int menu)
35{
36 int i;
37 const struct binding_t *map;
38
39 if (menu != MENU_PAGER)
40 {
41 /* first look in the generic map for the function */
42 for (i = 0; OpGeneric[i].name; i++)
43 if (OpGeneric[i].op == op)
44 return (&OpGeneric[i]);
45 }
46
47 if ((map = km_get_table(menu)))
48 {
49 for (i = 0; map[i].name; i++)
50 if (map[i].op == op)
51 return (&map[i]);
52 }
53
54 return (NULL);
55}
56
57void mutt_make_help (char *d, size_t dlen, const char *txt, int menu, int op)
58{
59 char buf[SHORT_STRING];
60
61 if (km_expand_key (buf, sizeof (buf), km_find_func (menu, op)) ||
62 km_expand_key (buf, sizeof (buf), km_find_func (MENU_GENERIC, op)))
63 snprintf (d, dlen, "%s:%s", buf, txt);
64 else
65 d[0] = 0;
66}
67
68char *
69mutt_compile_help (char *buf, size_t buflen, int menu, const struct mapping_t *items)
70{
71 int i;
72 size_t len;
73 char *pbuf = buf;
74
75 for (i = 0; items[i].name && buflen > 2; i++)
76 {
77 if (i)
78 {
79 *pbuf++ = ' ';
80 *pbuf++ = ' ';
81 buflen -= 2;
82 }
83 mutt_make_help (pbuf, buflen, _(items[i].name), menu, items[i].value);
84 len = mutt_strlen (pbuf);
85 pbuf += len;
86 buflen -= len;
87 }
88 return buf;
89}
90
91static int print_macro (FILE *f, int maxwidth, const char **macro)
92{
93 int n = maxwidth;
94 wchar_t wc;
95 int w;
96 size_t k;
97 size_t len = mutt_strlen (*macro);
98 mbstate_t mbstate1, mbstate2;
99
100 memset (&mbstate1, 0, sizeof (mbstate1));
101 memset (&mbstate2, 0, sizeof (mbstate2));
102 for (; len && (k = mbrtowc (&wc, *macro, len, &mbstate1)); *macro += k, len -= k)
103 {
104 if (k == (size_t)(-1) || k == (size_t)(-2))
105 {
106 if (k == (size_t)(-1))
107 memset (&mbstate1, 0, sizeof (mbstate1));
108 k = (k == (size_t)(-1)) ? 1 : len;
109 wc = replacement_char ();
110 }
111 /* glibc-2.1.3's wcwidth() returns 1 for unprintable chars! */
112 if (IsWPrint (wc) && (w = wcwidth (wc)) >= 0)
113 {
114 if (w > n)
115 break;
116 n -= w;
117 {
118 char buf[MB_LEN_MAX*2];
119 size_t n1, n2;
120 if ((n1 = wcrtomb (buf, wc, &mbstate2)) != (size_t)(-1) &&
121 (n2 = wcrtomb (buf + n1, 0, &mbstate2)) != (size_t)(-1))
122 fputs (buf, f);
123 }
124 }
125 else if (wc < 0x20 || wc == 0x7f)
126 {
127 if (2 > n)
128 break;
129 n -= 2;
130 if (wc == '\033')
131 fprintf (f, "\\e");
132 else if (wc == '\n')
133 fprintf (f, "\\n");
134 else if (wc == '\r')
135 fprintf (f, "\\r");
136 else if (wc == '\t')
137 fprintf (f, "\\t");
138 else
139 fprintf (f, "^%c", (char)((wc + '@') & 0x7f));
140 }
141 else
142 {
143 if (1 > n)
144 break;
145 n -= 1;
146 fprintf (f, "?");
147 }
148 }
149 return (maxwidth - n);
150}
151
152static int get_wrapped_width (const char *t, size_t wid)
153{
154 wchar_t wc;
155 size_t k;
156 size_t m, n;
157 size_t len = mutt_strlen (t);
158 const char *s = t;
159 mbstate_t mbstate;
160
161 memset (&mbstate, 0, sizeof (mbstate));
162 for (m = wid, n = 0;
163 len && (k = mbrtowc (&wc, s, len, &mbstate)) && (n <= wid);
164 s += k, len -= k)
165 {
166 if (*s == ' ')
167 m = n;
168 if (k == (size_t)(-1) || k == (size_t)(-2))
169 {
170 if (k == (size_t)(-1))
171 memset (&mbstate, 0, sizeof (mbstate));
172 k = (k == (size_t)(-1)) ? 1 : len;
173 wc = replacement_char ();
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
186static int pad (FILE *f, int col, int i)
187{
188 char fmt[15]; /* min size to accommodate %%-%ds */
189
190 if (col < i)
191 {
192 snprintf (fmt, sizeof(fmt), "%%-%ds", i - col);
193 fprintf (f, fmt, "");
194 return (i);
195 }
196 fputc (' ', f);
197 return (col + 1);
198}
199
200static void format_line (FILE *f, int ismacro,
201 const char *t1, const char *t2, const char *t3)
202{
203 int col;
204 int col_a, col_b;
205 int split;
206 int n;
207
208 fputs (t1, f);
209
210 /* don't try to press string into one line with less than 40 characters.
211 The double parenthesis avoids a gcc warning, sigh ... */
212 if ((split = MuttIndexWindow->cols < 40))
213 {
214 col_a = col = 0;
215 col_b = LONG_STRING;
216 fputc ('\n', f);
217 }
218 else
219 {
220 col_a = MuttIndexWindow->cols > 83 ? (MuttIndexWindow->cols - 32) >> 2 : 12;
221 col_b = MuttIndexWindow->cols > 49 ? (MuttIndexWindow->cols - 10) >> 1 : 19;
222 col = pad (f, mutt_strwidth(t1), col_a);
223 }
224
225 if (ismacro > 0)
226 {
227 if (!Pager || !mutt_strcmp (Pager, "builtin"))
228 fputs ("_\010", f);
229 fputs ("M ", f);
230 col += 2;
231
232 if (!split)
233 {
234 col += print_macro (f, col_b - col - 4, &t2);
235 if (mutt_strwidth (t2) > col_b - col)
236 t2 = "...";
237 }
238 }
239
240 col += print_macro (f, col_b - col - 1, &t2);
241 if (split)
242 fputc ('\n', f);
243 else
244 col = pad (f, col, col_b);
245
246 if (split)
247 {
248 print_macro (f, LONG_STRING, &t3);
249 fputc ('\n', f);
250 }
251 else
252 {
253 while (*t3)
254 {
255 n = MuttIndexWindow->cols - col;
256
257 if (ismacro >= 0)
258 {
259 SKIPWS(t3);
260 n = get_wrapped_width (t3, n);
261 }
262
263 n = print_macro (f, n, &t3);
264
265 if (*t3)
266 {
267 if (Pager && mutt_strcmp (Pager, "builtin"))
268 {
269 fputc ('\n', f);
270 n = 0;
271 }
272 else
273 {
274 n += col - MuttIndexWindow->cols;
275 if (option (OPTMARKERS))
276 ++n;
277 }
278 col = pad (f, n, col_b);
279 }
280 }
281 }
282
283 fputc ('\n', f);
284}
285
286static void dump_menu (FILE *f, int menu)
287{
288 struct keymap_t *map;
289 const struct binding_t *b;
290 char buf[SHORT_STRING];
291
292 /* browse through the keymap table */
293 for (map = Keymaps[menu]; map; map = map->next)
294 {
295 if (map->op != OP_NULL)
296 {
297 km_expand_key (buf, sizeof (buf), map);
298
299 if (map->op == OP_MACRO)
300 {
301 if (map->descr == NULL)
302 format_line (f, -1, buf, "macro", map->macro);
303 else
304 format_line (f, 1, buf, map->macro, map->descr);
305 }
306 else
307 {
308 b = help_lookupFunction (map->op, menu);
309 format_line (f, 0, buf, b ? b->name : "UNKNOWN",
310 b ? _(HelpStrings[b->op]) : _("ERROR: please report this bug"));
311 }
312 }
313 }
314}
315
316static int is_bound (struct keymap_t *map, int op)
317{
318 for (; map; map = map->next)
319 if (map->op == op)
320 return 1;
321 return 0;
322}
323
324static void dump_unbound (FILE *f,
325 const struct binding_t *funcs,
326 struct keymap_t *map,
327 struct keymap_t *aux)
328{
329 int i;
330
331 for (i = 0; funcs[i].name; i++)
332 {
333 if (! is_bound (map, funcs[i].op) &&
334 (!aux || ! is_bound (aux, funcs[i].op)))
335 format_line (f, 0, funcs[i].name, "", _(HelpStrings[funcs[i].op]));
336 }
337}
338
339void mutt_help (int menu)
340{
341 BUFFER *t = NULL;
342 char buf[SHORT_STRING];
343 const char *desc;
344 FILE *f;
345 const struct binding_t *funcs;
346
347 /* We don't use the buffer pool because of the extended lifetime of t */
348 t = mutt_buffer_new ();
349 mutt_buffer_mktemp (t);
350
351 funcs = km_get_table (menu);
352 desc = mutt_getnamebyvalue (menu, Menus);
353 if (!desc)
354 desc = _("<UNKNOWN>");
355
356 do
357 {
358 if ((f = safe_fopen (mutt_b2s (t), "w")) == NULL)
359 {
360 mutt_perror (mutt_b2s (t));
361 goto cleanup;
362 }
363
364 dump_menu (f, menu);
365 if (menu != MENU_EDITOR && menu != MENU_PAGER)
366 {
367 fputs (_("\nGeneric bindings:\n\n"), f);
368 dump_menu (f, MENU_GENERIC);
369 }
370
371 fputs (_("\nUnbound functions:\n\n"), f);
372 if (funcs)
373 dump_unbound (f, funcs, Keymaps[menu], NULL);
374 if (menu != MENU_PAGER)
375 dump_unbound (f, OpGeneric, Keymaps[MENU_GENERIC], Keymaps[menu]);
376
377 safe_fclose (&f);
378
379 snprintf (buf, sizeof (buf), _("Help for %s"), desc);
380 }
381 while
382 (mutt_do_pager (buf, mutt_b2s (t),
383 MUTT_PAGER_RETWINCH | MUTT_PAGER_MARKER | MUTT_PAGER_NSKIP | MUTT_PAGER_NOWRAP,
384 NULL)
385 == OP_REFORMAT_WINCH);
386
387cleanup:
388 mutt_buffer_free (&t);
389}