Coverage Report

Created: 2023-09-25 07:17

/src/neomutt/key/dump.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * @file
3
 * Dump key bindings
4
 *
5
 * @authors
6
 * Copyright (C) 2023 Richard Russon <rich@flatcap.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 key_dump Dump key bindings
25
 *
26
 * Dump key bindings
27
 */
28
29
#include "config.h"
30
#include <stdbool.h>
31
#include <stdint.h>
32
#include <stdio.h>
33
#include "mutt/lib.h"
34
#include "config/lib.h"
35
#include "core/lib.h"
36
#include "gui/lib.h"
37
#include "key/lib.h"
38
#include "menu/lib.h"
39
#include "pager/lib.h"
40
#include "parse/lib.h"
41
42
/**
43
 * dump_bind - Dumps all the binds maps of a menu into a buffer
44
 * @param buf  Output buffer
45
 * @param menu Menu to dump
46
 * @param name Menu name
47
 * @retval true  Menu is empty
48
 * @retval false Menu is not empty
49
 */
50
static bool dump_bind(struct Buffer *buf, enum MenuType menu, const char *name)
51
0
{
52
0
  bool empty = true;
53
0
  struct Keymap *map = NULL;
54
55
0
  STAILQ_FOREACH(map, &Keymaps[menu], entries)
56
0
  {
57
0
    if (map->op == OP_MACRO)
58
0
      continue;
59
60
0
    char key_binding[128] = { 0 };
61
0
    const char *fn_name = NULL;
62
63
0
    km_expand_key(key_binding, sizeof(key_binding), map);
64
0
    if (map->op == OP_NULL)
65
0
    {
66
0
      buf_add_printf(buf, "bind %s %s noop\n", name, key_binding);
67
0
      continue;
68
0
    }
69
70
    /* The pager and editor menus don't use the generic map,
71
     * however for other menus try generic first. */
72
0
    if ((menu != MENU_PAGER) && (menu != MENU_EDITOR) && (menu != MENU_GENERIC))
73
0
    {
74
0
      fn_name = mutt_get_func(OpGeneric, map->op);
75
0
    }
76
77
    /* if it's one of the menus above or generic doesn't find
78
     * the function, try with its own menu. */
79
0
    if (!fn_name)
80
0
    {
81
0
      const struct MenuFuncOp *funcs = km_get_table(menu);
82
0
      if (!funcs)
83
0
        continue;
84
85
0
      fn_name = mutt_get_func(funcs, map->op);
86
0
    }
87
88
0
    buf_add_printf(buf, "bind %s %s %s\n", name, key_binding, fn_name);
89
0
    empty = false;
90
0
  }
91
92
0
  return empty;
93
0
}
94
95
/**
96
 * dump_all_binds - Dumps all the binds inside every menu
97
 * @param buf  Output buffer
98
 */
99
static void dump_all_binds(struct Buffer *buf)
100
0
{
101
0
  for (enum MenuType i = 1; i < MENU_MAX; i++)
102
0
  {
103
0
    const bool empty = dump_bind(buf, i, mutt_map_get_name(i, MenuNames));
104
105
    /* Add a new line for readability between menus. */
106
0
    if (!empty && (i < (MENU_MAX - 1)))
107
0
      buf_addch(buf, '\n');
108
0
  }
109
0
}
110
111
/**
112
 * dump_macro - Dumps all the macros maps of a menu into a buffer
113
 * @param buf  Output buffer
114
 * @param menu Menu to dump
115
 * @param name Menu name
116
 * @retval true  Menu is empty
117
 * @retval false Menu is not empty
118
 */
119
static bool dump_macro(struct Buffer *buf, enum MenuType menu, const char *name)
120
0
{
121
0
  bool empty = true;
122
0
  struct Keymap *map = NULL;
123
124
0
  STAILQ_FOREACH(map, &Keymaps[menu], entries)
125
0
  {
126
0
    if (map->op != OP_MACRO)
127
0
      continue;
128
129
0
    char key_binding[128] = { 0 };
130
0
    km_expand_key(key_binding, sizeof(key_binding), map);
131
132
0
    struct Buffer tmp = buf_make(0);
133
0
    escape_string(&tmp, map->macro);
134
135
0
    if (map->desc)
136
0
    {
137
0
      buf_add_printf(buf, "macro %s %s \"%s\" \"%s\"\n", name, key_binding,
138
0
                     tmp.data, map->desc);
139
0
    }
140
0
    else
141
0
    {
142
0
      buf_add_printf(buf, "macro %s %s \"%s\"\n", name, key_binding, tmp.data);
143
0
    }
144
145
0
    buf_dealloc(&tmp);
146
0
    empty = false;
147
0
  }
148
149
0
  return empty;
150
0
}
151
152
/**
153
 * dump_all_macros - Dumps all the macros inside every menu
154
 * @param buf  Output buffer
155
 */
156
static void dump_all_macros(struct Buffer *buf)
157
0
{
158
0
  for (enum MenuType i = 1; i < MENU_MAX; i++)
159
0
  {
160
0
    const bool empty = dump_macro(buf, i, mutt_map_get_name(i, MenuNames));
161
162
    /* Add a new line for legibility between menus. */
163
0
    if (!empty && (i < (MENU_MAX - 1)))
164
0
      buf_addch(buf, '\n');
165
0
  }
166
0
}
167
168
/**
169
 * dump_bind_macro - Parse 'bind' and 'macro' commands - Implements ICommand::parse()
170
 */
171
enum CommandResult dump_bind_macro(struct Buffer *buf, struct Buffer *s,
172
                                   intptr_t data, struct Buffer *err)
173
0
{
174
0
  FILE *fp_out = NULL;
175
0
  bool dump_all = false, bind = (data == 0);
176
177
0
  if (!MoreArgs(s))
178
0
    dump_all = true;
179
0
  else
180
0
    parse_extract_token(buf, s, TOKEN_NO_FLAGS);
181
182
0
  if (MoreArgs(s))
183
0
  {
184
    /* More arguments potentially means the user is using the
185
     * ::command_t :bind command thus we delegate the task. */
186
0
    return MUTT_CMD_ERROR;
187
0
  }
188
189
0
  struct Buffer filebuf = buf_make(4096);
190
0
  if (dump_all || mutt_istr_equal(buf_string(buf), "all"))
191
0
  {
192
0
    if (bind)
193
0
      dump_all_binds(&filebuf);
194
0
    else
195
0
      dump_all_macros(&filebuf);
196
0
  }
197
0
  else
198
0
  {
199
0
    const int menu_index = mutt_map_get_value(buf_string(buf), MenuNames);
200
0
    if (menu_index == -1)
201
0
    {
202
      // L10N: '%s' is the (misspelled) name of the menu, e.g. 'index' or 'pager'
203
0
      buf_printf(err, _("%s: no such menu"), buf_string(buf));
204
0
      buf_dealloc(&filebuf);
205
0
      return MUTT_CMD_ERROR;
206
0
    }
207
208
0
    if (bind)
209
0
      dump_bind(&filebuf, menu_index, buf_string(buf));
210
0
    else
211
0
      dump_macro(&filebuf, menu_index, buf_string(buf));
212
0
  }
213
214
0
  if (buf_is_empty(&filebuf))
215
0
  {
216
    // L10N: '%s' is the name of the menu, e.g. 'index' or 'pager',
217
    //       it might also be 'all' when all menus are affected.
218
0
    buf_printf(err, bind ? _("%s: no binds for this menu") : _("%s: no macros for this menu"),
219
0
               dump_all ? "all" : buf_string(buf));
220
0
    buf_dealloc(&filebuf);
221
0
    return MUTT_CMD_ERROR;
222
0
  }
223
224
0
  struct Buffer *tempfile = buf_pool_get();
225
0
  buf_mktemp(tempfile);
226
0
  fp_out = mutt_file_fopen(buf_string(tempfile), "w");
227
0
  if (!fp_out)
228
0
  {
229
    // L10N: '%s' is the file name of the temporary file
230
0
    buf_printf(err, _("Could not create temporary file %s"), buf_string(tempfile));
231
0
    buf_dealloc(&filebuf);
232
0
    buf_pool_release(&tempfile);
233
0
    return MUTT_CMD_ERROR;
234
0
  }
235
0
  fputs(filebuf.data, fp_out);
236
237
0
  mutt_file_fclose(&fp_out);
238
0
  buf_dealloc(&filebuf);
239
240
0
  struct PagerData pdata = { 0 };
241
0
  struct PagerView pview = { &pdata };
242
243
0
  pdata.fname = buf_string(tempfile);
244
245
0
  pview.banner = (bind) ? "bind" : "macro";
246
0
  pview.flags = MUTT_PAGER_NO_FLAGS;
247
0
  pview.mode = PAGER_MODE_OTHER;
248
249
0
  mutt_do_pager(&pview, NULL);
250
0
  buf_pool_release(&tempfile);
251
252
0
  return MUTT_CMD_SUCCESS;
253
0
}