NeoMutt  2025-09-05-43-g177ed6
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
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 candidate(cd, cd->user_typed, (*hep)->key.strkey, cd->completed,
201 sizeof(cd->completed));
202 }
203 ARRAY_FREE(&hea);
204
206 cd->match_list[cd->num_matched++] = cd->user_typed;
207
208 /* All matches are stored. Longest non-ambiguous string is ""
209 * i.e. don't change 'buf'. Fake successful return this time */
210 if (cd->user_typed[0] == '\0')
211 return 1;
212 }
213
214 if ((cd->completed[0] == 0) && cd->user_typed[0])
215 return 0;
216
217 /* cd->num_matched will _always_ be at least 1 since the initial
218 * user-typed string is always stored */
219 if ((numtabs == 1) && (cd->num_matched == 2))
220 {
221 snprintf(cd->completed, sizeof(cd->completed), "%s", cd->match_list[0]);
222 }
223 else if ((numtabs > 1) && (cd->num_matched > 2))
224 {
225 /* cycle through all the matches */
226 snprintf(cd->completed, sizeof(cd->completed), "%s",
227 cd->match_list[(numtabs - 2) % cd->num_matched]);
228 }
229
230 strncpy(pt, cd->completed, buf->data + buf->dsize - pt - spaces);
231 buf_fix_dptr(buf);
232 }
233 else if (buf_startswith(buf, "exec"))
234 {
235 const enum MenuType mtype = menu_get_current_type();
236 const struct MenuFuncOp *funcs = km_get_table(mtype);
237 if (!funcs && (mtype != MENU_PAGER))
238 funcs = OpGeneric;
239
240 pt++;
241 /* first TAB. Collect all the matches */
242 if (numtabs == 1)
243 {
244 cd->num_matched = 0;
245 mutt_str_copy(cd->user_typed, pt, sizeof(cd->user_typed));
246 memset(cd->match_list, 0, cd->match_list_len);
247 memset(cd->completed, 0, sizeof(cd->completed));
248 for (int num = 0; funcs[num].name; num++)
249 candidate(cd, cd->user_typed, funcs[num].name, cd->completed, sizeof(cd->completed));
250 /* try the generic menu */
251 if ((mtype != MENU_PAGER) && (mtype != MENU_GENERIC))
252 {
253 funcs = OpGeneric;
254 for (int num = 0; funcs[num].name; num++)
255 candidate(cd, cd->user_typed, funcs[num].name, cd->completed,
256 sizeof(cd->completed));
257 }
259 cd->match_list[cd->num_matched++] = cd->user_typed;
260
261 /* All matches are stored. Longest non-ambiguous string is ""
262 * i.e. don't change 'buf'. Fake successful return this time */
263 if (cd->user_typed[0] == '\0')
264 return 1;
265 }
266
267 if ((cd->completed[0] == '\0') && (cd->user_typed[0] != '\0'))
268 return 0;
269
270 /* cd->num_matched will _always_ be at least 1 since the initial
271 * user-typed string is always stored */
272 if ((numtabs == 1) && (cd->num_matched == 2))
273 {
274 snprintf(cd->completed, sizeof(cd->completed), "%s", cd->match_list[0]);
275 }
276 else if ((numtabs > 1) && (cd->num_matched > 2))
277 {
278 /* cycle through all the matches */
279 snprintf(cd->completed, sizeof(cd->completed), "%s",
280 cd->match_list[(numtabs - 2) % cd->num_matched]);
281 }
282
283 strncpy(pt, cd->completed, buf->data + buf->dsize - pt - spaces);
284 buf_fix_dptr(buf);
285 }
286 else
287 {
288 return 0;
289 }
290
291 return 1;
292}
293
297static int label_sort(const void *a, const void *b, void *sdata)
298{
299 return strcasecmp(*(const char **) a, *(const char **) b);
300}
301
310int mutt_label_complete(struct CompletionData *cd, struct Buffer *buf, int numtabs)
311{
312 char *pt = buf->data;
313
314 struct Mailbox *m_cur = get_current_mailbox();
315 if (!m_cur || !m_cur->label_hash)
316 return 0;
317
318 SKIPWS(pt);
319
320 /* first TAB. Collect all the matches */
321 if (numtabs == 1)
322 {
323 struct HashElem *he = NULL;
324 struct HashWalkState hws = { 0 };
325
326 cd->num_matched = 0;
327 mutt_str_copy(cd->user_typed, buf_string(buf), sizeof(cd->user_typed));
328 memset(cd->match_list, 0, cd->match_list_len);
329 memset(cd->completed, 0, sizeof(cd->completed));
330 while ((he = mutt_hash_walk(m_cur->label_hash, &hws)))
331 candidate(cd, cd->user_typed, he->key.strkey, cd->completed, sizeof(cd->completed));
333 mutt_qsort_r(cd->match_list, cd->num_matched, sizeof(char *), label_sort, NULL);
334 cd->match_list[cd->num_matched++] = cd->user_typed;
335
336 /* All matches are stored. Longest non-ambiguous string is ""
337 * i.e. don't change 'buf'. Fake successful return this time */
338 if (cd->user_typed[0] == '\0')
339 return 1;
340 }
341
342 if ((cd->completed[0] == '\0') && (cd->user_typed[0] != '\0'))
343 return 0;
344
345 /* cd->num_matched will _always_ be at least 1 since the initial
346 * user-typed string is always stored */
347 if ((numtabs == 1) && (cd->num_matched == 2))
348 {
349 snprintf(cd->completed, sizeof(cd->completed), "%s", cd->match_list[0]);
350 }
351 else if ((numtabs > 1) && (cd->num_matched > 2))
352 {
353 /* cycle through all the matches */
354 snprintf(cd->completed, sizeof(cd->completed), "%s",
355 cd->match_list[(numtabs - 2) % cd->num_matched]);
356 }
357
358 /* return the completed label */
359 buf_strcpy(buf, cd->completed);
360
361 return 1;
362}
363
372int mutt_var_value_complete(struct CompletionData *cd, struct Buffer *buf, int pos)
373{
374 char *pt = buf->data;
375
376 if (pt[0] == '\0')
377 return 0;
378
379 SKIPWS(pt);
380 const int spaces = pt - buf->data;
381
382 pt = buf->data + pos - spaces;
383 while ((pt > buf->data) && !mutt_isspace(*pt))
384 pt--;
385 pt++; /* move past the space */
386 if (*pt == '=') /* abort if no var before the '=' */
387 return 0;
388
389 if (buf_startswith(buf, "set"))
390 {
391 char var[256] = { 0 };
392 mutt_str_copy(var, pt, sizeof(var));
393 /* ignore the trailing '=' when comparing */
394 int vlen = mutt_str_len(var);
395 if (vlen == 0)
396 return 0;
397
398 var[vlen - 1] = '\0';
399
400 struct HashElem *he = cs_subset_lookup(NeoMutt->sub, var);
401 if (!he)
402 return 0; /* no such variable. */
403
404 struct Buffer *value = buf_pool_get();
405 struct Buffer *pretty = buf_pool_get();
406 int rc = cs_subset_he_string_get(NeoMutt->sub, he, value);
407 if (CSR_RESULT(rc) == CSR_SUCCESS)
408 {
409 pretty_var(value->data, pretty);
410 snprintf(pt, buf->dsize - (pt - buf->data), "%s=%s", var, pretty->data);
411 buf_pool_release(&value);
412 buf_pool_release(&pretty);
413 return 0;
414 }
415 buf_pool_release(&value);
416 buf_pool_release(&pretty);
417 return 1;
418 }
419 return 0;
420}
421
426{
427 if (!wdata || ((op != OP_EDITOR_COMPLETE) && (op != OP_EDITOR_COMPLETE_QUERY)))
428 return FR_NO_ACTION;
429
430 int rc = FR_SUCCESS;
431 buf_mb_wcstombs(wdata->buffer, wdata->state->wbuf, wdata->state->curpos);
432 size_t i = buf_len(wdata->buffer);
433 if ((i != 0) && (buf_at(wdata->buffer, i - 1) == '=') &&
434 (mutt_var_value_complete(wdata->cd, wdata->buffer, i) != 0))
435 {
436 wdata->tabs = 0;
437 }
438 else if (mutt_command_complete(wdata->cd, wdata->buffer, i, wdata->tabs) == 0)
439 {
440 rc = FR_ERROR;
441 }
442
443 replace_part(wdata->state, 0, buf_string(wdata->buffer));
444 return rc;
445}
446
451{
452 if (!wdata || ((op != OP_EDITOR_COMPLETE) && (op != OP_EDITOR_COMPLETE_QUERY)))
453 return FR_NO_ACTION;
454
455 size_t i;
456 for (i = wdata->state->curpos; (i > 0) && (wdata->state->wbuf[i - 1] != ',') &&
457 (wdata->state->wbuf[i - 1] != ':');
458 i--)
459 {
460 }
461 for (; (i < wdata->state->lastchar) && (wdata->state->wbuf[i] == ' '); i++)
462 ; // do nothing
463
464 buf_mb_wcstombs(wdata->buffer, wdata->state->wbuf + i, wdata->state->curpos - i);
465 int rc = mutt_label_complete(wdata->cd, wdata->buffer, wdata->tabs);
466 replace_part(wdata->state, i, buf_string(wdata->buffer));
467 if (rc != 1)
468 return FR_CONTINUE;
469
470 return FR_SUCCESS;
471}
472
477 .complete = complete_command,
478};
479
484 .complete = complete_label,
485};
#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:372
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:310
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:483
const struct CompleteOps CompleteCommandOps
Auto-Completion of Commands.
Definition helpers.c:476
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:450
enum FunctionRetval complete_command(struct EnterWindowData *wdata, int op)
Complete a NeoMutt Command - Implements CompleteOps::complete() -.
Definition helpers.c:425
static int label_sort(const void *a, const void *b, void *sdata)
Compare two label strings - Implements sort_t -.
Definition helpers.c:297
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:571
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
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
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
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: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:36
@ MENU_GENERIC
Generic selection list.
Definition type.h:46
@ MENU_PAGER
Pager pager (email viewer)
Definition type.h:48
const char * strkey
String key.
Definition hash.h:36