NeoMutt  2025-09-05-70-gcfdde0
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
helpers.c
Go to the documentation of this file.
1
24
30
31#include "config.h"
32#include <stdbool.h>
33#include <stdio.h>
34#include <string.h>
35#include <strings.h>
36#include "mutt/lib.h"
37#include "config/lib.h"
38#include "core/lib.h"
39#include "gui/lib.h"
40#include "lib.h"
41#include "editor/lib.h"
42#include "index/lib.h"
43#include "key/lib.h"
44#include "menu/lib.h"
45#include "compapi.h"
46#include "data.h"
47
53void matches_ensure_morespace(struct CompletionData *cd, int new_size)
54{
55 if (new_size <= (cd->match_list_len - 2))
56 return;
57
58 new_size = ROUND_UP(new_size + 2, 512);
59
60 MUTT_MEM_REALLOC(&cd->match_list, new_size, const char *);
61 memset(&cd->match_list[cd->match_list_len], 0, new_size - cd->match_list_len);
62
63 cd->match_list_len = new_size;
64}
65
77bool candidate(struct CompletionData *cd, char *user, const char *src, char *dest, size_t dlen)
78{
79 if (!dest || !user || !src)
80 return false;
81
82 if (strstr(src, user) != src)
83 return false;
84
86 cd->match_list[cd->num_matched++] = src;
87 if (dest[0] == '\0')
88 {
89 mutt_str_copy(dest, src, dlen);
90 }
91 else
92 {
93 int l;
94 for (l = 0; (src[l] != '\0') && (src[l] == dest[l]); l++)
95 ; // do nothing
96
97 dest[l] = '\0';
98 }
99 return true;
100}
101
112int mutt_command_complete(struct CompletionData *cd, struct Buffer *buf,
113 int pos, int numtabs, void *cdata)
114{
115 char *pt = buf->data;
116 int spaces; /* keep track of the number of leading spaces on the line */
117
118 SKIPWS(pt);
119 spaces = pt - buf->data;
120
121 pt = buf->data + pos - spaces;
122 while ((pt > buf->data) && !mutt_isspace(*pt))
123 pt--;
124
125 if (pt == buf->data) /* complete cmd */
126 {
127 /* first TAB. Collect all the matches */
128 if (numtabs == 1)
129 {
130 cd->num_matched = 0;
131 mutt_str_copy(cd->user_typed, pt, sizeof(cd->user_typed));
132 memset(cd->match_list, 0, cd->match_list_len);
133 memset(cd->completed, 0, sizeof(cd->completed));
134
135 const struct Command **cp = NULL;
137 {
138 const struct Command *cmd = *cp;
139
140 candidate(cd, cd->user_typed, cmd->name, cd->completed, sizeof(cd->completed));
141 }
142
144 cd->match_list[cd->num_matched++] = cd->user_typed;
145
146 /* All matches are stored. Longest non-ambiguous string is ""
147 * i.e. don't change 'buf'. Fake successful return this time */
148 if (cd->user_typed[0] == '\0')
149 return 1;
150 }
151
152 if ((cd->completed[0] == '\0') && (cd->user_typed[0] != '\0'))
153 return 0;
154
155 /* cd->num_matched will _always_ be at least 1 since the initial
156 * user-typed string is always stored */
157 if ((numtabs == 1) && (cd->num_matched == 2))
158 {
159 snprintf(cd->completed, sizeof(cd->completed), "%s", cd->match_list[0]);
160 }
161 else if ((numtabs > 1) && (cd->num_matched > 2))
162 {
163 /* cycle through all the matches */
164 snprintf(cd->completed, sizeof(cd->completed), "%s",
165 cd->match_list[(numtabs - 2) % cd->num_matched]);
166 }
167
168 /* return the completed command */
169 buf_strcpy(buf, cd->completed);
170 }
171 else if (buf_startswith(buf, "set") || buf_startswith(buf, "unset") ||
172 buf_startswith(buf, "reset") || buf_startswith(buf, "toggle"))
173 { /* complete variables */
174 static const char *const prefixes[] = { "no", "inv", "?", "&", 0 };
175
176 pt++;
177 /* loop through all the possible prefixes (no, inv, ...) */
178 if (buf_startswith(buf, "set"))
179 {
180 for (int num = 0; prefixes[num]; num++)
181 {
182 if (mutt_str_startswith(pt, prefixes[num]))
183 {
184 pt += mutt_str_len(prefixes[num]);
185 break;
186 }
187 }
188 }
189
190 /* first TAB. Collect all the matches */
191 if (numtabs == 1)
192 {
193 cd->num_matched = 0;
194 mutt_str_copy(cd->user_typed, pt, sizeof(cd->user_typed));
195 memset(cd->match_list, 0, cd->match_list_len);
196 memset(cd->completed, 0, sizeof(cd->completed));
197
198 struct HashElemArray hea = get_elem_list(NeoMutt->sub->cs, GEL_ALL_CONFIG);
199 struct HashElem **hep = NULL;
200 ARRAY_FOREACH(hep, &hea)
201 {
202 candidate(cd, cd->user_typed, (*hep)->key.strkey, cd->completed,
203 sizeof(cd->completed));
204 }
205 ARRAY_FREE(&hea);
206
208 cd->match_list[cd->num_matched++] = cd->user_typed;
209
210 /* All matches are stored. Longest non-ambiguous string is ""
211 * i.e. don't change 'buf'. Fake successful return this time */
212 if (cd->user_typed[0] == '\0')
213 return 1;
214 }
215
216 if ((cd->completed[0] == 0) && cd->user_typed[0])
217 return 0;
218
219 /* cd->num_matched will _always_ be at least 1 since the initial
220 * user-typed string is always stored */
221 if ((numtabs == 1) && (cd->num_matched == 2))
222 {
223 snprintf(cd->completed, sizeof(cd->completed), "%s", cd->match_list[0]);
224 }
225 else if ((numtabs > 1) && (cd->num_matched > 2))
226 {
227 /* cycle through all the matches */
228 snprintf(cd->completed, sizeof(cd->completed), "%s",
229 cd->match_list[(numtabs - 2) % cd->num_matched]);
230 }
231
232 strncpy(pt, cd->completed, buf->data + buf->dsize - pt - spaces);
233 buf_fix_dptr(buf);
234 }
235 else if (buf_startswith(buf, "exec"))
236 {
237 enum MenuType mtype = MENU_GENERIC;
238 if (cdata)
239 {
240 struct FileCompletionData *fcd = cdata;
241 struct MuttWindow *win = fcd->win;
242 if (win && win->wdata)
243 {
244 struct Menu *menu = win->wdata;
245 mtype = menu->type;
246 }
247 }
248 else
249 {
250 mtype = menu_get_current_type();
251 }
252 const struct MenuFuncOp *funcs = km_get_table(mtype);
253 if (!funcs && (mtype != MENU_PAGER))
254 funcs = OpGeneric;
255
256 pt++;
257 /* first TAB. Collect all the matches */
258 if (numtabs == 1)
259 {
260 cd->num_matched = 0;
261 mutt_str_copy(cd->user_typed, pt, sizeof(cd->user_typed));
262 memset(cd->match_list, 0, cd->match_list_len);
263 memset(cd->completed, 0, sizeof(cd->completed));
264 for (int num = 0; funcs[num].name; num++)
265 candidate(cd, cd->user_typed, funcs[num].name, cd->completed, sizeof(cd->completed));
266 /* try the generic menu */
267 if ((mtype != MENU_PAGER) && (mtype != MENU_GENERIC))
268 {
269 funcs = OpGeneric;
270 for (int num = 0; funcs[num].name; num++)
271 candidate(cd, cd->user_typed, funcs[num].name, cd->completed,
272 sizeof(cd->completed));
273 }
275 cd->match_list[cd->num_matched++] = cd->user_typed;
276
277 /* All matches are stored. Longest non-ambiguous string is ""
278 * i.e. don't change 'buf'. Fake successful return this time */
279 if (cd->user_typed[0] == '\0')
280 return 1;
281 }
282
283 if ((cd->completed[0] == '\0') && (cd->user_typed[0] != '\0'))
284 return 0;
285
286 /* cd->num_matched will _always_ be at least 1 since the initial
287 * user-typed string is always stored */
288 if ((numtabs == 1) && (cd->num_matched == 2))
289 {
290 snprintf(cd->completed, sizeof(cd->completed), "%s", cd->match_list[0]);
291 }
292 else if ((numtabs > 1) && (cd->num_matched > 2))
293 {
294 /* cycle through all the matches */
295 snprintf(cd->completed, sizeof(cd->completed), "%s",
296 cd->match_list[(numtabs - 2) % cd->num_matched]);
297 }
298
299 strncpy(pt, cd->completed, buf->data + buf->dsize - pt - spaces);
300 buf_fix_dptr(buf);
301 }
302 else
303 {
304 return 0;
305 }
306
307 return 1;
308}
309
313static int label_sort(const void *a, const void *b, void *sdata)
314{
315 return strcasecmp(*(const char **) a, *(const char **) b);
316}
317
326int mutt_label_complete(struct CompletionData *cd, struct Buffer *buf, int numtabs)
327{
328 char *pt = buf->data;
329
330 struct Mailbox *m_cur = get_current_mailbox();
331 if (!m_cur || !m_cur->label_hash)
332 return 0;
333
334 SKIPWS(pt);
335
336 /* first TAB. Collect all the matches */
337 if (numtabs == 1)
338 {
339 struct HashElem *he = NULL;
340 struct HashWalkState hws = { 0 };
341
342 cd->num_matched = 0;
343 mutt_str_copy(cd->user_typed, buf_string(buf), sizeof(cd->user_typed));
344 memset(cd->match_list, 0, cd->match_list_len);
345 memset(cd->completed, 0, sizeof(cd->completed));
346 while ((he = mutt_hash_walk(m_cur->label_hash, &hws)))
347 candidate(cd, cd->user_typed, he->key.strkey, cd->completed, sizeof(cd->completed));
349 mutt_qsort_r(cd->match_list, cd->num_matched, sizeof(char *), label_sort, NULL);
350 cd->match_list[cd->num_matched++] = cd->user_typed;
351
352 /* All matches are stored. Longest non-ambiguous string is ""
353 * i.e. don't change 'buf'. Fake successful return this time */
354 if (cd->user_typed[0] == '\0')
355 return 1;
356 }
357
358 if ((cd->completed[0] == '\0') && (cd->user_typed[0] != '\0'))
359 return 0;
360
361 /* cd->num_matched will _always_ be at least 1 since the initial
362 * user-typed string is always stored */
363 if ((numtabs == 1) && (cd->num_matched == 2))
364 {
365 snprintf(cd->completed, sizeof(cd->completed), "%s", cd->match_list[0]);
366 }
367 else if ((numtabs > 1) && (cd->num_matched > 2))
368 {
369 /* cycle through all the matches */
370 snprintf(cd->completed, sizeof(cd->completed), "%s",
371 cd->match_list[(numtabs - 2) % cd->num_matched]);
372 }
373
374 /* return the completed label */
375 buf_strcpy(buf, cd->completed);
376
377 return 1;
378}
379
388int mutt_var_value_complete(struct CompletionData *cd, struct Buffer *buf, int pos)
389{
390 char *pt = buf->data;
391
392 if (pt[0] == '\0')
393 return 0;
394
395 SKIPWS(pt);
396 const int spaces = pt - buf->data;
397
398 pt = buf->data + pos - spaces;
399 while ((pt > buf->data) && !mutt_isspace(*pt))
400 pt--;
401 pt++; /* move past the space */
402 if (*pt == '=') /* abort if no var before the '=' */
403 return 0;
404
405 if (buf_startswith(buf, "set"))
406 {
407 char var[256] = { 0 };
408 mutt_str_copy(var, pt, sizeof(var));
409 /* ignore the trailing '=' when comparing */
410 int vlen = mutt_str_len(var);
411 if (vlen == 0)
412 return 0;
413
414 var[vlen - 1] = '\0';
415
416 struct HashElem *he = cs_subset_lookup(NeoMutt->sub, var);
417 if (!he)
418 return 0; /* no such variable. */
419
420 struct Buffer *value = buf_pool_get();
421 struct Buffer *pretty = buf_pool_get();
422 int rc = cs_subset_he_string_get(NeoMutt->sub, he, value);
423 if (CSR_RESULT(rc) == CSR_SUCCESS)
424 {
425 pretty_var(value->data, pretty);
426 snprintf(pt, buf->dsize - (pt - buf->data), "%s=%s", var, pretty->data);
427 buf_pool_release(&value);
428 buf_pool_release(&pretty);
429 return 0;
430 }
431 buf_pool_release(&value);
432 buf_pool_release(&pretty);
433 return 1;
434 }
435 return 0;
436}
437
442{
443 if (!wdata || ((op != OP_EDITOR_COMPLETE) && (op != OP_EDITOR_COMPLETE_QUERY)))
444 return FR_NO_ACTION;
445
446 int rc = FR_SUCCESS;
447 buf_mb_wcstombs(wdata->buffer, wdata->state->wbuf, wdata->state->curpos);
448 size_t i = buf_len(wdata->buffer);
449 if ((i != 0) && (buf_at(wdata->buffer, i - 1) == '=') &&
450 (mutt_var_value_complete(wdata->cd, wdata->buffer, i) != 0))
451 {
452 wdata->tabs = 0;
453 }
454 else if (mutt_command_complete(wdata->cd, wdata->buffer, i, wdata->tabs, wdata->cdata) == 0)
455 {
456 rc = FR_ERROR;
457 }
458
459 replace_part(wdata->state, 0, buf_string(wdata->buffer));
460 return rc;
461}
462
467{
468 if (!wdata || ((op != OP_EDITOR_COMPLETE) && (op != OP_EDITOR_COMPLETE_QUERY)))
469 return FR_NO_ACTION;
470
471 size_t i;
472 for (i = wdata->state->curpos; (i > 0) && (wdata->state->wbuf[i - 1] != ',') &&
473 (wdata->state->wbuf[i - 1] != ':');
474 i--)
475 {
476 }
477 for (; (i < wdata->state->lastchar) && (wdata->state->wbuf[i] == ' '); i++)
478 ; // do nothing
479
480 buf_mb_wcstombs(wdata->buffer, wdata->state->wbuf + i, wdata->state->curpos - i);
481 int rc = mutt_label_complete(wdata->cd, wdata->buffer, wdata->tabs);
482 replace_part(wdata->state, i, buf_string(wdata->buffer));
483 if (rc != 1)
484 return FR_CONTINUE;
485
486 return FR_SUCCESS;
487}
488
493 .complete = complete_command,
494};
495
500 .complete = complete_label,
501};
#define ARRAY_FOREACH(elem, head)
Iterate over all elements of the array.
Definition array.h:214
#define ARRAY_FREE(head)
Release all memory.
Definition array.h:204
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
API Auto-Completion.
int mutt_var_value_complete(struct CompletionData *cd, struct Buffer *buf, int pos)
Complete a variable/value.
Definition helpers.c:388
void matches_ensure_morespace(struct CompletionData *cd, int new_size)
Allocate more space for auto-completion.
Definition helpers.c:53
int mutt_label_complete(struct CompletionData *cd, struct Buffer *buf, int numtabs)
Complete a label name.
Definition helpers.c:326
const struct CompleteOps CompleteLabelOps
Auto-Completion of Labels.
Definition helpers.c:499
const struct CompleteOps CompleteCommandOps
Auto-Completion of Commands.
Definition helpers.c:492
int mutt_command_complete(struct CompletionData *cd, struct Buffer *buf, int pos, int numtabs, void *cdata)
Complete a command name.
Definition helpers.c:112
bool candidate(struct CompletionData *cd, char *user, const char *src, char *dest, size_t dlen)
Helper function for completion.
Definition helpers.c:77
Auto-completion.
size_t pretty_var(const char *str, struct Buffer *buf)
Escape and stringify a config item value.
Definition dump.c:85
Convenience wrapper for the config headers.
#define CSR_RESULT(x)
Definition set.h:50
#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:95
String auto-completion data.
FunctionRetval
Possible return values for NeoMutt functions.
Definition dispatcher.h:32
@ FR_SUCCESS
Valid function - successfully performed.
Definition dispatcher.h:39
@ FR_ERROR
Valid function - error occurred.
Definition dispatcher.h:38
@ FR_CONTINUE
Remain in the Dialog.
Definition dispatcher.h:34
@ FR_NO_ACTION
Valid function - no action performed.
Definition dispatcher.h:37
void replace_part(struct EnterState *es, size_t from, const char *buf)
Search and replace on a buffer.
Definition functions.c:132
Edit a string.
enum FunctionRetval complete_label(struct EnterWindowData *wdata, int op)
Complete a label - Implements CompleteOps::complete() -.
Definition helpers.c:466
enum FunctionRetval complete_command(struct EnterWindowData *wdata, int op)
Complete a NeoMutt Command - Implements CompleteOps::complete() -.
Definition helpers.c:441
static int label_sort(const void *a, const void *b, void *sdata)
Compare two label strings - Implements sort_t -.
Definition helpers.c:313
const struct MenuFuncOp OpGeneric[]
Functions for the Generic Menu.
Definition functions.c:69
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:489
GUI manage the main index (list of emails)
struct Mailbox * get_current_mailbox(void)
Get the current Mailbox.
Definition index.c:721
const struct MenuFuncOp * km_get_table(enum MenuType mtype)
Lookup a Menu's functions.
Definition lib.c:482
Manage keymappings.
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:256
#define ROUND_UP(NUM, STEP)
Definition memory.h:41
#define MUTT_MEM_REALLOC(pptr, n, type)
Definition memory.h:50
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:89
Convenience wrapper for the library headers.
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition string.c:232
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition string.c:498
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:581
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition pool.c:82
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition pool.c:96
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:67
#define SKIPWS(ch)
Definition string2.h:51
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
const char * name
Name of the command.
Definition command.h:51
State data for auto-completion.
Definition data.h:33
int match_list_len
Enough space for all of the config items.
Definition data.h:38
char user_typed[1024]
Initial string that starts completion.
Definition data.h:34
char completed[256]
Completed string (command or variable)
Definition data.h:36
int num_matched
Number of matches for completion.
Definition data.h:35
const char ** match_list
Matching strings.
Definition data.h:37
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:46
int tabs
Number of times the user has hit tab.
Definition wdata.h:63
void * cdata
Auto-Completion private data.
Definition wdata.h:53
struct CompletionData * cd
Auto-completion state data.
Definition wdata.h:67
struct Buffer * buffer
struct Buffer for the result
Definition wdata.h:48
struct EnterState * state
Current state of text entry.
Definition wdata.h:50
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:79
struct HashTable * label_hash
Hash Table: "x-labels" -> Email.
Definition mailbox.h:125
Mapping between a function and an operation.
Definition lib.h:115
const char * name
Name of the function.
Definition lib.h:116
Definition lib.h:79
struct MuttWindow * win
Window holding the Menu.
Definition lib.h:86
enum MenuType type
Menu definition for keymap entries.
Definition lib.h:83
void * wdata
Private data.
Container for Accounts, Notifications.
Definition neomutt.h:42
struct CommandArray commands
NeoMutt commands.
Definition neomutt.h:50
struct ConfigSubset * sub
Inherited config items.
Definition neomutt.h:46
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:35
@ MENU_GENERIC
Generic selection list.
Definition type.h:45
@ MENU_PAGER
Pager pager (email viewer)
Definition type.h:47
const char * strkey
String key.
Definition hash.h:36