NeoMutt  2024-04-25-127-g771158
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 * sizeof(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 struct Command *c = NULL;
135 for (size_t num = 0, size = commands_array(&c); num < size; num++)
136 candidate(cd, cd->user_typed, c[num].name, cd->completed, sizeof(cd->completed));
138 cd->match_list[cd->num_matched++] = cd->user_typed;
139
140 /* All matches are stored. Longest non-ambiguous string is ""
141 * i.e. don't change 'buf'. Fake successful return this time */
142 if (cd->user_typed[0] == '\0')
143 return 1;
144 }
145
146 if ((cd->completed[0] == '\0') && (cd->user_typed[0] != '\0'))
147 return 0;
148
149 /* cd->num_matched will _always_ be at least 1 since the initial
150 * user-typed string is always stored */
151 if ((numtabs == 1) && (cd->num_matched == 2))
152 {
153 snprintf(cd->completed, sizeof(cd->completed), "%s", cd->match_list[0]);
154 }
155 else if ((numtabs > 1) && (cd->num_matched > 2))
156 {
157 /* cycle through all the matches */
158 snprintf(cd->completed, sizeof(cd->completed), "%s",
159 cd->match_list[(numtabs - 2) % cd->num_matched]);
160 }
161
162 /* return the completed command */
163 buf_strcpy(buf, cd->completed);
164 }
165 else if (buf_startswith(buf, "set") || buf_startswith(buf, "unset") ||
166 buf_startswith(buf, "reset") || buf_startswith(buf, "toggle"))
167 { /* complete variables */
168 static const char *const prefixes[] = { "no", "inv", "?", "&", 0 };
169
170 pt++;
171 /* loop through all the possible prefixes (no, inv, ...) */
172 if (buf_startswith(buf, "set"))
173 {
174 for (int num = 0; prefixes[num]; num++)
175 {
176 if (mutt_str_startswith(pt, prefixes[num]))
177 {
178 pt += mutt_str_len(prefixes[num]);
179 break;
180 }
181 }
182 }
183
184 /* first TAB. Collect all the matches */
185 if (numtabs == 1)
186 {
187 cd->num_matched = 0;
188 mutt_str_copy(cd->user_typed, pt, sizeof(cd->user_typed));
189 memset(cd->match_list, 0, cd->match_list_len);
190 memset(cd->completed, 0, sizeof(cd->completed));
191
192 struct HashElem *he = NULL;
193 struct HashElem **he_list = get_elem_list(NeoMutt->sub->cs);
194 for (size_t i = 0; he_list[i]; i++)
195 {
196 he = he_list[i];
197 const int type = DTYPE(he->type);
198
200 continue;
201
202 candidate(cd, cd->user_typed, he->key.strkey, cd->completed, sizeof(cd->completed));
203 }
204 FREE(&he_list);
205
207 cd->match_list[cd->num_matched++] = cd->user_typed;
208
209 /* All matches are stored. Longest non-ambiguous string is ""
210 * i.e. don't change 'buf'. Fake successful return this time */
211 if (cd->user_typed[0] == '\0')
212 return 1;
213 }
214
215 if ((cd->completed[0] == 0) && cd->user_typed[0])
216 return 0;
217
218 /* cd->num_matched will _always_ be at least 1 since the initial
219 * user-typed string is always stored */
220 if ((numtabs == 1) && (cd->num_matched == 2))
221 {
222 snprintf(cd->completed, sizeof(cd->completed), "%s", cd->match_list[0]);
223 }
224 else if ((numtabs > 1) && (cd->num_matched > 2))
225 {
226 /* cycle through all the matches */
227 snprintf(cd->completed, sizeof(cd->completed), "%s",
228 cd->match_list[(numtabs - 2) % cd->num_matched]);
229 }
230
231 strncpy(pt, cd->completed, buf->data + buf->dsize - pt - spaces);
232 buf_fix_dptr(buf);
233 }
234 else if (buf_startswith(buf, "exec"))
235 {
236 const enum MenuType mtype = menu_get_current_type();
237 const struct MenuFuncOp *funcs = km_get_table(mtype);
238 if (!funcs && (mtype != MENU_PAGER))
239 funcs = OpGeneric;
240
241 pt++;
242 /* first TAB. Collect all the matches */
243 if (numtabs == 1)
244 {
245 cd->num_matched = 0;
246 mutt_str_copy(cd->user_typed, pt, sizeof(cd->user_typed));
247 memset(cd->match_list, 0, cd->match_list_len);
248 memset(cd->completed, 0, sizeof(cd->completed));
249 for (int num = 0; funcs[num].name; num++)
250 candidate(cd, cd->user_typed, funcs[num].name, cd->completed, sizeof(cd->completed));
251 /* try the generic menu */
252 if ((mtype != MENU_PAGER) && (mtype != MENU_GENERIC))
253 {
254 funcs = OpGeneric;
255 for (int num = 0; funcs[num].name; num++)
256 candidate(cd, cd->user_typed, funcs[num].name, cd->completed,
257 sizeof(cd->completed));
258 }
260 cd->match_list[cd->num_matched++] = cd->user_typed;
261
262 /* All matches are stored. Longest non-ambiguous string is ""
263 * i.e. don't change 'buf'. Fake successful return this time */
264 if (cd->user_typed[0] == '\0')
265 return 1;
266 }
267
268 if ((cd->completed[0] == '\0') && (cd->user_typed[0] != '\0'))
269 return 0;
270
271 /* cd->num_matched will _always_ be at least 1 since the initial
272 * user-typed string is always stored */
273 if ((numtabs == 1) && (cd->num_matched == 2))
274 {
275 snprintf(cd->completed, sizeof(cd->completed), "%s", cd->match_list[0]);
276 }
277 else if ((numtabs > 1) && (cd->num_matched > 2))
278 {
279 /* cycle through all the matches */
280 snprintf(cd->completed, sizeof(cd->completed), "%s",
281 cd->match_list[(numtabs - 2) % cd->num_matched]);
282 }
283
284 strncpy(pt, cd->completed, buf->data + buf->dsize - pt - spaces);
285 buf_fix_dptr(buf);
286 }
287 else
288 {
289 return 0;
290 }
291
292 return 1;
293}
294
298static int label_sort(const void *a, const void *b, void *sdata)
299{
300 return strcasecmp(*(const char **) a, *(const char **) b);
301}
302
311int mutt_label_complete(struct CompletionData *cd, struct Buffer *buf, int numtabs)
312{
313 char *pt = buf->data;
314
315 struct Mailbox *m_cur = get_current_mailbox();
316 if (!m_cur || !m_cur->label_hash)
317 return 0;
318
319 SKIPWS(pt);
320
321 /* first TAB. Collect all the matches */
322 if (numtabs == 1)
323 {
324 struct HashElem *he = NULL;
325 struct HashWalkState hws = { 0 };
326
327 cd->num_matched = 0;
328 mutt_str_copy(cd->user_typed, buf_string(buf), sizeof(cd->user_typed));
329 memset(cd->match_list, 0, cd->match_list_len);
330 memset(cd->completed, 0, sizeof(cd->completed));
331 while ((he = mutt_hash_walk(m_cur->label_hash, &hws)))
332 candidate(cd, cd->user_typed, he->key.strkey, cd->completed, sizeof(cd->completed));
334 mutt_qsort_r(cd->match_list, cd->num_matched, sizeof(char *), label_sort, NULL);
335 cd->match_list[cd->num_matched++] = cd->user_typed;
336
337 /* All matches are stored. Longest non-ambiguous string is ""
338 * i.e. don't change 'buf'. Fake successful return this time */
339 if (cd->user_typed[0] == '\0')
340 return 1;
341 }
342
343 if ((cd->completed[0] == '\0') && (cd->user_typed[0] != '\0'))
344 return 0;
345
346 /* cd->num_matched will _always_ be at least 1 since the initial
347 * user-typed string is always stored */
348 if ((numtabs == 1) && (cd->num_matched == 2))
349 {
350 snprintf(cd->completed, sizeof(cd->completed), "%s", cd->match_list[0]);
351 }
352 else if ((numtabs > 1) && (cd->num_matched > 2))
353 {
354 /* cycle through all the matches */
355 snprintf(cd->completed, sizeof(cd->completed), "%s",
356 cd->match_list[(numtabs - 2) % cd->num_matched]);
357 }
358
359 /* return the completed label */
360 buf_strcpy(buf, cd->completed);
361
362 return 1;
363}
364
373int mutt_var_value_complete(struct CompletionData *cd, struct Buffer *buf, int pos)
374{
375 char *pt = buf->data;
376
377 if (pt[0] == '\0')
378 return 0;
379
380 SKIPWS(pt);
381 const int spaces = pt - buf->data;
382
383 pt = buf->data + pos - spaces;
384 while ((pt > buf->data) && !isspace((unsigned char) *pt))
385 pt--;
386 pt++; /* move past the space */
387 if (*pt == '=') /* abort if no var before the '=' */
388 return 0;
389
390 if (buf_startswith(buf, "set"))
391 {
392 char var[256] = { 0 };
393 mutt_str_copy(var, pt, sizeof(var));
394 /* ignore the trailing '=' when comparing */
395 int vlen = mutt_str_len(var);
396 if (vlen == 0)
397 return 0;
398
399 var[vlen - 1] = '\0';
400
401 struct HashElem *he = cs_subset_lookup(NeoMutt->sub, var);
402 if (!he)
403 return 0; /* no such variable. */
404
405 struct Buffer *value = buf_pool_get();
406 struct Buffer *pretty = buf_pool_get();
407 int rc = cs_subset_he_string_get(NeoMutt->sub, he, value);
408 if (CSR_RESULT(rc) == CSR_SUCCESS)
409 {
410 pretty_var(value->data, pretty);
411 snprintf(pt, buf->dsize - (pt - buf->data), "%s=%s", var, pretty->data);
412 buf_pool_release(&value);
413 buf_pool_release(&pretty);
414 return 0;
415 }
416 buf_pool_release(&value);
417 buf_pool_release(&pretty);
418 return 1;
419 }
420 return 0;
421}
422
427{
428 if (!wdata || ((op != OP_EDITOR_COMPLETE) && (op != OP_EDITOR_COMPLETE_QUERY)))
429 return FR_NO_ACTION;
430
431 int rc = FR_SUCCESS;
432 buf_mb_wcstombs(wdata->buffer, wdata->state->wbuf, wdata->state->curpos);
433 size_t i = buf_len(wdata->buffer);
434 if ((i != 0) && (buf_at(wdata->buffer, i - 1) == '=') &&
435 (mutt_var_value_complete(wdata->cd, wdata->buffer, i) != 0))
436 {
437 wdata->tabs = 0;
438 }
439 else if (mutt_command_complete(wdata->cd, wdata->buffer, i, wdata->tabs) == 0)
440 {
441 rc = FR_ERROR;
442 }
443
444 replace_part(wdata->state, 0, buf_string(wdata->buffer));
445 return rc;
446}
447
452{
453 if (!wdata || ((op != OP_EDITOR_COMPLETE) && (op != OP_EDITOR_COMPLETE_QUERY)))
454 return FR_NO_ACTION;
455
456 size_t i;
457 for (i = wdata->state->curpos; (i > 0) && (wdata->state->wbuf[i - 1] != ',') &&
458 (wdata->state->wbuf[i - 1] != ':');
459 i--)
460 {
461 }
462 for (; (i < wdata->state->lastchar) && (wdata->state->wbuf[i] == ' '); i++)
463 ; // do nothing
464
465 buf_mb_wcstombs(wdata->buffer, wdata->state->wbuf + i, wdata->state->curpos - i);
466 int rc = mutt_label_complete(wdata->cd, wdata->buffer, wdata->tabs);
467 replace_part(wdata->state, i, buf_string(wdata->buffer));
468 if (rc != 1)
469 return FR_CONTINUE;
470
471 return FR_SUCCESS;
472}
473
479};
480
486};
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:670
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:709
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:373
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:311
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:484
const struct CompleteOps CompleteCommandOps
Auto-Completion of Commands.
Definition: helpers.c:477
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:52
#define CSR_SUCCESS
Action completed successfully.
Definition: set.h:35
size_t commands_array(struct Command **first)
Get Commands array.
Definition: command.c:75
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:451
enum FunctionRetval complete_command(struct EnterWindowData *wdata, int op)
Complete a NeoMutt Command - Implements CompleteOps::complete() -.
Definition: helpers.c:426
static int label_sort(const void *a, const void *b, void *sdata)
Compare two label strings - Implements sort_t -.
Definition: helpers.c:298
const struct MenuFuncOp OpGeneric[]
Functions for the Generic Menu.
Definition: functions.c:68
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:715
const struct MenuFuncOp * km_get_table(enum MenuType mtype)
Lookup a Menu's functions.
Definition: lib.c:525
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
void mutt_mem_realloc(void *ptr, size_t size)
Resize a block of memory on the heap.
Definition: memory.c:115
#define FREE(x)
Definition: memory.h:45
#define ROUND_UP(NUM, STEP)
Definition: memory.h:36
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:230
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:496
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:81
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition: pool.c:94
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:52
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:51
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:43
union HashKey key
Key representing the data.
Definition: hash.h:45
int type
Type of data stored in Hash Table, e.g. DT_STRING.
Definition: hash.h:44
Cursor to iterate through a Hash Table.
Definition: hash.h:132
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:101
const char * name
Name of the function.
Definition: lib.h:102
Container for Accounts, Notifications.
Definition: neomutt.h:42
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:332
struct HashElem ** get_elem_list(struct ConfigSet *cs)
Create a sorted list of all config items.
Definition: subset.c:79
struct HashElem * cs_subset_lookup(const struct ConfigSubset *sub, const char *name)
Find an inherited config item.
Definition: subset.c:187
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:52
#define DTYPE(t)
Definition: types.h:50
#define D_INTERNAL_DEPRECATED
Config item shouldn't be used any more.
Definition: types.h:88
@ DT_SYNONYM
synonym for another variable
Definition: types.h:46
const char * strkey
String key.
Definition: hash.h:35