NeoMutt  2025-09-05-7-geaa2bd
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
helpers.c
Go to the documentation of this file.
1
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
111int mutt_command_complete(struct CompletionData *cd, struct Buffer *buf, int pos, int numtabs)
112{
113 char *pt = buf->data;
114 int spaces; /* keep track of the number of leading spaces on the line */
115
116 SKIPWS(pt);
117 spaces = pt - buf->data;
118
119 pt = buf->data + pos - spaces;
120 while ((pt > buf->data) && !mutt_isspace(*pt))
121 pt--;
122
123 if (pt == buf->data) /* complete cmd */
124 {
125 /* first TAB. Collect all the matches */
126 if (numtabs == 1)
127 {
128 cd->num_matched = 0;
129 mutt_str_copy(cd->user_typed, pt, sizeof(cd->user_typed));
130 memset(cd->match_list, 0, cd->match_list_len);
131 memset(cd->completed, 0, sizeof(cd->completed));
132
133 const struct Command **cp = NULL;
135 {
136 const struct Command *cmd = *cp;
137
138 candidate(cd, cd->user_typed, cmd->name, cd->completed, sizeof(cd->completed));
139 }
140
142 cd->match_list[cd->num_matched++] = cd->user_typed;
143
144 /* All matches are stored. Longest non-ambiguous string is ""
145 * i.e. don't change 'buf'. Fake successful return this time */
146 if (cd->user_typed[0] == '\0')
147 return 1;
148 }
149
150 if ((cd->completed[0] == '\0') && (cd->user_typed[0] != '\0'))
151 return 0;
152
153 /* cd->num_matched will _always_ be at least 1 since the initial
154 * user-typed string is always stored */
155 if ((numtabs == 1) && (cd->num_matched == 2))
156 {
157 snprintf(cd->completed, sizeof(cd->completed), "%s", cd->match_list[0]);
158 }
159 else if ((numtabs > 1) && (cd->num_matched > 2))
160 {
161 /* cycle through all the matches */
162 snprintf(cd->completed, sizeof(cd->completed), "%s",
163 cd->match_list[(numtabs - 2) % cd->num_matched]);
164 }
165
166 /* return the completed command */
167 buf_strcpy(buf, cd->completed);
168 }
169 else if (buf_startswith(buf, "set") || buf_startswith(buf, "unset") ||
170 buf_startswith(buf, "reset") || buf_startswith(buf, "toggle"))
171 { /* complete variables */
172 static const char *const prefixes[] = { "no", "inv", "?", "&", 0 };
173
174 pt++;
175 /* loop through all the possible prefixes (no, inv, ...) */
176 if (buf_startswith(buf, "set"))
177 {
178 for (int num = 0; prefixes[num]; num++)
179 {
180 if (mutt_str_startswith(pt, prefixes[num]))
181 {
182 pt += mutt_str_len(prefixes[num]);
183 break;
184 }
185 }
186 }
187
188 /* first TAB. Collect all the matches */
189 if (numtabs == 1)
190 {
191 cd->num_matched = 0;
192 mutt_str_copy(cd->user_typed, pt, sizeof(cd->user_typed));
193 memset(cd->match_list, 0, cd->match_list_len);
194 memset(cd->completed, 0, sizeof(cd->completed));
195
196 struct HashElemArray hea = get_elem_list(NeoMutt->sub->cs, GEL_ALL_CONFIG);
197 struct HashElem **hep = NULL;
198 ARRAY_FOREACH(hep, &hea)
199 {
200 struct HashElem *he = *hep;
201 const int type = CONFIG_TYPE(he->type);
202
204 continue;
205
206 candidate(cd, cd->user_typed, he->key.strkey, cd->completed, sizeof(cd->completed));
207 }
208 ARRAY_FREE(&hea);
209
211 cd->match_list[cd->num_matched++] = cd->user_typed;
212
213 /* All matches are stored. Longest non-ambiguous string is ""
214 * i.e. don't change 'buf'. Fake successful return this time */
215 if (cd->user_typed[0] == '\0')
216 return 1;
217 }
218
219 if ((cd->completed[0] == 0) && cd->user_typed[0])
220 return 0;
221
222 /* cd->num_matched will _always_ be at least 1 since the initial
223 * user-typed string is always stored */
224 if ((numtabs == 1) && (cd->num_matched == 2))
225 {
226 snprintf(cd->completed, sizeof(cd->completed), "%s", cd->match_list[0]);
227 }
228 else if ((numtabs > 1) && (cd->num_matched > 2))
229 {
230 /* cycle through all the matches */
231 snprintf(cd->completed, sizeof(cd->completed), "%s",
232 cd->match_list[(numtabs - 2) % cd->num_matched]);
233 }
234
235 strncpy(pt, cd->completed, buf->data + buf->dsize - pt - spaces);
236 buf_fix_dptr(buf);
237 }
238 else if (buf_startswith(buf, "exec"))
239 {
240 const enum MenuType mtype = menu_get_current_type();
241 const struct MenuFuncOp *funcs = km_get_table(mtype);
242 if (!funcs && (mtype != MENU_PAGER))
243 funcs = OpGeneric;
244
245 pt++;
246 /* first TAB. Collect all the matches */
247 if (numtabs == 1)
248 {
249 cd->num_matched = 0;
250 mutt_str_copy(cd->user_typed, pt, sizeof(cd->user_typed));
251 memset(cd->match_list, 0, cd->match_list_len);
252 memset(cd->completed, 0, sizeof(cd->completed));
253 for (int num = 0; funcs[num].name; num++)
254 candidate(cd, cd->user_typed, funcs[num].name, cd->completed, sizeof(cd->completed));
255 /* try the generic menu */
256 if ((mtype != MENU_PAGER) && (mtype != MENU_GENERIC))
257 {
258 funcs = OpGeneric;
259 for (int num = 0; funcs[num].name; num++)
260 candidate(cd, cd->user_typed, funcs[num].name, cd->completed,
261 sizeof(cd->completed));
262 }
264 cd->match_list[cd->num_matched++] = cd->user_typed;
265
266 /* All matches are stored. Longest non-ambiguous string is ""
267 * i.e. don't change 'buf'. Fake successful return this time */
268 if (cd->user_typed[0] == '\0')
269 return 1;
270 }
271
272 if ((cd->completed[0] == '\0') && (cd->user_typed[0] != '\0'))
273 return 0;
274
275 /* cd->num_matched will _always_ be at least 1 since the initial
276 * user-typed string is always stored */
277 if ((numtabs == 1) && (cd->num_matched == 2))
278 {
279 snprintf(cd->completed, sizeof(cd->completed), "%s", cd->match_list[0]);
280 }
281 else if ((numtabs > 1) && (cd->num_matched > 2))
282 {
283 /* cycle through all the matches */
284 snprintf(cd->completed, sizeof(cd->completed), "%s",
285 cd->match_list[(numtabs - 2) % cd->num_matched]);
286 }
287
288 strncpy(pt, cd->completed, buf->data + buf->dsize - pt - spaces);
289 buf_fix_dptr(buf);
290 }
291 else
292 {
293 return 0;
294 }
295
296 return 1;
297}
298
302static int label_sort(const void *a, const void *b, void *sdata)
303{
304 return strcasecmp(*(const char **) a, *(const char **) b);
305}
306
315int mutt_label_complete(struct CompletionData *cd, struct Buffer *buf, int numtabs)
316{
317 char *pt = buf->data;
318
319 struct Mailbox *m_cur = get_current_mailbox();
320 if (!m_cur || !m_cur->label_hash)
321 return 0;
322
323 SKIPWS(pt);
324
325 /* first TAB. Collect all the matches */
326 if (numtabs == 1)
327 {
328 struct HashElem *he = NULL;
329 struct HashWalkState hws = { 0 };
330
331 cd->num_matched = 0;
332 mutt_str_copy(cd->user_typed, buf_string(buf), sizeof(cd->user_typed));
333 memset(cd->match_list, 0, cd->match_list_len);
334 memset(cd->completed, 0, sizeof(cd->completed));
335 while ((he = mutt_hash_walk(m_cur->label_hash, &hws)))
336 candidate(cd, cd->user_typed, he->key.strkey, cd->completed, sizeof(cd->completed));
338 mutt_qsort_r(cd->match_list, cd->num_matched, sizeof(char *), label_sort, NULL);
339 cd->match_list[cd->num_matched++] = cd->user_typed;
340
341 /* All matches are stored. Longest non-ambiguous string is ""
342 * i.e. don't change 'buf'. Fake successful return this time */
343 if (cd->user_typed[0] == '\0')
344 return 1;
345 }
346
347 if ((cd->completed[0] == '\0') && (cd->user_typed[0] != '\0'))
348 return 0;
349
350 /* cd->num_matched will _always_ be at least 1 since the initial
351 * user-typed string is always stored */
352 if ((numtabs == 1) && (cd->num_matched == 2))
353 {
354 snprintf(cd->completed, sizeof(cd->completed), "%s", cd->match_list[0]);
355 }
356 else if ((numtabs > 1) && (cd->num_matched > 2))
357 {
358 /* cycle through all the matches */
359 snprintf(cd->completed, sizeof(cd->completed), "%s",
360 cd->match_list[(numtabs - 2) % cd->num_matched]);
361 }
362
363 /* return the completed label */
364 buf_strcpy(buf, cd->completed);
365
366 return 1;
367}
368
377int mutt_var_value_complete(struct CompletionData *cd, struct Buffer *buf, int pos)
378{
379 char *pt = buf->data;
380
381 if (pt[0] == '\0')
382 return 0;
383
384 SKIPWS(pt);
385 const int spaces = pt - buf->data;
386
387 pt = buf->data + pos - spaces;
388 while ((pt > buf->data) && !mutt_isspace(*pt))
389 pt--;
390 pt++; /* move past the space */
391 if (*pt == '=') /* abort if no var before the '=' */
392 return 0;
393
394 if (buf_startswith(buf, "set"))
395 {
396 char var[256] = { 0 };
397 mutt_str_copy(var, pt, sizeof(var));
398 /* ignore the trailing '=' when comparing */
399 int vlen = mutt_str_len(var);
400 if (vlen == 0)
401 return 0;
402
403 var[vlen - 1] = '\0';
404
405 struct HashElem *he = cs_subset_lookup(NeoMutt->sub, var);
406 if (!he)
407 return 0; /* no such variable. */
408
409 struct Buffer *value = buf_pool_get();
410 struct Buffer *pretty = buf_pool_get();
411 int rc = cs_subset_he_string_get(NeoMutt->sub, he, value);
412 if (CSR_RESULT(rc) == CSR_SUCCESS)
413 {
414 pretty_var(value->data, pretty);
415 snprintf(pt, buf->dsize - (pt - buf->data), "%s=%s", var, pretty->data);
416 buf_pool_release(&value);
417 buf_pool_release(&pretty);
418 return 0;
419 }
420 buf_pool_release(&value);
421 buf_pool_release(&pretty);
422 return 1;
423 }
424 return 0;
425}
426
431{
432 if (!wdata || ((op != OP_EDITOR_COMPLETE) && (op != OP_EDITOR_COMPLETE_QUERY)))
433 return FR_NO_ACTION;
434
435 int rc = FR_SUCCESS;
436 buf_mb_wcstombs(wdata->buffer, wdata->state->wbuf, wdata->state->curpos);
437 size_t i = buf_len(wdata->buffer);
438 if ((i != 0) && (buf_at(wdata->buffer, i - 1) == '=') &&
439 (mutt_var_value_complete(wdata->cd, wdata->buffer, i) != 0))
440 {
441 wdata->tabs = 0;
442 }
443 else if (mutt_command_complete(wdata->cd, wdata->buffer, i, wdata->tabs) == 0)
444 {
445 rc = FR_ERROR;
446 }
447
448 replace_part(wdata->state, 0, buf_string(wdata->buffer));
449 return rc;
450}
451
456{
457 if (!wdata || ((op != OP_EDITOR_COMPLETE) && (op != OP_EDITOR_COMPLETE_QUERY)))
458 return FR_NO_ACTION;
459
460 size_t i;
461 for (i = wdata->state->curpos; (i > 0) && (wdata->state->wbuf[i - 1] != ',') &&
462 (wdata->state->wbuf[i - 1] != ':');
463 i--)
464 {
465 }
466 for (; (i < wdata->state->lastchar) && (wdata->state->wbuf[i] == ' '); i++)
467 ; // do nothing
468
469 buf_mb_wcstombs(wdata->buffer, wdata->state->wbuf + i, wdata->state->curpos - i);
470 int rc = mutt_label_complete(wdata->cd, wdata->buffer, wdata->tabs);
471 replace_part(wdata->state, i, buf_string(wdata->buffer));
472 if (rc != 1)
473 return FR_CONTINUE;
474
475 return FR_SUCCESS;
476}
477
483};
484
490};
#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:377
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:315
int mutt_command_complete(struct CompletionData *cd, struct Buffer *buf, int pos, int numtabs)
Complete a command name.
Definition: helpers.c:111
const struct CompleteOps CompleteLabelOps
Auto-Completion of Labels.
Definition: helpers.c:488
const struct CompleteOps CompleteCommandOps
Auto-Completion of Commands.
Definition: helpers.c:481
bool candidate(struct CompletionData *cd, char *user, const char *src, char *dest, size_t dlen)
Helper function for completion.
Definition: helpers.c:77
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:455
enum FunctionRetval complete_command(struct EnterWindowData *wdata, int op)
Complete a NeoMutt Command - Implements CompleteOps::complete() -.
Definition: helpers.c:430
static int label_sort(const void *a, const void *b, void *sdata)
Compare two label strings - Implements sort_t -.
Definition: helpers.c:302
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:566
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:231
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:497
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:580
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
Key value store.
#define SKIPWS(ch)
Definition: string2.h:44
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
enum FunctionRetval(* complete)(struct EnterWindowData *wdata, int op)
Definition: compapi.h:46
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
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
The item stored in a Hash Table.
Definition: hash.h:44
union HashKey key
Key representing the data.
Definition: hash.h:46
int type
Type of data stored in Hash Table, e.g. DT_STRING.
Definition: hash.h:45
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:112
const char * name
Name of the function.
Definition: lib.h:113
Container for Accounts, Notifications.
Definition: neomutt.h:43
struct CommandArray commands
NeoMutt commands.
Definition: neomutt.h:51
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:47
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:334
struct HashElemArray get_elem_list(struct ConfigSet *cs, enum GetElemListFlags flags)
Create a sorted list of all config items.
Definition: subset.c:80
struct HashElem * cs_subset_lookup(const struct ConfigSubset *sub, const char *name)
Find an inherited config item.
Definition: subset.c:189
@ GEL_ALL_CONFIG
All the normal config (no synonyms or deprecated)
Definition: subset.h:81
MenuType
Types of GUI selections.
Definition: type.h:36
@ MENU_GENERIC
Generic selection list.
Definition: type.h:46
@ MENU_PAGER
Pager pager (email viewer)
Definition: type.h:48
#define CONFIG_TYPE(t)
Definition: types.h:49
#define D_INTERNAL_DEPRECATED
Config item shouldn't be used any more.
Definition: types.h:87
@ DT_SYNONYM
synonym for another variable
Definition: types.h:45
const char * strkey
String key.
Definition: hash.h:36