NeoMutt  2025-01-09-117-gace867
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 <ctype.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, new_size - cd->match_list_len);
63
64 cd->match_list_len = new_size;
65}
66
78bool candidate(struct CompletionData *cd, char *user, const char *src, char *dest, size_t dlen)
79{
80 if (!dest || !user || !src)
81 return false;
82
83 if (strstr(src, user) != src)
84 return false;
85
87 cd->match_list[cd->num_matched++] = src;
88 if (dest[0] == '\0')
89 {
90 mutt_str_copy(dest, src, dlen);
91 }
92 else
93 {
94 int l;
95 for (l = 0; (src[l] != '\0') && (src[l] == dest[l]); l++)
96 ; // do nothing
97
98 dest[l] = '\0';
99 }
100 return true;
101}
102
112int mutt_command_complete(struct CompletionData *cd, struct Buffer *buf, int pos, int numtabs)
113{
114 char *pt = buf->data;
115 int spaces; /* keep track of the number of leading spaces on the line */
116
117 SKIPWS(pt);
118 spaces = pt - buf->data;
119
120 pt = buf->data + pos - spaces;
121 while ((pt > buf->data) && !isspace((unsigned char) *pt))
122 pt--;
123
124 if (pt == buf->data) /* complete cmd */
125 {
126 /* first TAB. Collect all the matches */
127 if (numtabs == 1)
128 {
129 cd->num_matched = 0;
130 mutt_str_copy(cd->user_typed, pt, sizeof(cd->user_typed));
131 memset(cd->match_list, 0, cd->match_list_len);
132 memset(cd->completed, 0, sizeof(cd->completed));
133
134 const struct Command **cp = NULL;
136 {
137 const struct Command *cmd = *cp;
138
139 candidate(cd, cd->user_typed, cmd->name, cd->completed, sizeof(cd->completed));
140 }
141
143 cd->match_list[cd->num_matched++] = cd->user_typed;
144
145 /* All matches are stored. Longest non-ambiguous string is ""
146 * i.e. don't change 'buf'. Fake successful return this time */
147 if (cd->user_typed[0] == '\0')
148 return 1;
149 }
150
151 if ((cd->completed[0] == '\0') && (cd->user_typed[0] != '\0'))
152 return 0;
153
154 /* cd->num_matched will _always_ be at least 1 since the initial
155 * user-typed string is always stored */
156 if ((numtabs == 1) && (cd->num_matched == 2))
157 {
158 snprintf(cd->completed, sizeof(cd->completed), "%s", cd->match_list[0]);
159 }
160 else if ((numtabs > 1) && (cd->num_matched > 2))
161 {
162 /* cycle through all the matches */
163 snprintf(cd->completed, sizeof(cd->completed), "%s",
164 cd->match_list[(numtabs - 2) % cd->num_matched]);
165 }
166
167 /* return the completed command */
168 buf_strcpy(buf, cd->completed);
169 }
170 else if (buf_startswith(buf, "set") || buf_startswith(buf, "unset") ||
171 buf_startswith(buf, "reset") || buf_startswith(buf, "toggle"))
172 { /* complete variables */
173 static const char *const prefixes[] = { "no", "inv", "?", "&", 0 };
174
175 pt++;
176 /* loop through all the possible prefixes (no, inv, ...) */
177 if (buf_startswith(buf, "set"))
178 {
179 for (int num = 0; prefixes[num]; num++)
180 {
181 if (mutt_str_startswith(pt, prefixes[num]))
182 {
183 pt += mutt_str_len(prefixes[num]);
184 break;
185 }
186 }
187 }
188
189 /* first TAB. Collect all the matches */
190 if (numtabs == 1)
191 {
192 cd->num_matched = 0;
193 mutt_str_copy(cd->user_typed, pt, sizeof(cd->user_typed));
194 memset(cd->match_list, 0, cd->match_list_len);
195 memset(cd->completed, 0, sizeof(cd->completed));
196
197 struct HashElemArray hea = get_elem_list(NeoMutt->sub->cs, GEL_ALL_CONFIG);
198 struct HashElem **hep = NULL;
199 ARRAY_FOREACH(hep, &hea)
200 {
201 struct HashElem *he = *hep;
202 const int type = CONFIG_TYPE(he->type);
203
205 continue;
206
207 candidate(cd, cd->user_typed, he->key.strkey, cd->completed, sizeof(cd->completed));
208 }
209 ARRAY_FREE(&hea);
210
212 cd->match_list[cd->num_matched++] = cd->user_typed;
213
214 /* All matches are stored. Longest non-ambiguous string is ""
215 * i.e. don't change 'buf'. Fake successful return this time */
216 if (cd->user_typed[0] == '\0')
217 return 1;
218 }
219
220 if ((cd->completed[0] == 0) && cd->user_typed[0])
221 return 0;
222
223 /* cd->num_matched will _always_ be at least 1 since the initial
224 * user-typed string is always stored */
225 if ((numtabs == 1) && (cd->num_matched == 2))
226 {
227 snprintf(cd->completed, sizeof(cd->completed), "%s", cd->match_list[0]);
228 }
229 else if ((numtabs > 1) && (cd->num_matched > 2))
230 {
231 /* cycle through all the matches */
232 snprintf(cd->completed, sizeof(cd->completed), "%s",
233 cd->match_list[(numtabs - 2) % cd->num_matched]);
234 }
235
236 strncpy(pt, cd->completed, buf->data + buf->dsize - pt - spaces);
237 buf_fix_dptr(buf);
238 }
239 else if (buf_startswith(buf, "exec"))
240 {
241 const enum MenuType mtype = menu_get_current_type();
242 const struct MenuFuncOp *funcs = km_get_table(mtype);
243 if (!funcs && (mtype != MENU_PAGER))
244 funcs = OpGeneric;
245
246 pt++;
247 /* first TAB. Collect all the matches */
248 if (numtabs == 1)
249 {
250 cd->num_matched = 0;
251 mutt_str_copy(cd->user_typed, pt, sizeof(cd->user_typed));
252 memset(cd->match_list, 0, cd->match_list_len);
253 memset(cd->completed, 0, sizeof(cd->completed));
254 for (int num = 0; funcs[num].name; num++)
255 candidate(cd, cd->user_typed, funcs[num].name, cd->completed, sizeof(cd->completed));
256 /* try the generic menu */
257 if ((mtype != MENU_PAGER) && (mtype != MENU_GENERIC))
258 {
259 funcs = OpGeneric;
260 for (int num = 0; funcs[num].name; num++)
261 candidate(cd, cd->user_typed, funcs[num].name, cd->completed,
262 sizeof(cd->completed));
263 }
265 cd->match_list[cd->num_matched++] = cd->user_typed;
266
267 /* All matches are stored. Longest non-ambiguous string is ""
268 * i.e. don't change 'buf'. Fake successful return this time */
269 if (cd->user_typed[0] == '\0')
270 return 1;
271 }
272
273 if ((cd->completed[0] == '\0') && (cd->user_typed[0] != '\0'))
274 return 0;
275
276 /* cd->num_matched will _always_ be at least 1 since the initial
277 * user-typed string is always stored */
278 if ((numtabs == 1) && (cd->num_matched == 2))
279 {
280 snprintf(cd->completed, sizeof(cd->completed), "%s", cd->match_list[0]);
281 }
282 else if ((numtabs > 1) && (cd->num_matched > 2))
283 {
284 /* cycle through all the matches */
285 snprintf(cd->completed, sizeof(cd->completed), "%s",
286 cd->match_list[(numtabs - 2) % cd->num_matched]);
287 }
288
289 strncpy(pt, cd->completed, buf->data + buf->dsize - pt - spaces);
290 buf_fix_dptr(buf);
291 }
292 else
293 {
294 return 0;
295 }
296
297 return 1;
298}
299
303static int label_sort(const void *a, const void *b, void *sdata)
304{
305 return strcasecmp(*(const char **) a, *(const char **) b);
306}
307
316int mutt_label_complete(struct CompletionData *cd, struct Buffer *buf, int numtabs)
317{
318 char *pt = buf->data;
319
320 struct Mailbox *m_cur = get_current_mailbox();
321 if (!m_cur || !m_cur->label_hash)
322 return 0;
323
324 SKIPWS(pt);
325
326 /* first TAB. Collect all the matches */
327 if (numtabs == 1)
328 {
329 struct HashElem *he = NULL;
330 struct HashWalkState hws = { 0 };
331
332 cd->num_matched = 0;
333 mutt_str_copy(cd->user_typed, buf_string(buf), sizeof(cd->user_typed));
334 memset(cd->match_list, 0, cd->match_list_len);
335 memset(cd->completed, 0, sizeof(cd->completed));
336 while ((he = mutt_hash_walk(m_cur->label_hash, &hws)))
337 candidate(cd, cd->user_typed, he->key.strkey, cd->completed, sizeof(cd->completed));
339 mutt_qsort_r(cd->match_list, cd->num_matched, sizeof(char *), label_sort, NULL);
340 cd->match_list[cd->num_matched++] = cd->user_typed;
341
342 /* All matches are stored. Longest non-ambiguous string is ""
343 * i.e. don't change 'buf'. Fake successful return this time */
344 if (cd->user_typed[0] == '\0')
345 return 1;
346 }
347
348 if ((cd->completed[0] == '\0') && (cd->user_typed[0] != '\0'))
349 return 0;
350
351 /* cd->num_matched will _always_ be at least 1 since the initial
352 * user-typed string is always stored */
353 if ((numtabs == 1) && (cd->num_matched == 2))
354 {
355 snprintf(cd->completed, sizeof(cd->completed), "%s", cd->match_list[0]);
356 }
357 else if ((numtabs > 1) && (cd->num_matched > 2))
358 {
359 /* cycle through all the matches */
360 snprintf(cd->completed, sizeof(cd->completed), "%s",
361 cd->match_list[(numtabs - 2) % cd->num_matched]);
362 }
363
364 /* return the completed label */
365 buf_strcpy(buf, cd->completed);
366
367 return 1;
368}
369
378int mutt_var_value_complete(struct CompletionData *cd, struct Buffer *buf, int pos)
379{
380 char *pt = buf->data;
381
382 if (pt[0] == '\0')
383 return 0;
384
385 SKIPWS(pt);
386 const int spaces = pt - buf->data;
387
388 pt = buf->data + pos - spaces;
389 while ((pt > buf->data) && !isspace((unsigned char) *pt))
390 pt--;
391 pt++; /* move past the space */
392 if (*pt == '=') /* abort if no var before the '=' */
393 return 0;
394
395 if (buf_startswith(buf, "set"))
396 {
397 char var[256] = { 0 };
398 mutt_str_copy(var, pt, sizeof(var));
399 /* ignore the trailing '=' when comparing */
400 int vlen = mutt_str_len(var);
401 if (vlen == 0)
402 return 0;
403
404 var[vlen - 1] = '\0';
405
406 struct HashElem *he = cs_subset_lookup(NeoMutt->sub, var);
407 if (!he)
408 return 0; /* no such variable. */
409
410 struct Buffer *value = buf_pool_get();
411 struct Buffer *pretty = buf_pool_get();
412 int rc = cs_subset_he_string_get(NeoMutt->sub, he, value);
413 if (CSR_RESULT(rc) == CSR_SUCCESS)
414 {
415 pretty_var(value->data, pretty);
416 snprintf(pt, buf->dsize - (pt - buf->data), "%s=%s", var, pretty->data);
417 buf_pool_release(&value);
418 buf_pool_release(&pretty);
419 return 0;
420 }
421 buf_pool_release(&value);
422 buf_pool_release(&pretty);
423 return 1;
424 }
425 return 0;
426}
427
432{
433 if (!wdata || ((op != OP_EDITOR_COMPLETE) && (op != OP_EDITOR_COMPLETE_QUERY)))
434 return FR_NO_ACTION;
435
436 int rc = FR_SUCCESS;
437 buf_mb_wcstombs(wdata->buffer, wdata->state->wbuf, wdata->state->curpos);
438 size_t i = buf_len(wdata->buffer);
439 if ((i != 0) && (buf_at(wdata->buffer, i - 1) == '=') &&
440 (mutt_var_value_complete(wdata->cd, wdata->buffer, i) != 0))
441 {
442 wdata->tabs = 0;
443 }
444 else if (mutt_command_complete(wdata->cd, wdata->buffer, i, wdata->tabs) == 0)
445 {
446 rc = FR_ERROR;
447 }
448
449 replace_part(wdata->state, 0, buf_string(wdata->buffer));
450 return rc;
451}
452
457{
458 if (!wdata || ((op != OP_EDITOR_COMPLETE) && (op != OP_EDITOR_COMPLETE_QUERY)))
459 return FR_NO_ACTION;
460
461 size_t i;
462 for (i = wdata->state->curpos; (i > 0) && (wdata->state->wbuf[i - 1] != ',') &&
463 (wdata->state->wbuf[i - 1] != ':');
464 i--)
465 {
466 }
467 for (; (i < wdata->state->lastchar) && (wdata->state->wbuf[i] == ' '); i++)
468 ; // do nothing
469
470 buf_mb_wcstombs(wdata->buffer, wdata->state->wbuf + i, wdata->state->curpos - i);
471 int rc = mutt_label_complete(wdata->cd, wdata->buffer, wdata->tabs);
472 replace_part(wdata->state, i, buf_string(wdata->buffer));
473 if (rc != 1)
474 return FR_CONTINUE;
475
476 return FR_SUCCESS;
477}
478
484};
485
491};
#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:378
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:316
int mutt_command_complete(struct CompletionData *cd, struct Buffer *buf, int pos, int numtabs)
Complete a command name.
Definition: helpers.c:112
const struct CompleteOps CompleteLabelOps
Auto-Completion of Labels.
Definition: helpers.c:489
const struct CompleteOps CompleteCommandOps
Auto-Completion of Commands.
Definition: helpers.c:482
bool candidate(struct CompletionData *cd, char *user, const char *src, char *dest, size_t dlen)
Helper function for completion.
Definition: helpers.c:78
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.
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:456
enum FunctionRetval complete_command(struct EnterWindowData *wdata, int op)
Complete a NeoMutt Command - Implements CompleteOps::complete() -.
Definition: helpers.c:431
static int label_sort(const void *a, const void *b, void *sdata)
Compare two label strings - Implements sort_t -.
Definition: helpers.c:303
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:557
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:36
#define MUTT_MEM_REALLOC(pptr, n, type)
Definition: memory.h:43
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:582
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:45
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