1 /**
2 * @file
3 * Information commands
4 *
5 * @authors
6 * Copyright (C) 2016 Christopher John Czettel <chris@meicloud.at>
7 * Copyright (C) 2018 Richard Russon <rich@flatcap.org>
8 * Copyright (C) 2019 Victor Fernandes <criw@pm.me>
9 *
10 * @copyright
11 * This program is free software: you can redistribute it and/or modify it under
12 * the terms of the GNU General Public License as published by the Free Software
13 * Foundation, either version 2 of the License, or (at your option) any later
14 * version.
15 *
16 * This program is distributed in the hope that it will be useful, but WITHOUT
17 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
19 * details.
20 *
21 * You should have received a copy of the GNU General Public License along with
22 * this program. If not, see <http://www.gnu.org/licenses/>.
23 */
24
25 /**
26 * @page neo_icommands Information commands
27 *
28 * Information commands
29 */
30
31 #include "config.h"
32 #include <limits.h>
33 #include <stdbool.h>
34 #include <stdio.h>
35 #include "mutt/lib.h"
36 #include "config/lib.h"
37 #include "core/lib.h"
38 #include "mutt.h"
39 #include "icommands.h"
40 #include "menu/lib.h"
41 #include "pager/lib.h"
42 #include "functions.h"
43 #include "init.h"
44 #include "keymap.h"
45 #include "muttlib.h"
46 #include "opcodes.h"
47 #include "version.h"
48
49 // clang-format off
50 static enum CommandResult icmd_bind (struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err);
51 static enum CommandResult icmd_set (struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err);
52 static enum CommandResult icmd_version(struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err);
53 // clang-format on
54
55 /**
56 * ICommandList - All available informational commands
57 *
58 * @note These commands take precedence over conventional NeoMutt rc-lines
59 */
60 static const struct ICommand ICommandList[] = {
61 // clang-format off
62 { "bind", icmd_bind, 0 },
63 { "macro", icmd_bind, 1 },
64 { "set", icmd_set, 0 },
65 { "version", icmd_version, 0 },
66 { NULL, NULL, 0 },
67 // clang-format on
68 };
69
70 /**
71 * mutt_parse_icommand - Parse an informational command
72 * @param line Command to execute
73 * @param err Buffer for error messages
74 * @retval #MUTT_CMD_SUCCESS Success
75 * @retval #MUTT_CMD_WARNING Warning with message: command failed
76 * @retval #MUTT_CMD_ERROR
77 * - Error (no message): command not found
78 * - Error with message: command failed
79 */
mutt_parse_icommand(char * line,struct Buffer * err)80 enum CommandResult mutt_parse_icommand(/* const */ char *line, struct Buffer *err)
81 {
82 if (!line || (*line == '\0') || !err)
83 return MUTT_CMD_ERROR;
84
85 enum CommandResult rc = MUTT_CMD_ERROR;
86
87 struct Buffer *token = mutt_buffer_pool_get();
88 struct Buffer expn = mutt_buffer_make(0);
89 mutt_buffer_addstr(&expn, line);
90 mutt_buffer_seek(&expn, 0);
91
92 mutt_buffer_reset(err);
93
94 SKIPWS(expn.dptr);
95 mutt_extract_token(token, &expn, MUTT_TOKEN_NO_FLAGS);
96 for (size_t i = 0; ICommandList[i].name; i++)
97 {
98 if (!mutt_str_equal(token->data, ICommandList[i].name))
99 continue;
100
101 rc = ICommandList[i].parse(token, &expn, ICommandList[i].data, err);
102 if (rc != 0)
103 goto finish;
104
105 break; /* Continue with next command */
106 }
107
108 finish:
109 mutt_buffer_pool_release(&token);
110 mutt_buffer_dealloc(&expn);
111 return rc;
112 }
113
114 /**
115 * dump_bind - Dump a bind map to a buffer
116 * @param buf Output buffer
117 * @param menu Map menu
118 * @param map Bind keymap
119 */
dump_bind(struct Buffer * buf,struct Mapping * menu,struct Keymap * map)120 static void dump_bind(struct Buffer *buf, struct Mapping *menu, struct Keymap *map)
121 {
122 char key_binding[32];
123 const char *fn_name = NULL;
124
125 km_expand_key(key_binding, sizeof(key_binding), map);
126 if (map->op == OP_NULL)
127 {
128 mutt_buffer_add_printf(buf, "bind %s %s noop\n", menu->name, key_binding);
129 return;
130 }
131
132 /* The pager and editor menus don't use the generic map,
133 * however for other menus try generic first. */
134 if ((menu->value != MENU_PAGER) && (menu->value != MENU_EDITOR) && (menu->value != MENU_GENERIC))
135 {
136 fn_name = mutt_get_func(OpGeneric, map->op);
137 }
138
139 /* if it's one of the menus above or generic doesn't find
140 * the function, try with its own menu. */
141 if (!fn_name)
142 {
143 const struct Binding *bindings = km_get_table(menu->value);
144 if (!bindings)
145 return;
146
147 fn_name = mutt_get_func(bindings, map->op);
148 }
149
150 mutt_buffer_add_printf(buf, "bind %s %s %s\n", menu->name, key_binding, fn_name);
151 }
152
153 /**
154 * dump_macro - Dump a macro map to a buffer
155 * @param buf Output buffer
156 * @param menu Map menu
157 * @param map Macro keymap
158 */
dump_macro(struct Buffer * buf,struct Mapping * menu,struct Keymap * map)159 static void dump_macro(struct Buffer *buf, struct Mapping *menu, struct Keymap *map)
160 {
161 char key_binding[MAX_SEQ];
162 km_expand_key(key_binding, MAX_SEQ, map);
163
164 struct Buffer tmp = mutt_buffer_make(0);
165 escape_string(&tmp, map->macro);
166
167 if (map->desc)
168 {
169 mutt_buffer_add_printf(buf, "macro %s %s \"%s\" \"%s\"\n", menu->name,
170 key_binding, tmp.data, map->desc);
171 }
172 else
173 {
174 mutt_buffer_add_printf(buf, "macro %s %s \"%s\"\n", menu->name, key_binding, tmp.data);
175 }
176
177 mutt_buffer_dealloc(&tmp);
178 }
179
180 /**
181 * dump_menu - Dumps all the binds or macros maps of a menu into a buffer
182 * @param buf Output buffer
183 * @param menu Menu to dump
184 * @param bind If true it's :bind, else :macro
185 * @retval true Menu is empty
186 * @retval false Menu is not empty
187 */
dump_menu(struct Buffer * buf,struct Mapping * menu,bool bind)188 static bool dump_menu(struct Buffer *buf, struct Mapping *menu, bool bind)
189 {
190 bool empty = true;
191 struct Keymap *map = NULL;
192
193 STAILQ_FOREACH(map, &Keymaps[menu->value], entries)
194 {
195 if (bind && (map->op != OP_MACRO))
196 {
197 empty = false;
198 dump_bind(buf, menu, map);
199 }
200 else if (!bind && (map->op == OP_MACRO))
201 {
202 empty = false;
203 dump_macro(buf, menu, map);
204 }
205 }
206
207 return empty;
208 }
209
210 /**
211 * dump_all_menus - Dumps all the binds or macros inside every menu
212 * @param buf Output buffer
213 * @param bind If true it's :bind, else :macro
214 */
dump_all_menus(struct Buffer * buf,bool bind)215 static void dump_all_menus(struct Buffer *buf, bool bind)
216 {
217 for (int i = 0; i < MENU_MAX; i++)
218 {
219 const char *menu_name = mutt_map_get_name(i, MenuNames);
220 struct Mapping menu = { menu_name, i };
221
222 const bool empty = dump_menu(buf, &menu, bind);
223
224 /* Add a new line for readability between menus. */
225 if (!empty && (i < (MENU_MAX - 1)))
226 mutt_buffer_addch(buf, '\n');
227 }
228 }
229
230 /**
231 * icmd_bind - Parse 'bind' and 'macro' commands - Implements ICommand::parse()
232 */
icmd_bind(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)233 static enum CommandResult icmd_bind(struct Buffer *buf, struct Buffer *s,
234 intptr_t data, struct Buffer *err)
235 {
236 FILE *fp_out = NULL;
237 char tempfile[PATH_MAX];
238 bool dump_all = false, bind = (data == 0);
239
240 if (!MoreArgs(s))
241 dump_all = true;
242 else
243 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
244
245 if (MoreArgs(s))
246 {
247 /* More arguments potentially means the user is using the
248 * ::command_t :bind command thus we delegate the task. */
249 return MUTT_CMD_ERROR;
250 }
251
252 struct Buffer filebuf = mutt_buffer_make(4096);
253 if (dump_all || mutt_istr_equal(buf->data, "all"))
254 {
255 dump_all_menus(&filebuf, bind);
256 }
257 else
258 {
259 const int menu_index = mutt_map_get_value(buf->data, MenuNames);
260 if (menu_index == -1)
261 {
262 // L10N: '%s' is the (misspelled) name of the menu, e.g. 'index' or 'pager'
263 mutt_buffer_printf(err, _("%s: no such menu"), buf->data);
264 mutt_buffer_dealloc(&filebuf);
265 return MUTT_CMD_ERROR;
266 }
267
268 struct Mapping menu = { buf->data, menu_index };
269 dump_menu(&filebuf, &menu, bind);
270 }
271
272 if (mutt_buffer_is_empty(&filebuf))
273 {
274 // L10N: '%s' is the name of the menu, e.g. 'index' or 'pager',
275 // it might also be 'all' when all menus are affected.
276 mutt_buffer_printf(err, bind ? _("%s: no binds for this menu") : _("%s: no macros for this menu"),
277 dump_all ? "all" : buf->data);
278 mutt_buffer_dealloc(&filebuf);
279 return MUTT_CMD_ERROR;
280 }
281
282 mutt_mktemp(tempfile, sizeof(tempfile));
283 fp_out = mutt_file_fopen(tempfile, "w");
284 if (!fp_out)
285 {
286 // L10N: '%s' is the file name of the temporary file
287 mutt_buffer_printf(err, _("Could not create temporary file %s"), tempfile);
288 mutt_buffer_dealloc(&filebuf);
289 return MUTT_CMD_ERROR;
290 }
291 fputs(filebuf.data, fp_out);
292
293 mutt_file_fclose(&fp_out);
294 mutt_buffer_dealloc(&filebuf);
295
296 struct PagerData pdata = { 0 };
297 struct PagerView pview = { &pdata };
298
299 pdata.fname = tempfile;
300
301 pview.banner = (bind) ? "bind" : "macro";
302 pview.flags = MUTT_PAGER_NO_FLAGS;
303 pview.mode = PAGER_MODE_OTHER;
304
305 if (mutt_do_pager(&pview, NULL) == -1)
306 {
307 // L10N: '%s' is the file name of the temporary file
308 mutt_buffer_printf(err, _("Could not create temporary file %s"), tempfile);
309 return MUTT_CMD_ERROR;
310 }
311
312 return MUTT_CMD_SUCCESS;
313 }
314
315 /**
316 * icmd_set - Parse 'set' command to display config - Implements ICommand::parse()
317 */
icmd_set(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)318 static enum CommandResult icmd_set(struct Buffer *buf, struct Buffer *s,
319 intptr_t data, struct Buffer *err)
320 {
321 const bool set = mutt_str_equal(s->data, "set");
322 const bool set_all = mutt_str_equal(s->data, "set all");
323
324 if (!set && !set_all)
325 return MUTT_CMD_ERROR;
326
327 char tempfile[PATH_MAX];
328 mutt_mktemp(tempfile, sizeof(tempfile));
329
330 FILE *fp_out = mutt_file_fopen(tempfile, "w");
331 if (!fp_out)
332 {
333 // L10N: '%s' is the file name of the temporary file
334 mutt_buffer_printf(err, _("Could not create temporary file %s"), tempfile);
335 return MUTT_CMD_ERROR;
336 }
337
338 if (set_all)
339 dump_config(NeoMutt->sub->cs, CS_DUMP_NO_FLAGS, fp_out);
340 else
341 dump_config(NeoMutt->sub->cs, CS_DUMP_ONLY_CHANGED, fp_out);
342
343 mutt_file_fclose(&fp_out);
344
345 struct PagerData pdata = { 0 };
346 struct PagerView pview = { &pdata };
347
348 pdata.fname = tempfile;
349
350 pview.banner = "set";
351 pview.flags = MUTT_PAGER_NO_FLAGS;
352 pview.mode = PAGER_MODE_OTHER;
353
354 mutt_do_pager(&pview, NULL);
355
356 return MUTT_CMD_SUCCESS;
357 }
358
359 /**
360 * icmd_version - Parse 'version' command - Implements ICommand::parse()
361 */
icmd_version(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)362 static enum CommandResult icmd_version(struct Buffer *buf, struct Buffer *s,
363 intptr_t data, struct Buffer *err)
364 {
365 char tempfile[PATH_MAX];
366 mutt_mktemp(tempfile, sizeof(tempfile));
367
368 FILE *fp_out = mutt_file_fopen(tempfile, "w");
369 if (!fp_out)
370 {
371 // L10N: '%s' is the file name of the temporary file
372 mutt_buffer_printf(err, _("Could not create temporary file %s"), tempfile);
373 return MUTT_CMD_ERROR;
374 }
375
376 print_version(fp_out);
377 mutt_file_fclose(&fp_out);
378
379 struct PagerData pdata = { 0 };
380 struct PagerView pview = { &pdata };
381
382 pdata.fname = tempfile;
383
384 pview.banner = "version";
385 pview.flags = MUTT_PAGER_NO_FLAGS;
386 pview.mode = PAGER_MODE_OTHER;
387
388 if (mutt_do_pager(&pview, NULL) == -1)
389 {
390 // L10N: '%s' is the file name of the temporary file
391 mutt_buffer_printf(err, _("Could not create temporary file %s"), tempfile);
392 return MUTT_CMD_ERROR;
393 }
394
395 return MUTT_CMD_SUCCESS;
396 }
397