/src/neomutt/menu/functions.c
Line | Count | Source (jump to first uncovered line) |
1 | | /** |
2 | | * @file |
3 | | * Menu functions |
4 | | * |
5 | | * @authors |
6 | | * Copyright (C) 2022 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 menu_functions Menu functions |
25 | | * |
26 | | * Menu functions |
27 | | */ |
28 | | |
29 | | #include "config.h" |
30 | | #include <stddef.h> |
31 | | #include <stdbool.h> |
32 | | #include <stdint.h> |
33 | | #include "mutt/lib.h" |
34 | | #include "config/lib.h" |
35 | | #include "core/lib.h" |
36 | | #include "gui/lib.h" |
37 | | #include "mutt.h" |
38 | | #include "functions.h" |
39 | | #include "lib.h" |
40 | | #include "editor/lib.h" |
41 | | #include "history/lib.h" |
42 | | #include "protos.h" |
43 | | #include "type.h" |
44 | | |
45 | | extern char *SearchBuffers[]; |
46 | | |
47 | 0 | #define MUTT_SEARCH_UP 1 |
48 | 0 | #define MUTT_SEARCH_DOWN 2 |
49 | | |
50 | | /** |
51 | | * search - Search a menu |
52 | | * @param menu Menu to search |
53 | | * @param op Search operation, e.g. OP_SEARCH_NEXT |
54 | | * @retval >=0 Index of matching item |
55 | | * @retval -1 Search failed, or was cancelled |
56 | | */ |
57 | | static int search(struct Menu *menu, int op) |
58 | 0 | { |
59 | 0 | int rc = -1; |
60 | 0 | int wrap = 0; |
61 | 0 | int search_dir; |
62 | 0 | regex_t re = { 0 }; |
63 | 0 | struct Buffer *buf = buf_pool_get(); |
64 | |
|
65 | 0 | char *search_buf = ((menu->type < MENU_MAX)) ? SearchBuffers[menu->type] : NULL; |
66 | |
|
67 | 0 | if (!(search_buf && *search_buf) || ((op != OP_SEARCH_NEXT) && (op != OP_SEARCH_OPPOSITE))) |
68 | 0 | { |
69 | 0 | buf_strcpy(buf, search_buf && (search_buf[0] != '\0') ? search_buf : ""); |
70 | 0 | if ((mw_get_field(((op == OP_SEARCH) || (op == OP_SEARCH_NEXT)) ? _("Search for: ") : _("Reverse search for: "), |
71 | 0 | buf, MUTT_COMP_CLEAR, HC_OTHER, NULL, NULL) != 0) || |
72 | 0 | buf_is_empty(buf)) |
73 | 0 | { |
74 | 0 | goto done; |
75 | 0 | } |
76 | 0 | if (menu->type < MENU_MAX) |
77 | 0 | { |
78 | 0 | mutt_str_replace(&SearchBuffers[menu->type], buf_string(buf)); |
79 | 0 | search_buf = SearchBuffers[menu->type]; |
80 | 0 | } |
81 | 0 | menu->search_dir = ((op == OP_SEARCH) || (op == OP_SEARCH_NEXT)) ? |
82 | 0 | MUTT_SEARCH_DOWN : |
83 | 0 | MUTT_SEARCH_UP; |
84 | 0 | } |
85 | | |
86 | 0 | search_dir = (menu->search_dir == MUTT_SEARCH_UP) ? -1 : 1; |
87 | 0 | if (op == OP_SEARCH_OPPOSITE) |
88 | 0 | search_dir = -search_dir; |
89 | |
|
90 | 0 | if (search_buf) |
91 | 0 | { |
92 | 0 | uint16_t flags = mutt_mb_is_lower(search_buf) ? REG_ICASE : 0; |
93 | 0 | rc = REG_COMP(&re, search_buf, REG_NOSUB | flags); |
94 | 0 | } |
95 | |
|
96 | 0 | if (rc != 0) |
97 | 0 | { |
98 | 0 | regerror(rc, &re, buf->data, buf->dsize); |
99 | 0 | mutt_error("%s", buf_string(buf)); |
100 | 0 | rc = -1; |
101 | 0 | goto done; |
102 | 0 | } |
103 | | |
104 | 0 | rc = menu->current + search_dir; |
105 | 0 | search_next: |
106 | 0 | if (wrap) |
107 | 0 | mutt_message(_("Search wrapped to top")); |
108 | 0 | while ((rc >= 0) && (rc < menu->max)) |
109 | 0 | { |
110 | 0 | if (menu->search(menu, &re, rc) == 0) |
111 | 0 | { |
112 | 0 | regfree(&re); |
113 | 0 | goto done; |
114 | 0 | } |
115 | | |
116 | 0 | rc += search_dir; |
117 | 0 | } |
118 | | |
119 | 0 | const bool c_wrap_search = cs_subset_bool(menu->sub, "wrap_search"); |
120 | 0 | if (c_wrap_search && (wrap++ == 0)) |
121 | 0 | { |
122 | 0 | rc = (search_dir == 1) ? 0 : menu->max - 1; |
123 | 0 | goto search_next; |
124 | 0 | } |
125 | 0 | regfree(&re); |
126 | 0 | mutt_error(_("Not found")); |
127 | 0 | rc = -1; |
128 | |
|
129 | 0 | done: |
130 | 0 | buf_pool_release(&buf); |
131 | 0 | return rc; |
132 | 0 | } |
133 | | |
134 | | // ----------------------------------------------------------------------------- |
135 | | |
136 | | /** |
137 | | * menu_movement - Handle all the common Menu movements - Implements ::menu_function_t - @ingroup menu_function_api |
138 | | */ |
139 | | static int menu_movement(struct Menu *menu, int op) |
140 | 0 | { |
141 | 0 | switch (op) |
142 | 0 | { |
143 | 0 | case OP_BOTTOM_PAGE: |
144 | 0 | menu_bottom_page(menu); |
145 | 0 | return FR_SUCCESS; |
146 | | |
147 | 0 | case OP_CURRENT_BOTTOM: |
148 | 0 | menu_current_bottom(menu); |
149 | 0 | return FR_SUCCESS; |
150 | | |
151 | 0 | case OP_CURRENT_MIDDLE: |
152 | 0 | menu_current_middle(menu); |
153 | 0 | return FR_SUCCESS; |
154 | | |
155 | 0 | case OP_CURRENT_TOP: |
156 | 0 | menu_current_top(menu); |
157 | 0 | return FR_SUCCESS; |
158 | | |
159 | 0 | case OP_FIRST_ENTRY: |
160 | 0 | menu_first_entry(menu); |
161 | 0 | return FR_SUCCESS; |
162 | | |
163 | 0 | case OP_HALF_DOWN: |
164 | 0 | menu_half_down(menu); |
165 | 0 | return FR_SUCCESS; |
166 | | |
167 | 0 | case OP_HALF_UP: |
168 | 0 | menu_half_up(menu); |
169 | 0 | return FR_SUCCESS; |
170 | | |
171 | 0 | case OP_LAST_ENTRY: |
172 | 0 | menu_last_entry(menu); |
173 | 0 | return FR_SUCCESS; |
174 | | |
175 | 0 | case OP_MIDDLE_PAGE: |
176 | 0 | menu_middle_page(menu); |
177 | 0 | return FR_SUCCESS; |
178 | | |
179 | 0 | case OP_NEXT_ENTRY: |
180 | 0 | menu_next_entry(menu); |
181 | 0 | return FR_SUCCESS; |
182 | | |
183 | 0 | case OP_NEXT_LINE: |
184 | 0 | menu_next_line(menu); |
185 | 0 | return FR_SUCCESS; |
186 | | |
187 | 0 | case OP_NEXT_PAGE: |
188 | 0 | menu_next_page(menu); |
189 | 0 | return FR_SUCCESS; |
190 | | |
191 | 0 | case OP_PREV_ENTRY: |
192 | 0 | menu_prev_entry(menu); |
193 | 0 | return FR_SUCCESS; |
194 | | |
195 | 0 | case OP_PREV_LINE: |
196 | 0 | menu_prev_line(menu); |
197 | 0 | return FR_SUCCESS; |
198 | | |
199 | 0 | case OP_PREV_PAGE: |
200 | 0 | menu_prev_page(menu); |
201 | 0 | return FR_SUCCESS; |
202 | | |
203 | 0 | case OP_TOP_PAGE: |
204 | 0 | menu_top_page(menu); |
205 | 0 | return FR_SUCCESS; |
206 | | |
207 | 0 | default: |
208 | 0 | return FR_UNKNOWN; |
209 | 0 | } |
210 | 0 | } |
211 | | |
212 | | /** |
213 | | * menu_search - Handle Menu searching - Implements ::menu_function_t - @ingroup menu_function_api |
214 | | */ |
215 | | static int menu_search(struct Menu *menu, int op) |
216 | 0 | { |
217 | 0 | if (menu->search) |
218 | 0 | { |
219 | 0 | int index = search(menu, op); |
220 | 0 | if (index != -1) |
221 | 0 | menu_set_index(menu, index); |
222 | 0 | } |
223 | 0 | return FR_SUCCESS; |
224 | 0 | } |
225 | | |
226 | | /** |
227 | | * op_help - Show the help screen - Implements ::menu_function_t - @ingroup menu_function_api |
228 | | */ |
229 | | static int op_help(struct Menu *menu, int op) |
230 | | { |
231 | | mutt_help(menu->type); |
232 | | menu->redraw = MENU_REDRAW_FULL; |
233 | | return FR_SUCCESS; |
234 | | } |
235 | | |
236 | | /** |
237 | | * op_jump - Jump to an index number - Implements ::menu_function_t - @ingroup menu_function_api |
238 | | */ |
239 | | static int op_jump(struct Menu *menu, int op) |
240 | | { |
241 | | if (menu->max == 0) |
242 | | { |
243 | | mutt_error(_("No entries")); |
244 | | return FR_SUCCESS; |
245 | | } |
246 | | |
247 | | const int digit = op - OP_JUMP; |
248 | | if ((digit > 0) && (digit < 10)) |
249 | | { |
250 | | mutt_unget_ch('0' + digit); |
251 | | } |
252 | | |
253 | | struct Buffer *buf = buf_pool_get(); |
254 | | if ((mw_get_field(_("Jump to: "), buf, MUTT_COMP_NO_FLAGS, HC_OTHER, NULL, NULL) == 0) && |
255 | | !buf_is_empty(buf)) |
256 | | { |
257 | | int n = 0; |
258 | | if (mutt_str_atoi_full(buf_string(buf), &n) && (n > 0) && (n < (menu->max + 1))) |
259 | | { |
260 | | menu_set_index(menu, n - 1); // msg numbers are 0-based |
261 | | } |
262 | | else |
263 | | { |
264 | | mutt_error(_("Invalid index number")); |
265 | | } |
266 | | } |
267 | | |
268 | | buf_pool_release(&buf); |
269 | | return FR_SUCCESS; |
270 | | } |
271 | | |
272 | | // ----------------------------------------------------------------------------- |
273 | | |
274 | | /** |
275 | | * MenuFunctions - All the NeoMutt functions that the Menu supports |
276 | | */ |
277 | | static const struct MenuFunction MenuFunctions[] = { |
278 | | // clang-format off |
279 | | { OP_BOTTOM_PAGE, menu_movement }, |
280 | | { OP_CURRENT_BOTTOM, menu_movement }, |
281 | | { OP_CURRENT_MIDDLE, menu_movement }, |
282 | | { OP_CURRENT_TOP, menu_movement }, |
283 | | { OP_FIRST_ENTRY, menu_movement }, |
284 | | { OP_HALF_DOWN, menu_movement }, |
285 | | { OP_HALF_UP, menu_movement }, |
286 | | { OP_HELP, op_help }, |
287 | | { OP_JUMP, op_jump }, |
288 | | { OP_JUMP_1, op_jump }, |
289 | | { OP_JUMP_2, op_jump }, |
290 | | { OP_JUMP_3, op_jump }, |
291 | | { OP_JUMP_4, op_jump }, |
292 | | { OP_JUMP_5, op_jump }, |
293 | | { OP_JUMP_6, op_jump }, |
294 | | { OP_JUMP_7, op_jump }, |
295 | | { OP_JUMP_8, op_jump }, |
296 | | { OP_JUMP_9, op_jump }, |
297 | | { OP_LAST_ENTRY, menu_movement }, |
298 | | { OP_MIDDLE_PAGE, menu_movement }, |
299 | | { OP_NEXT_ENTRY, menu_movement }, |
300 | | { OP_NEXT_LINE, menu_movement }, |
301 | | { OP_NEXT_PAGE, menu_movement }, |
302 | | { OP_PREV_ENTRY, menu_movement }, |
303 | | { OP_PREV_LINE, menu_movement }, |
304 | | { OP_PREV_PAGE, menu_movement }, |
305 | | { OP_SEARCH, menu_search }, |
306 | | { OP_SEARCH_NEXT, menu_search }, |
307 | | { OP_SEARCH_OPPOSITE, menu_search }, |
308 | | { OP_SEARCH_REVERSE, menu_search }, |
309 | | { OP_TOP_PAGE, menu_movement }, |
310 | | { 0, NULL }, |
311 | | // clang-format on |
312 | | }; |
313 | | |
314 | | /** |
315 | | * menu_function_dispatcher - Perform a Menu function - Implements ::function_dispatcher_t - @ingroup dispatcher_api |
316 | | */ |
317 | | int menu_function_dispatcher(struct MuttWindow *win, int op) |
318 | 0 | { |
319 | 0 | if (!win || !win->wdata) |
320 | 0 | return FR_UNKNOWN; |
321 | | |
322 | 0 | struct Menu *menu = win->wdata; |
323 | |
|
324 | 0 | int rc = FR_UNKNOWN; |
325 | 0 | for (size_t i = 0; MenuFunctions[i].op != OP_NULL; i++) |
326 | 0 | { |
327 | 0 | const struct MenuFunction *fn = &MenuFunctions[i]; |
328 | 0 | if (fn->op == op) |
329 | 0 | { |
330 | 0 | rc = fn->function(menu, op); |
331 | 0 | break; |
332 | 0 | } |
333 | 0 | } |
334 | |
|
335 | 0 | if (rc == FR_UNKNOWN) // Not our function |
336 | 0 | return rc; |
337 | | |
338 | 0 | const char *result = dispatcher_get_retval_name(rc); |
339 | 0 | mutt_debug(LL_DEBUG1, "Handled %s (%d) -> %s\n", opcodes_get_name(op), op, NONULL(result)); |
340 | |
|
341 | 0 | return rc; |
342 | 0 | } |