summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Tirsek <peter@tirsek.com>2022-08-14 00:52:56 -0500
committerEduardo Chappa <chappa@washington.edu>2022-09-10 09:37:03 -0600
commitb21b6c54269c692b7dd8e9198eaead1cb939e0c6 (patch)
tree0e024a87faaf8e300ac289fc95a0a024c72db702
parent768042b955ae35df6234586a9ea2949f42216248 (diff)
downloadalpine-b21b6c54269c692b7dd8e9198eaead1cb939e0c6.tar.xz
Add support for tab-completion when selecting by rule
When selecting messages based on a previously defined rule, this patch allows for completing the name of the rule if the enable-tab-completion feature is set.
-rw-r--r--alpine/mailcmd.c147
1 files changed, 136 insertions, 11 deletions
diff --git a/alpine/mailcmd.c b/alpine/mailcmd.c
index ed93348d..05c7de26 100644
--- a/alpine/mailcmd.c
+++ b/alpine/mailcmd.c
@@ -107,7 +107,8 @@ SEARCHSET *visible_searchset(MAILSTREAM *, MSGNO_S *);
int select_by_status(MAILSTREAM *, SEARCHSET **);
int select_by_rule(MAILSTREAM *, SEARCHSET **);
int select_by_thread(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
-char *choose_a_rule(int);
+char *choose_a_rule(int, char *);
+int rulenick_complete(int, char *, int);
int select_by_keyword(MAILSTREAM *, SEARCHSET **);
char *choose_a_keyword(void);
int select_sort(struct pine *, int, SortOrder *, int *);
@@ -328,6 +329,14 @@ static ESCKEY_S sel_size_opt[] = {
{-1, 0, NULL, NULL}
};
+static ESCKEY_S sel_rule_opt[] = {
+ {0, 0, NULL, NULL},
+ {ctrl('T'), 14, "^T", N_("To List")},
+ {0, 0, NULL, NULL}, /* Reserved for TAB completion */
+ {'!', '!', "!", N_("Not")},
+ {-1, 0, NULL, NULL}
+};
+
static ESCKEY_S sel_key_opt[] = {
{0, 0, NULL, NULL},
{ctrl('T'), 14, "^T", N_("To List")},
@@ -8856,7 +8865,7 @@ select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
{
char rulenick[1000], *nick;
PATGRP_S *patgrp;
- int r, not = 0, we_cancel = 0, rflags = ROLE_DO_SRCH
+ int r, last_r = 0, not = 0, we_cancel = 0, rflags = ROLE_DO_SRCH
| ROLE_DO_INCOLS
| ROLE_DO_ROLES
| ROLE_DO_SCORES
@@ -8866,6 +8875,16 @@ select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
rulenick[0] = '\0';
ps_global->mangled_footer = 1;
+ if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){
+ sel_rule_opt[2].ch = TAB;
+ sel_rule_opt[2].rval = 15;
+ sel_rule_opt[2].name = "TAB";
+ sel_rule_opt[2].label = N_("Complete");
+ }
+ else{
+ memset(&sel_rule_opt[2], 0, sizeof(sel_rule_opt[2]));
+ }
+
do{
int oe_flags;
@@ -8874,11 +8893,11 @@ select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
sizeof(rulenick),
not ? _("Rule to NOT match: ")
: _("Rule to match: "),
- sel_key_opt, NO_HELP, &oe_flags);
+ sel_rule_opt, NO_HELP, &oe_flags);
if(r == 14){
/* select rulenick from a list */
- if((nick=choose_a_rule(rflags)) != NULL){
+ if((nick=choose_a_rule(rflags, NULL)) != NULL){
strncpy(rulenick, nick, sizeof(rulenick)-1);
rulenick[sizeof(rulenick)-1] = '\0';
fs_give((void **) &nick);
@@ -8886,6 +8905,23 @@ select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
else
r = 4;
}
+ else if(r == 15){
+ int n = rulenick_complete(rflags, rulenick, sizeof(rulenick));
+
+ if(n > 1 && last_r == 15 && !(oe_flags & OE_USER_MODIFIED)){
+ /* double tab with multiple completions: select from list */
+ if((nick=choose_a_rule(rflags, rulenick)) != NULL){
+ strncpy(rulenick, nick, sizeof(rulenick)-1);
+ rulenick[sizeof(rulenick)-1] = '\0';
+ fs_give((void **) &nick);
+ r = 0;
+ }
+ else
+ r = 4;
+ }
+ else if(n != 1)
+ Writechar(BELL, 0);
+ }
else if(r == '!')
not = !not;
@@ -8899,8 +8935,9 @@ select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
}
removing_leading_and_trailing_white_space(rulenick);
+ last_r = r;
- }while(r == 3 || r == 4 || r == '!');
+ }while(r == 3 || r == 4 || r == 15 || r == '!');
/*
@@ -8942,14 +8979,18 @@ select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
/*
* Allow user to choose a rule from their list of rules.
*
+ * Args rflags -- Pattern types to choose from
+ * prefix -- Only list rules matching the given prefix
+ *
* Returns an allocated rule nickname on success, NULL otherwise.
*/
char *
-choose_a_rule(int rflags)
+choose_a_rule(int rflags, char *prefix)
{
char *choice = NULL;
char **rule_list, **lp;
int cnt = 0;
+ int prefix_length = 0;
PAT_S *pat;
PAT_STATE pstate;
void (*redraw)(void) = ps_global->redrawer;
@@ -8964,8 +9005,15 @@ choose_a_rule(int rflags)
* Build a list of rules to choose from.
*/
- for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
- cnt++;
+ if(prefix)
+ prefix_length = strlen(prefix);
+
+ for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)) {
+ if(!pat->patgrp || !pat->patgrp->nick)
+ continue;
+ if(!prefix || strncmp(pat->patgrp->nick, prefix, prefix_length) == 0)
+ cnt++;
+ }
if(cnt <= 0){
q_status_message(SM_ORDER, 3, 4, _("No rules defined, use Setup/Rules"));
@@ -8975,9 +9023,12 @@ choose_a_rule(int rflags)
lp = rule_list = (char **) fs_get((cnt + 1) * sizeof(*rule_list));
memset(rule_list, 0, (cnt+1) * sizeof(*rule_list));
- for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
- *lp++ = cpystr((pat->patgrp && pat->patgrp->nick)
- ? pat->patgrp->nick : "?");
+ for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)) {
+ if(!pat->patgrp || !pat->patgrp->nick)
+ continue;
+ if(!prefix || strncmp(pat->patgrp->nick, prefix, prefix_length) == 0)
+ *lp++ = cpystr(pat->patgrp->nick);
+ }
/* TRANSLATORS: SELECT A RULE is a screen title
TRANSLATORS: Print something1 using something2.
@@ -8998,6 +9049,80 @@ choose_a_rule(int rflags)
/*
+ * Complete a partial rule name against the user's list of rules.
+ *
+ * Args rflags -- Pattern types to choose from
+ * nick -- Current rule nickname to complete
+ * nick_size -- Maximum length of the nick array to store completion in
+ *
+ * Returns the number of valid completions, i.e.:
+ *
+ * 0 if there are no valid completions. The value of the nick argument has not
+ * been changed.
+ * 1 if there is only a single valid completion. The value of the nick argument
+ * has been replaced with the full text of that completion.
+ * >1 if there is more than one valid completion. The value of the nick argument
+ * has been replaced with the longest substring common to all the appropriate
+ * completions.
+ */
+int
+rulenick_complete(int rflags, char *nick, int nick_size)
+{
+ char *candidate = NULL;
+ int cnt = 0;
+ int common_prefix_length = 0;
+ int nick_length;
+ PAT_S *pat;
+ PAT_STATE pstate;
+
+ if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate)))
+ return 0;
+
+ nick_length = strlen(nick);
+
+ for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)){
+ if(!pat->patgrp || !pat->patgrp->nick)
+ continue;
+ if(strncmp(pat->patgrp->nick, nick, nick_length) == 0){
+ /* This is a candidate for completion. */
+ cnt++;
+ if(!candidate){
+ /* This is the first candidate. Keep it as a future reference to
+ * compare against to find the longest common prefix length of
+ * all the matches. */
+ candidate = pat->patgrp->nick;
+ common_prefix_length = strlen(pat->patgrp->nick);
+ }
+ else{
+ /* Find the common prefix length between the first candidate and
+ * this one. */
+ int i;
+ for(i = 0; i < common_prefix_length; i++){
+ if(pat->patgrp->nick[i] != candidate[i]){
+ /* In the event that we ended up in the middle of a
+ * UTF-8 code point, backtrack to the byte before the
+ * start of this code point. */
+ while(i > 0 && (candidate[i] & 0xC0) == 0x80)
+ i--;
+ common_prefix_length = i;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if(cnt > 0){
+ int length = MIN(nick_size, common_prefix_length);
+ strncpy(nick + nick_length, candidate + nick_length, length - nick_length);
+ nick[length] = '\0';
+ }
+
+ return cnt;
+}
+
+
+/*
* Select by current thread.
* Sets searched bits in mail_elts for this entire thread
*