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