diff --git a/contrib/remind-conf-mode/remind-conf-mode.el b/contrib/remind-conf-mode/remind-conf-mode.el index 3952170f..9cf99031 100644 --- a/contrib/remind-conf-mode/remind-conf-mode.el +++ b/contrib/remind-conf-mode/remind-conf-mode.el @@ -114,7 +114,7 @@ "DURATION" "ELSE" "ENDIF" "ERRMSG" "EXIT" "EXPR" "FIRST" "FLUSH" "FOURTH" "FRENAME" "FROM" "FSET" "FUNSET" "IF" "IFTRIG" "IN" "INC" "INCLUDE" "INCLUDECMD" "INFO" "LAST" - "LASTDAY" "LASTWORKDAY" "MAYBE" "MAYBE-UNCOMPUTABLE" "MSF" + "LASTDAY" "LASTWORKDAY" "MAX-OVERDUE" "MAYBE" "MAYBE-UNCOMPUTABLE" "MSF" "MSG" "NOQUEUE" "OMIT" "OMITFUNC" "ONCE" "POP" "POP-OMIT-CONTEXT" "POP-FUNCS" "POP-VARS" "PRESERVE" "PRIORITY" "PS" "PSFILE" "PUSH" "PUSH-FUNCS" "PUSH-VARS" "PUSH-OMIT-CONTEXT" "REM" "RETURN" diff --git a/man/remind.1.in b/man/remind.1.in index dda2342d..6c0285de 100644 --- a/man/remind.1.in +++ b/man/remind.1.in @@ -668,6 +668,7 @@ Its syntax is: [\fIdelta\fR] [\fIrepeat\fR] [\fBTODO\fR] +[\fBMAX-OVERDUE\fR \fIn\fR] [\fBCOMPLETE-THROUGH\fR \fIcomplete_date\fR] [\fBPRIORITY\fR \fIprio\fR] [\fBSKIP\fR | \fBBEFORE\fR | \fBAFTER\fR] @@ -1944,7 +1945,7 @@ which recurrences have been completed. For example: Canadian income taxes must be filed every 30 April. The above command will remind you to pay taxes in 2026. If you don't update the COMPLETE-THROUGH date, then after 30 April 2026, \fBRemind\fR will -keep reminding you until the end of time that my taxes were due on 30 +keep reminding you until the end of time that your taxes were due on 30 April 2026. To indicate that you've paid them, simply update the COMPLETE-THROUGH date to 2026-04-30 and then \fBRemind\fR will start reminding you of your 2027 taxes (starting 15 days before the due @@ -1952,6 +1953,26 @@ date.) .PP It is an error to specify COMPLETE-THROUGH without also specifying TODO. .PP +.SH LIMITING REMINDERS ABOUT OVERDUE TODOS +.PP +Although \fBRemind\fR is happy to keep reminding you about overdue +TODOs for hundreds of years, for some things this may be pointless. +If you want \fBRemind\fR to \fIstop\fR nagging you about an overdue +TODO after a certain number of days, use the MAX-OVERDUE \fIn\fR clause. +In this case, \fBRemind\fR stops reminding you of a TODO that is overdue +by more than \fIn\fR days. Here is an example. +.PP +.nf + REM TODO 2025-08-13 ++5 MAX-OVERDUE 5 MSG Task: %b. +.fi +.PP +\fBRemind\fR \fIstarts\fR reminding you of the task on 2025-08-08, +because of the ++5 back value. It keeps reminding you of the task +after the due date. However, the last time it will remind you +will be on 2025-08-18, because of the MAX-OVERDUE clause. Starting +on 2025-08-19, \fBRemind\fR will no longer remind you of the task +since it's probably pointless---it has passed the MAX-OVERDUE period. +.PP .SH MORE DETAILS ABOUT TODOS .PP TODOs are treated specially only in Agenda Mode. In Calendar Mode, diff --git a/src/dorem.c b/src/dorem.c index ff99ab39..5e674e4a 100644 --- a/src/dorem.c +++ b/src/dorem.c @@ -293,6 +293,12 @@ int DoRem(ParsePtr p) return E_COMPLETE_WITHOUT_TODO; } + if (trig.max_overdue >= 0 && !trig.is_todo) { + PurgeEchoLine("%s\n", CurLine); + FreeTrig(&trig); + return E_MAX_OVERDUE_WITHOUT_TODO; + } + if (trig.typ == NO_TYPE) { if (!Hush) { PurgeEchoLine("%s\n", "#!P! Cannot parse next line"); @@ -668,6 +674,7 @@ int ParseRem(ParsePtr s, Trigger *trig, TimeTrig *tim) tim->duration = NO_TIME; trig->need_wkday = 0; trig->is_todo = 0; + trig->max_overdue = -1; trig->complete_through = NO_DATE; trig->adj_for_last = 0; trig->infos = NULL; @@ -758,6 +765,25 @@ int ParseRem(ParsePtr s, Trigger *trig, TimeTrig *tim) trig->skip = tok.val; break; + case T_MaxOverdue: + if (trig->max_overdue >= 0) return E_MAX_OVERDUE_TWICE; + DBufFree(&buf); + r = ParseToken(s, &buf); + if (r) return r; + FindToken(DBufValue(&buf), &tok); + DBufFree(&buf); + if (tok.type == T_Illegal) { + return -tok.val; + } + if (tok.type != T_Day && tok.type != T_Year && tok.type != T_Number) { + return E_EXPECTING_NUMBER; + } + if (tok.val < 0) { + return E_2LOW; + } + trig->max_overdue = tok.val; + break; + case T_Priority: DBufFree(&buf); r=ParsePriority(s, trig); @@ -1664,8 +1690,14 @@ int ShouldTriggerReminder(Trigger const *t, TimeTrig const *tim, int dse, int *e } /* DO trigger if has not been completed through trigger date */ if (t->complete_through == NO_DATE || t->complete_through < dse) { - /* Trigger date is in the past - overdue */ + /* Trigger date is in the past - overdue, Trigger unless we're + more than max_overdue days late */ if (dse < DSEToday) { + if (t->max_overdue >= 0) { + if (dse + t->max_overdue < DSEToday) { + return 0; + } + } return 1; } /* Trigger date in future - use normal Remind rules */ @@ -1961,8 +1993,14 @@ static int ShouldTriggerBasedOnWarn(Trigger const *t, int dse, int *err) } /* DO trigger if has not been completed through trigger date */ if (t->complete_through == NO_DATE || t->complete_through < dse) { - /* Trigger date is in the past - overdue */ + /* Trigger date is in the past - overdue, Trigger unless we're + more than max_overdue days late */ if (dse < DSEToday) { + if (t->max_overdue >= 0) { + if (dse + t->max_overdue < DSEToday) { + return 0; + } + } return 1; } /* Trigger date in future - use normal Remind rules */ diff --git a/src/err.h b/src/err.h index fc811722..204f8723 100644 --- a/src/err.h +++ b/src/err.h @@ -130,6 +130,8 @@ #define E_EXPR_DISABLED 106 #define E_TIME_EXCEEDED 107 #define E_COMPLETE_WITHOUT_TODO 108 +#define E_MAX_OVERDUE_TWICE 109 +#define E_MAX_OVERDUE_WITHOUT_TODO 110 #ifdef MK_GLOBALS #undef EXTERN @@ -257,6 +259,8 @@ EXTERN char *ErrMsg[] /* E_EXPR_DISABLED */ "Expression evaluation is disabled", /* E_TIME_EXCEEDED */ "Time limit for expression evaluation exceeded", /* E_COMPLETE_WITHOUT_TODO */ "COMPLETE-THROUGH specified without TODO", +/* E_MAX_OVERDUE_TWICE */ "MAX-OVERDUE specified twice", +/* E_MAX_OVERDUE_WITHOUT_TODO */ "MAX-OVERDUE specified without TODO", } #endif /* MK_GLOBALS */ ; diff --git a/src/init.c b/src/init.c index 93bdc8c4..ad847b08 100644 --- a/src/init.c +++ b/src/init.c @@ -1149,10 +1149,18 @@ ProcessLongOption(char const *arg) return; } if (!strcmp(arg, "only-todos")) { + if (TodoFilter == ONLY_EVENTS) { + fprintf(ErrFp, "remind: Cannot combine --only-todos and --only-events\n"); + exit(1); + } TodoFilter = ONLY_TODOS; return; } if (!strcmp(arg, "only-events")) { + if (TodoFilter == ONLY_TODOS) { + fprintf(ErrFp, "remind: Cannot combine --only-todos and --only-events\n"); + exit(1); + } TodoFilter = ONLY_EVENTS; return; } diff --git a/src/token.c b/src/token.c index 15fb73b9..0cf02b5e 100644 --- a/src/token.c +++ b/src/token.c @@ -80,6 +80,7 @@ Token TokArray[] = { { "lastday", 7, T_BackAdj, -1 }, { "lastworkday", 11, T_BackAdj, 1 }, { "march", 3, T_Month, 2 }, + { "max-overdue", 11, T_MaxOverdue, 0 }, { "may", 3, T_Month, 4 }, { "maybe-uncomputable", 5, T_MaybeUncomputable, 0}, { "monday", 3, T_WkDay, 0 }, diff --git a/src/types.h b/src/types.h index 1586f98a..ace10a32 100644 --- a/src/types.h +++ b/src/types.h @@ -145,6 +145,7 @@ typedef struct { int maybe_uncomputable; /* Suppress "can't compute trigger" warnings */ int addomit; /* Add trigger date to global OMITs */ int noqueue; /* Don't queue even if timed */ + int max_overdue; /* Stop warning if TODO is too far overdue */ char sched[VAR_NAME_LEN+1]; /* Scheduling function */ char warn[VAR_NAME_LEN+1]; /* Warning function */ char omitfunc[VAR_NAME_LEN+1]; /* OMITFUNC function */ @@ -234,7 +235,7 @@ enum TokTypes T_Else, T_Empty, T_EndIf, T_ErrMsg, T_Exit, T_Expr, T_Flush, T_Frename, T_Fset, T_Funset, T_If, T_IfTrig, T_In, T_Include, T_IncludeCmd, T_IncludeR, T_IncludeSys, T_Info, T_LastBack, - T_LongTime, T_MaybeUncomputable, T_Month, T_NoQueue, T_Number, + T_LongTime, T_MaxOverdue, T_MaybeUncomputable, T_Month, T_NoQueue, T_Number, T_Omit, T_OmitFunc, T_Once, T_Ordinal, T_Pop, T_PopFuncs, T_PopVars, T_Preserve, T_Priority, T_Push, T_PushFuncs, T_PushVars, T_Rem, T_RemType, T_Rep, T_Return, T_Scanfrom, T_Sched, T_Set, T_Skip, T_Tag, diff --git a/tests/test.cmp b/tests/test.cmp index 5996a0ad..8dfb0c20 100644 --- a/tests/test.cmp +++ b/tests/test.cmp @@ -24430,6 +24430,7 @@ info last lastday lastworkday +max-overdue maybe maybe-uncomputable msf @@ -24965,6 +24966,8 @@ TRANSLATE "Duplicate argument name" "" TRANSLATE "Expression evaluation is disabled" "" TRANSLATE "Time limit for expression evaluation exceeded" "" TRANSLATE "COMPLETE-THROUGH specified without TODO" "" +TRANSLATE "MAX-OVERDUE specified twice" "" +TRANSLATE "MAX-OVERDUE specified without TODO" "" # Other Messages TRANSLATE "%s function `%s' defined at %s(%s) does not use its argument" "" @@ -39553,6 +39556,10 @@ Fifth today Sixth on 2025-08-06 Eighth on 2025-08-20 Ninth today +Yup today +Yup2 yesterday +Yup3 on 2025-08-11 +Yup4 on 2025-08-10 Testing TODOS in calendar mode 2025/08/01 * * * * First @@ -39562,9 +39569,14 @@ Testing TODOS in calendar mode 2025/08/06 * * * * Seventh 2025/08/06 * * * * Eighth 2025/08/06 * * * * Ninth +2025/08/09 * * * * Nope +2025/08/10 * * * * Yup4 +2025/08/11 * * * * Yup3 +2025/08/12 * * * * Yup2 2025/08/13 * * * * Fifth 2025/08/13 * * * * Eighth 2025/08/13 * * * * Ninth +2025/08/13 * * * * Yup 2025/08/20 * * * * Fourth 2025/08/20 * * * * Eighth 2025/08/20 * * * * Ninth @@ -39576,8 +39588,13 @@ Testing TODOS in calendar mode with completed todos hidden 2025/08/01 * * * * Second 2025/08/01 * * * * Third 2025/08/06 * * * * Sixth +2025/08/09 * * * * Nope +2025/08/10 * * * * Yup4 +2025/08/11 * * * * Yup3 +2025/08/12 * * * * Yup2 2025/08/13 * * * * Fifth 2025/08/13 * * * * Ninth +2025/08/13 * * * * Yup 2025/08/20 * * * * Fourth 2025/08/20 * * * * Eighth 2025/08/20 * * * * Ninth diff --git a/tests/todos.rem b/tests/todos.rem index 008888d2..1292f6f2 100644 --- a/tests/todos.rem +++ b/tests/todos.rem @@ -10,3 +10,10 @@ REM TODO 6 Aug 2025 +7 MSG %"Sixth%" %l REM TODO COMPLETE-THROUGH 2025-08-06 6 Aug 2025 +7 MSG %"Seventh%" %l REM TODO Wed +7 COMPLETE-THROUGH 2025-08-13 MSG %"Eighth%" %l REM TODO Wed +7 COMPLETE-THROUGH 2025-08-12 MSG %"Ninth%" %l + +# Test MAX-OVERDUE +REM TODO 2025-08-13 MAX-OVERDUE 3 MSG %"Yup%" %l +REM TODO 2025-08-12 MAX-OVERDUE 3 MSG %"Yup2%" %l +REM TODO 2025-08-11 MAX-OVERDUE 3 MSG %"Yup3%" %l +REM TODO 2025-08-10 MAX-OVERDUE 3 MSG %"Yup4%" %l +REM TODO 2025-08-9 MAX-OVERDUE 3 MSG %"Nope%" %l