NeoMutt  2025-12-11-800-ga0ee0f
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
helpers.c
Go to the documentation of this file.
1
25
31
32#include "config.h"
33#include <stdbool.h>
34#include <stdio.h>
35#include <string.h>
36#include <strings.h>
37#include "mutt/lib.h"
38#include "config/lib.h"
39#include "core/lib.h"
40#include "gui/lib.h"
41#include "lib.h"
42#include "editor/lib.h"
43#include "index/lib.h"
44#include "key/lib.h"
45#include "menu/lib.h"
46#include "compapi.h"
47#include "data.h"
48
54void matches_ensure_morespace(struct CompletionData *cd, int new_size)
55{
56 if (new_size <= (cd->match_list_len - 2))
57 return;
58
59 new_size = ROUND_UP(new_size + 2, 512);
60
61 MUTT_MEM_REALLOC(&cd->match_list, new_size, const char *);
62 memset(&cd->match_list[cd->match_list_len], 0,
63 (new_size - cd->match_list_len) * sizeof(const char *));
64
65 cd->match_list_len = new_size;
66}
67
79bool candidate(struct CompletionData *cd, char *user, const char *src, char *dest, size_t dlen)
80{
81 if (!dest || !user || !src)
82 return false;
83
84 if (strstr(src, user) != src)
85 return false;
86
88 cd->match_list[cd->num_matched++] = src;
89 if (dest[0] == '\0')
90 {
91 mutt_str_copy(dest, src, dlen);
92 }
93 else
94 {
95 int l;
96 for (l = 0; (src[l] != '\0') && (src[l] == dest[l]); l++)
97 ; // do nothing
98
99 dest[l] = '\0';
100 }
101 return true;
102}
103
107static int complete_sort_strings(const void *a, const void *b, void *sdata)
108{
109 const char *pa = *(const char **) a;
110 const char *pb = *(const char **) b;
111
112 return mutt_str_cmp(pa, pb);
113}
114
125int mutt_command_complete(struct CompletionData *cd, struct Buffer *buf,
126 int pos, int numtabs, void *cdata)
127{
128 char *pt = buf->data;
129 int spaces; /* keep track of the number of leading spaces on the line */
130
131 SKIPWS(pt);
132 spaces = pt - buf->data;
133
134 pt = buf->data + pos - spaces;
135 while ((pt > buf->data) && !mutt_isspace(*pt))
136 pt--;
137
138 if (pt == buf->data) /* complete cmd */
139 {
140 /* first TAB. Collect all the matches */
141 if (numtabs == 1)
142 {
143 cd->num_matched = 0;
144 mutt_str_copy(cd->user_typed, pt, sizeof(cd->user_typed));
145 memset(cd->match_list, 0, cd->match_list_len * sizeof(const char *));
146 memset(cd->completed, 0, sizeof(cd->completed));
147
148 const struct Command **cp = NULL;
150 {
151 const struct Command *cmd = *cp;
152
153 // For synonyms, match against the synonym name but complete to the real command name
154 if ((cmd->flags & CF_SYNONYM) && cmd->help)
155 {
156 // Check if user-typed matches the beginning of the synonym name
157 if (strstr(cmd->name, cd->user_typed) == cmd->name)
158 {
159 // Use the real command name for completion
160 const char *real_name = cmd->help;
161
162 // Manually add the real command name to the match list
164 cd->match_list[cd->num_matched++] = real_name;
165
166 // Update the completion result
167 if (cd->completed[0] == '\0')
168 {
169 mutt_str_copy(cd->completed, real_name, sizeof(cd->completed));
170 }
171 else
172 {
173 int l;
174 for (l = 0; (real_name[l] != '\0') && (real_name[l] == cd->completed[l]); l++)
175 ; // do nothing
176 cd->completed[l] = '\0';
177 }
178 }
179 }
180 else
181 {
182 candidate(cd, cd->user_typed, cmd->name, cd->completed, sizeof(cd->completed));
183 }
184 }
185
187 cd->match_list[cd->num_matched++] = cd->user_typed;
188
189 /* All matches are stored. Longest non-ambiguous string is ""
190 * i.e. don't change 'buf'. Fake successful return this time */
191 if (cd->user_typed[0] == '\0')
192 return 1;
193 }
194
195 if ((cd->completed[0] == '\0') && (cd->user_typed[0] != '\0'))
196 return 0;
197
198 /* cd->num_matched will _always_ be at least 1 since the initial
199 * user-typed string is always stored */
200 if ((numtabs == 1) && (cd->num_matched == 2))
201 {
202 snprintf(cd->completed, sizeof(cd->completed), "%s", cd->match_list[0]);
203 }
204 else if ((numtabs > 1) && (cd->num_matched > 2))
205 {
206 /* cycle through all the matches */
207 snprintf(cd->completed, sizeof(cd->completed), "%s",
208 cd->match_list[(numtabs - 2) % cd->num_matched]);
209 }
210
211 /* return the completed command */
212 buf_strcpy(buf, cd->completed);
213 }
214 else if (buf_startswith(buf, "set") || buf_startswith(buf, "unset") ||
215 buf_startswith(buf, "reset") || buf_startswith(buf, "toggle"))
216 { /* complete variables */
217 static const char *const prefixes[] = { "no", "inv", "?", "&", 0 };
218
219 pt++;
220 /* loop through all the possible prefixes (no, inv, ...) */
221 if (buf_startswith(buf, "set"))
222 {
223 for (int num = 0; prefixes[num]; num++)
224 {
225 if (mutt_str_startswith(pt, prefixes[num]))
226 {
227 pt += mutt_str_len(prefixes[num]);
228 break;
229 }
230 }
231 }
232
233 /* first TAB. Collect all the matches */
234 if (numtabs == 1)
235 {
236 cd->num_matched = 0;
237 mutt_str_copy(cd->user_typed, pt, sizeof(cd->user_typed));
238 memset(cd->match_list, 0, cd->match_list_len * sizeof(const char *));
239 memset(cd->completed, 0, sizeof(cd->completed));
240
241 struct HashElemArray hea = get_elem_list(NeoMutt->sub->cs, GEL_ALL_CONFIG);
242 struct HashElem **hep = NULL;
243 ARRAY_FOREACH(hep, &hea)
244 {
245 candidate(cd, cd->user_typed, (*hep)->key.strkey, cd->completed,
246 sizeof(cd->completed));
247 }
248 ARRAY_FREE(&hea);
249
251 cd->match_list[cd->num_matched++] = cd->user_typed;
252
253 /* All matches are stored. Longest non-ambiguous string is ""
254 * i.e. don't change 'buf'. Fake successful return this time */
255 if (cd->user_typed[0] == '\0')
256 return 1;
257 }
258
259 if ((cd->completed[0] == 0) && cd->user_typed[0])
260 return 0;
261
262 /* cd->num_matched will _always_ be at least 1 since the initial
263 * user-typed string is always stored */
264 if ((numtabs == 1) && (cd->num_matched == 2))
265 {
266 snprintf(cd->completed, sizeof(cd->completed), "%s", cd->match_list[0]);
267 }
268 else if ((numtabs > 1) && (cd->num_matched > 2))
269 {
270 /* cycle through all the matches */
271 snprintf(cd->completed, sizeof(cd->completed), "%s",
272 cd->match_list[(numtabs - 2) % cd->num_matched]);
273 }
274
275 mutt_str_copy(pt, cd->completed, buf->data + buf->dsize - pt - spaces);
276 buf_fix_dptr(buf);
277 }
278 else if (buf_startswith(buf, "exec"))
279 {
280 pt++;
281 /* first TAB. Collect all the matches */
282 if (numtabs == 1)
283 {
284 cd->num_matched = 0;
285 mutt_str_copy(cd->user_typed, pt, sizeof(cd->user_typed));
286 memset(cd->match_list, 0, cd->match_list_len * sizeof(const char *));
287 memset(cd->completed, 0, sizeof(cd->completed));
288
289 enum MenuType mtype = MENU_GENERIC;
290 if (cdata)
291 {
292 struct FileCompletionData *fcd = cdata;
293 struct MuttWindow *win = fcd->win;
294 if (win && (win->type == WT_MENU) && win->wdata)
295 {
296 struct Menu *menu = win->wdata;
297 mtype = menu->md->id;
298 }
299 }
300 else
301 {
302 mtype = menu_get_current_type();
303 }
304
305 const struct MenuDefinition *md = menu_find(mtype);
306 struct StringArray fna = km_get_func_array(md);
307
309 const char **strp = NULL;
310 ARRAY_FOREACH(strp, &fna)
311 {
312 candidate(cd, cd->user_typed, *strp, cd->completed, sizeof(cd->completed));
313 }
314 ARRAY_FREE(&fna);
315
317 cd->match_list[cd->num_matched++] = cd->user_typed;
318
319 /* All matches are stored. Longest non-ambiguous string is ""
320 * i.e. don't change 'buf'. Fake successful return this time */
321 if (cd->user_typed[0] == '\0')
322 return 1;
323 }
324
325 if ((cd->completed[0] == '\0') && (cd->user_typed[0] != '\0'))
326 return 0;
327
328 /* cd->num_matched will _always_ be at least 1 since the initial
329 * user-typed string is always stored */
330 if ((numtabs == 1) && (cd->num_matched == 2))
331 {
332 snprintf(cd->completed, sizeof(cd->completed), "%s", cd->match_list[0]);
333 }
334 else if ((numtabs > 1) && (cd->num_matched > 2))
335 {
336 /* cycle through all the matches */
337 snprintf(cd->completed, sizeof(cd->completed), "%s",
338 cd->match_list[(numtabs - 2) % cd->num_matched]);
339 }
340
341 mutt_str_copy(pt, cd->completed, buf->data + buf->dsize - pt - spaces);
342 buf_fix_dptr(buf);
343 }
344 else
345 {
346 return 0;
347 }
348
349 return 1;
350}
351
355static int label_sort(const void *a, const void *b, void *sdata)
356{
357 return strcasecmp(*(const char **) a, *(const char **) b);
358}
359
368int mutt_label_complete(struct CompletionData *cd, struct Buffer *buf, int numtabs)
369{
370 char *pt = buf->data;
371
372 struct Mailbox *m_cur = get_current_mailbox();
373 if (!m_cur || !m_cur->label_hash)
374 return 0;
375
376 SKIPWS(pt);
377
378 /* first TAB. Collect all the matches */
379 if (numtabs == 1)
380 {
381 struct HashElem *he = NULL;
382 struct HashWalkState hws = { 0 };
383
384 cd->num_matched = 0;
385 mutt_str_copy(cd->user_typed, buf_string(buf), sizeof(cd->user_typed));
386 memset(cd->match_list, 0, cd->match_list_len * sizeof(const char *));
387 memset(cd->completed, 0, sizeof(cd->completed));
388 while ((he = mutt_hash_walk(m_cur->label_hash, &hws)))
389 candidate(cd, cd->user_typed, he->key.strkey, cd->completed, sizeof(cd->completed));
391 mutt_qsort_r(cd->match_list, cd->num_matched, sizeof(char *), label_sort, NULL);
392 cd->match_list[cd->num_matched++] = cd->user_typed;
393
394 /* All matches are stored. Longest non-ambiguous string is ""
395 * i.e. don't change 'buf'. Fake successful return this time */
396 if (cd->user_typed[0] == '\0')
397 return 1;
398 }
399
400 if ((cd->completed[0] == '\0') && (cd->user_typed[0] != '\0'))
401 return 0;
402
403 /* cd->num_matched will _always_ be at least 1 since the initial
404 * user-typed string is always stored */
405 if ((numtabs == 1) && (cd->num_matched == 2))
406 {
407 snprintf(cd->completed, sizeof(cd->completed), "%s", cd->match_list[0]);
408 }
409 else if ((numtabs > 1) && (cd->num_matched > 2))
410 {
411 /* cycle through all the matches */
412 snprintf(cd->completed, sizeof(cd->completed), "%s",
413 cd->match_list[(numtabs - 2) % cd->num_matched]);
414 }
415
416 /* return the completed label */
417 buf_strcpy(buf, cd->completed);
418
419 return 1;
420}
421
430int mutt_var_value_complete(struct CompletionData *cd, struct Buffer *buf, int pos)
431{
432 char *pt = buf->data;
433
434 if (pt[0] == '\0')
435 return 0;
436
437 SKIPWS(pt);
438 const int spaces = pt - buf->data;
439
440 pt = buf->data + pos - spaces;
441 while ((pt > buf->data) && !mutt_isspace(*pt))
442 pt--;
443 pt++; /* move past the space */
444 if (*pt == '=') /* abort if no var before the '=' */
445 return 0;
446
447 if (buf_startswith(buf, "set"))
448 {
449 char var[256] = { 0 };
450 mutt_str_copy(var, pt, sizeof(var));
451 /* ignore the trailing '=' when comparing */
452 int vlen = mutt_str_len(var);
453 if (vlen == 0)
454 return 0;
455
456 var[vlen - 1] = '\0';
457
458 struct HashElem *he = cs_subset_lookup(NeoMutt->sub, var);
459 if (!he)
460 return 0; /* no such variable. */
461
462 struct Buffer *value = buf_pool_get();
463 struct Buffer *pretty = buf_pool_get();
464 int rc = cs_subset_he_string_get(NeoMutt->sub, he, value);
465 if (CSR_RESULT(rc) == CSR_SUCCESS)
466 {
467 pretty_var(value->data, pretty);
468 snprintf(pt, buf->dsize - (pt - buf->data), "%s=%s", var, pretty->data);
469 buf_pool_release(&value);
470 buf_pool_release(&pretty);
471 return 0;
472 }
473 buf_pool_release(&value);
474 buf_pool_release(&pretty);
475 return 1;
476 }
477 return 0;
478}
479
484{
485 if (!wdata || ((op != OP_EDITOR_COMPLETE) && (op != OP_EDITOR_COMPLETE_QUERY)))
486 return FR_NO_ACTION;
487
488 int rc = FR_SUCCESS;
489 buf_mb_wcstombs(wdata->buffer, wdata->state->wbuf, wdata->state->curpos);
490 size_t i = buf_len(wdata->buffer);
491 if ((i != 0) && (buf_at(wdata->buffer, i - 1) == '=') &&
492 (mutt_var_value_complete(wdata->cd, wdata->buffer, i) != 0))
493 {
494 wdata->tabs = 0;
495 }
496 else if (mutt_command_complete(wdata->cd, wdata->buffer, i, wdata->tabs, wdata->cdata) == 0)
497 {
498 rc = FR_ERROR;
499 }
500
501 replace_part(wdata->state, 0, buf_string(wdata->buffer));
502 return rc;
503}
504
509{
510 if (!wdata || ((op != OP_EDITOR_COMPLETE) && (op != OP_EDITOR_COMPLETE_QUERY)))
511 return FR_NO_ACTION;
512
513 size_t i;
514 for (i = wdata->state->curpos; (i > 0) && (wdata->state->wbuf[i - 1] != ',') &&
515 (wdata->state->wbuf[i - 1] != ':');
516 i--)
517 {
518 }
519 for (; (i < wdata->state->lastchar) && (wdata->state->wbuf[i] == ' '); i++)
520 ; // do nothing
521
522 buf_mb_wcstombs(wdata->buffer, wdata->state->wbuf + i, wdata->state->curpos - i);
523 int rc = mutt_label_complete(wdata->cd, wdata->buffer, wdata->tabs);
524 replace_part(wdata->state, i, buf_string(wdata->buffer));
525 if (rc != 1)
526 return FR_CONTINUE;
527
528 return FR_SUCCESS;
529}
530
535 .complete = complete_command,
536};
537
542 .complete = complete_label,
543};
#define ARRAY_SORT(head, fn, sdata)
Sort an array.
Definition array.h:373
#define ARRAY_FOREACH(elem, head)
Iterate over all elements of the array.
Definition array.h:223
#define ARRAY_FREE(head)
Release all memory.
Definition array.h:209
size_t buf_len(const struct Buffer *buf)
Calculate the length of a Buffer.
Definition buffer.c:491
void buf_fix_dptr(struct Buffer *buf)
Move the dptr to end of the Buffer.
Definition buffer.c:182
char buf_at(const struct Buffer *buf, size_t offset)
Return the character at the given offset.
Definition buffer.c:668
size_t buf_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition buffer.c:395
size_t buf_startswith(const struct Buffer *buf, const char *prefix)
Check whether a buffer starts with a prefix.
Definition buffer.c:707
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition buffer.h:96
#define CF_SYNONYM
Command is a synonym for another command.
Definition command.h:49
API Auto-Completion.
int mutt_var_value_complete(struct CompletionData *cd, struct Buffer *buf, int pos)
Complete a variable/value.
Definition helpers.c:430
void matches_ensure_morespace(struct CompletionData *cd, int new_size)
Allocate more space for auto-completion.
Definition helpers.c:54
int mutt_label_complete(struct CompletionData *cd, struct Buffer *buf, int numtabs)
Complete a label name.
Definition helpers.c:368
const struct CompleteOps CompleteLabelOps
Auto-Completion of Labels.
Definition helpers.c:541
const struct CompleteOps CompleteCommandOps
Auto-Completion of Commands.
Definition helpers.c:534
int mutt_command_complete(struct CompletionData *cd, struct Buffer *buf, int pos, int numtabs, void *cdata)
Complete a command name.
Definition helpers.c:125
bool candidate(struct CompletionData *cd, char *user, const char *src, char *dest, size_t dlen)
Helper function for completion.
Definition helpers.c:79
Auto-completion.
size_t pretty_var(const char *str, struct Buffer *buf)
Escape and stringify a config item value.
Definition dump.c:87
Convenience wrapper for the config headers.
#define CSR_RESULT(x)
Extract the result code from CSR_* flags.
Definition set.h:53
#define CSR_SUCCESS
Action completed successfully.
Definition set.h:33
Convenience wrapper for the core headers.
bool mutt_isspace(int arg)
Wrapper for isspace(3)
Definition ctype.c:96
String auto-completion data.
FunctionRetval
Possible return values for NeoMutt functions.
Definition dispatcher.h:33
@ FR_SUCCESS
Valid function - successfully performed.
Definition dispatcher.h:40
@ FR_ERROR
Valid function - error occurred.
Definition dispatcher.h:39
@ FR_CONTINUE
Remain in the Dialog.
Definition dispatcher.h:35
@ FR_NO_ACTION
Valid function - no action performed.
Definition dispatcher.h:38
void replace_part(struct EnterState *es, size_t from, const char *buf)
Search and replace on a buffer.
Definition functions.c:149
Edit a string.
enum FunctionRetval complete_label(struct EnterWindowData *wdata, int op)
Complete a label - Implements CompleteOps::complete() -.
Definition helpers.c:508
enum FunctionRetval complete_command(struct EnterWindowData *wdata, int op)
Complete a NeoMutt Command - Implements CompleteOps::complete() -.
Definition helpers.c:483
static int complete_sort_strings(const void *a, const void *b, void *sdata)
Compare two strings - Implements sort_t -.
Definition helpers.c:107
static int label_sort(const void *a, const void *b, void *sdata)
Compare two label strings - Implements sort_t -.
Definition helpers.c:355
Convenience wrapper for the gui headers.
struct HashElem * mutt_hash_walk(const struct HashTable *table, struct HashWalkState *state)
Iterate through all the HashElem's in a Hash Table.
Definition hash.c:491
GUI manage the main index (list of emails)
struct Mailbox * get_current_mailbox(void)
Get the current Mailbox.
Definition index.c:726
struct StringArray km_get_func_array(const struct MenuDefinition *md)
Get array of function names for a Menu.
Definition dump.c:477
Manage keymappings.
struct MenuDefinition * menu_find(int menu)
Find a Menu Definition by Menu type.
Definition menu.c:231
void buf_mb_wcstombs(struct Buffer *dest, const wchar_t *wstr, size_t wlen)
Convert a string from wide to multibyte characters.
Definition mbyte.c:257
#define ROUND_UP(NUM, STEP)
Round up NUM to the nearest multiple of STEP.
Definition memory.h:46
#define MUTT_MEM_REALLOC(pptr, n, type)
Definition memory.h:55
GUI present the user with a selectable list.
enum MenuType menu_get_current_type(void)
Get the type of the current Window.
Definition menu.c:82
Convenience wrapper for the library headers.
int mutt_str_cmp(const char *a, const char *b)
Compare two strings, safely.
Definition string.c:403
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition string.c:234
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition string.c:503
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition string.c:586
@ WT_MENU
An Window containing a Menu.
Definition mutt_window.h:97
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition pool.c:91
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition pool.c:111
void mutt_qsort_r(void *base, size_t nmemb, size_t size, sort_t compar, void *sdata)
Sort an array, where the comparator has access to opaque data rather than requiring global variables.
Definition qsort_r.c:72
#define SKIPWS(ch)
Definition string2.h:52
String manipulation buffer.
Definition buffer.h:36
size_t dsize
Length of data.
Definition buffer.h:39
char * data
Pointer to data.
Definition buffer.h:37
CommandFlags flags
Command flags, e.g. CF_SYNONYM.
Definition command.h:184
const char * help
One-line description of the Command.
Definition command.h:180
const char * name
Name of the Command.
Definition command.h:159
State data for auto-completion.
Definition data.h:32
int match_list_len
Enough space for all of the config items.
Definition data.h:37
char user_typed[1024]
Initial string that starts completion.
Definition data.h:33
char completed[256]
Completed string (command or variable)
Definition data.h:35
int num_matched
Number of matches for completion.
Definition data.h:34
const char ** match_list
Matching strings.
Definition data.h:36
struct ConfigSet * cs
Parent ConfigSet.
Definition subset.h:50
size_t curpos
Position of the cursor.
Definition state.h:36
wchar_t * wbuf
Buffer for the string being entered.
Definition state.h:33
size_t lastchar
Position of the last character.
Definition state.h:35
Data to fill the Enter Window.
Definition wdata.h:51
int tabs
Number of times the user has hit tab.
Definition wdata.h:68
void * cdata
Auto-Completion private data.
Definition wdata.h:58
struct CompletionData * cd
Auto-completion state data.
Definition wdata.h:72
struct Buffer * buffer
struct Buffer for the result
Definition wdata.h:53
struct EnterState * state
Current state of text entry.
Definition wdata.h:55
Input for the file completion function.
Definition curs_lib.h:39
struct MuttWindow * win
Current Focused Window.
Definition curs_lib.h:44
The item stored in a Hash Table.
Definition hash.h:44
union HashKey key
Key representing the data.
Definition hash.h:46
Cursor to iterate through a Hash Table.
Definition hash.h:134
A mailbox.
Definition mailbox.h:78
struct HashTable * label_hash
Hash Table: "X-Label" -> Email.
Definition mailbox.h:124
Functions for a Dialog or Window.
Definition menu.h:77
int id
Menu ID, e.g. MENU_ALIAS.
Definition menu.h:78
Definition lib.h:80
struct MuttWindow * win
Window holding the Menu.
Definition lib.h:88
const struct MenuDefinition * md
Menu definition for keymap entries.
Definition lib.h:84
void * wdata
Private data.
enum WindowType type
Window type, e.g. WT_SIDEBAR.
Container for Accounts, Notifications.
Definition neomutt.h:41
struct CommandArray commands
NeoMutt commands.
Definition neomutt.h:53
struct ConfigSubset * sub
Inherited config items.
Definition neomutt.h:49
int cs_subset_he_string_get(const struct ConfigSubset *sub, struct HashElem *he, struct Buffer *result)
Get a config item as a string.
Definition subset.c:338
struct HashElemArray get_elem_list(struct ConfigSet *cs, enum GetElemListFlags flags)
Create a sorted list of all config items.
Definition subset.c:81
struct HashElem * cs_subset_lookup(const struct ConfigSubset *sub, const char *name)
Find an inherited config item.
Definition subset.c:193
@ GEL_ALL_CONFIG
All the normal config (no synonyms or deprecated)
Definition subset.h:81
MenuType
Types of GUI selections.
Definition type.h:33
@ MENU_GENERIC
Generic selection list.
Definition type.h:43
const char * strkey
String key.
Definition hash.h:36