From f6253d0fca42d8c882b4f059766ada288b149dff Mon Sep 17 00:00:00 2001 From: Dianne Skoll Date: Thu, 8 Jan 2026 14:13:34 -0500 Subject: [PATCH] Add $TerminalHyperlinks system variable. This lets you turn any reminder with an INFO "Url: ..." string into a hyperlink on the terminal. --- man/remind.1.in | 7 ++++ src/calendar.c | 10 ++--- src/dorem.c | 20 ++++++++-- src/globals.h | 5 ++- src/init.c | 5 --- src/main.c | 15 +++++++- src/protos.h | 4 +- src/queue.c | 11 ++++-- src/sort.c | 75 +++++++++++++++++++++++-------------- src/var.c | 1 + tests/manpage-personal-dict | 1 + tests/test-rem | 8 ++++ tests/test.cmp | 26 ++++++++++++- tests/test.rem | 5 +++ 14 files changed, 141 insertions(+), 52 deletions(-) diff --git a/man/remind.1.in b/man/remind.1.in index fc3eb7f6..f4c3e80f 100644 --- a/man/remind.1.in +++ b/man/remind.1.in @@ -3488,6 +3488,13 @@ to be "dark" if the average of the red, green and blue components is at most 85 out of 255, and if the maximum of any component is at most 128 out of 255. .TP +.B $TerminalHyperlinks (INT type) +If your terminal supports escape sequences to allow HTML-like +anchors around text (see https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda), then you can set this variable to 1. \fBRemind\fR will then +make any reminder with a "Url:" info string into a hyperlink in your +terminal. By default, \fB$TerminalHyperlinks\fR is set to zero because +not all terminals support this feature. +.TP .B $WarningLevel (STRING type) As new versions of \fBRemind\fR are released, new warnings may be added. If your formerly-fine scripts suddenly start issuing warnings when you diff --git a/src/calendar.c b/src/calendar.c index f733d503..0d008784 100644 --- a/src/calendar.c +++ b/src/calendar.c @@ -95,9 +95,6 @@ static struct line_drawing UTF8Drawing = { "\xe2\x94\x80" }; -static char const *start_link = "\x1B]8;;"; -static char const *end_link = "\x1B]8;;\x1B\\"; - static char *VT100Colors[2][2][2][2] /* [Br][R][G][B] */ = { { /*** DIM COLORS ***/ @@ -1578,8 +1575,7 @@ static int WriteOneColLine(int col) } if (url) { - printf("%s", start_link); - printf("%s\x1B\\", url); + printf("\x1B]8;;%s\x1B\\", url); } /* If we couldn't find a space char, print what we have. */ if (!wspace) { @@ -1616,7 +1612,7 @@ static int WriteOneColLine(int col) } if (url) { - printf("%s", end_link); + printf("\x1B]8;;\x1B\\"); } /* Decolorize reminder if necessary, but keep any SHADE */ if (UseVTColors && e->is_color) { @@ -2445,7 +2441,7 @@ get_url(TrigInfo *infos) { TrigInfo *ti = infos; char const *url; - if (!LinksInTerminal) { + if (!TerminalHyperlinks) { /* Nope, not doing links in terminal */ return NULL; } diff --git a/src/dorem.c b/src/dorem.c index fc47c1f2..5b9bc443 100644 --- a/src/dorem.c +++ b/src/dorem.c @@ -1555,6 +1555,7 @@ int TriggerReminder(ParsePtr p, Trigger *t, TimeTrig const *tim, int dse, int is DynamicBuffer pre_buf; char const *s; char const *msg_command = NULL; + char const *url; Value v; if (MsgCommand) { @@ -1830,9 +1831,16 @@ int TriggerReminder(ParsePtr p, Trigger *t, TimeTrig const *tim, int dse, int is } } -/* If we are sorting, just queue it up in the sort buffer */ + /* Get the url if terminal hyperlinks are enabled */ + if (TerminalHyperlinks) { + url = FindTrigInfo(t, "url"); + } else { + url = NULL; + } + + /* If we are sorting, just queue it up in the sort buffer */ if (SortByDate) { - if (InsertIntoSortBuffer(dse, tim->ttime, DBufValue(&buf), + if (InsertIntoSortBuffer(dse, tim->ttime, url, DBufValue(&buf), t->typ, t->priority) == OK) { DBufFree(&buf); NumTriggered++; @@ -1855,14 +1863,20 @@ int TriggerReminder(ParsePtr p, Trigger *t, TimeTrig const *tim, int dse, int is if (IsServerMode() && !strncmp(DBufValue(&buf), "NOTE endreminder", 16)) { printf(" %s", DBufValue(&buf)); } else { + if (url) { + printf("\x1B]8;;%s\x1B\\", url); + } printf("%s", DBufValue(&buf)); + if (url) { + printf("\x1B]8;;\x1B\\"); + } } } } break; case MSF_TYPE: - FillParagraph(DBufValue(&buf), output); + FillParagraph(url, DBufValue(&buf), output); break; case RUN_TYPE: diff --git a/src/globals.h b/src/globals.h index 0319e322..438fdec6 100644 --- a/src/globals.h +++ b/src/globals.h @@ -106,7 +106,6 @@ EXTERN INIT( int DefaultPrio, NO_PRIORITY); EXTERN INIT( int SysTime, -1); EXTERN INIT( int LocalSysTime, -1); EXTERN INIT( int ParseUntriggered, 0); -EXTERN INIT( int LinksInTerminal, 0); EXTERN char const *InitialFile; EXTERN char const *LocalTimeZone; @@ -171,6 +170,10 @@ EXTERN INIT( double Latitude, DEFAULT_LATITUDE); EXTERN INIT( char *Location, LOCATION); +/* Support hyperlinks in terminal emulators? + https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda +*/ +EXTERN INIT( int TerminalHyperlinks, 0); /* UTC calculation stuff */ EXTERN INIT( int MinsFromUTC, 0); EXTERN INIT( int CalculateUTC, 1); diff --git a/src/init.c b/src/init.c index 6533f44d..aeb1e3c7 100644 --- a/src/init.c +++ b/src/init.c @@ -519,11 +519,6 @@ void InitRemind(int argc, char const *argv[]) arg++; continue; } - if (*arg == 'z' || *arg == 'Z') { - LinksInTerminal = 1; - arg++; - continue; - } break; } if (weeks) { diff --git a/src/main.c b/src/main.c index 75dde993..444c6ee5 100644 --- a/src/main.c +++ b/src/main.c @@ -1866,7 +1866,7 @@ FillParagraphWC(char const *s, DynamicBuffer *output) /* A macro safe ONLY if used with arg with no side effects! */ #define ISBLANK(c) (isspace(c) && (c) != '\n') -void FillParagraph(char const *s, DynamicBuffer *output) +void FillParagraph(char const *url, char const *s, DynamicBuffer *output) { int line = 0; @@ -1883,7 +1883,14 @@ void FillParagraph(char const *s, DynamicBuffer *output) while(ISBLANK(*s)) s++; if (!*s) return; + if (url) { + printf("\x1B]8;;%s\x1B\\", url); + } + if (FillParagraphWC(s, output) == OK) { + if (url) { + printf("\x1B]8;;\x1B\\"); + } return; } @@ -1899,6 +1906,9 @@ void FillParagraph(char const *s, DynamicBuffer *output) continue; } if (!*s) { + if (url) { + printf("\x1B]8;;\x1B\\"); + } return; } /* Over here, we're at the beginning of a line. Emit the correct @@ -1933,6 +1943,9 @@ void FillParagraph(char const *s, DynamicBuffer *output) len++; } if (s == t) { + if (url) { + printf("\x1B]8;;\x1B\\"); + } return; } if (!pendspace || len+pendspace <= roomleft) { diff --git a/src/protos.h b/src/protos.h index 8cc14ea3..c4d7a990 100644 --- a/src/protos.h +++ b/src/protos.h @@ -144,7 +144,7 @@ int ParseNonSpaceChar (ParsePtr p, int *err, int peek); unsigned int HashVal_preservecase(char const *str); int DateOK (int y, int m, int d); BuiltinFunc *FindBuiltinFunc (char const *name); -int InsertIntoSortBuffer (int dse, int tim, char const *body, int typ, int prio); +int InsertIntoSortBuffer (int dse, int tim, char const *url, char const *body, int typ, int prio); void IssueSortedReminders (void); UserFunc *FindUserFunc(char const *name); int UserFuncExists (char const *fn); @@ -160,7 +160,7 @@ int GetSysVar (char const *name, Value *val); int SetSysVar (char const *name, Value *val); void DumpSysVarByName (char const *name); int CalcMinsFromUTC (int dse, int tim, int *mins, int *isdst); -void FillParagraph (char const *s, DynamicBuffer *output); +void FillParagraph (char const *url, char const *s, DynamicBuffer *output); void LocalToUTC (int locdate, int loctime, int *utcdate, int *utctime); void UTCToLocal (int utcdate, int utctime, int *locdate, int *loctime); int MoonPhase (int date, int time); diff --git a/src/queue.c b/src/queue.c index 387ecf0d..d015d5fc 100644 --- a/src/queue.c +++ b/src/queue.c @@ -140,7 +140,7 @@ int QueueReminder(ParsePtr p, Trigger *trig, TimeTrig const *tim, char const *sched) { QueuedRem *qelem; - + TrigInfo *ti; if (DontQueue || trig->noqueue || tim->ttime == NO_TIME || @@ -169,8 +169,13 @@ int QueueReminder(ParsePtr p, Trigger *trig, qelem->tt = *tim; qelem->t = *trig; - /* Take over infos */ - trig->infos = NULL; + /* Copy infos */ + qelem->t.infos = NULL; + ti = trig->infos; + while(ti) { + (void) AppendTrigInfo(&qelem->t, ti->info); + ti = ti->next; + } DBufInit(&(qelem->t.tags)); DBufPuts(&(qelem->t.tags), DBufValue(&(trig->tags))); diff --git a/src/sort.c b/src/sort.c index 69b622a8..d6582053 100644 --- a/src/sort.c +++ b/src/sort.c @@ -25,6 +25,7 @@ typedef struct sortrem { struct sortrem *next; char const *text; + char const *url; int trigdate; int trigtime; int typ; @@ -34,7 +35,7 @@ typedef struct sortrem { /* The sorted reminder queue */ static Sortrem *SortedQueue = (Sortrem *) NULL; -static Sortrem *MakeSortRem (int dse, int tim, char const *body, int typ, int prio); +static Sortrem *MakeSortRem (int dse, int tim, char const *url, char const *body, int typ, int prio); static void IssueSortBanner (int dse); /***************************************************************/ @@ -44,23 +45,33 @@ static void IssueSortBanner (int dse); /* Create a new Sortrem entry - return NULL on failure. */ /* */ /***************************************************************/ -static Sortrem *MakeSortRem(int dse, int tim, char const *body, int typ, int prio) +static Sortrem *MakeSortRem(int dse, int tim, char const *url, char const *body, int typ, int prio) { - Sortrem *new = NEW(Sortrem); - if (!new) return NULL; + Sortrem *srem = NEW(Sortrem); + if (!srem) return NULL; - new->text = strdup(body); - if (!new->text) { - free(new); + srem->text = strdup(body); + if (!srem->text) { + free(srem); return NULL; } - - new->trigdate = dse; - new->trigtime = tim; - new->typ = typ; - new->priority = prio; - new->next = NULL; - return new; + if (url) { + srem->url = strdup(url); + if (!srem->url) { + free((char *) srem->text); + free(srem); + return NULL; + } + } else { + srem->url = NULL; + } + + srem->trigdate = dse; + srem->trigtime = tim; + srem->typ = typ; + srem->priority = prio; + srem->next = NULL; + return srem; } /***************************************************************/ @@ -70,13 +81,13 @@ static Sortrem *MakeSortRem(int dse, int tim, char const *body, int typ, int pri /* Insert a reminder into the sort buffer */ /* */ /***************************************************************/ -int InsertIntoSortBuffer(int dse, int tim, char const *body, int typ, int prio) +int InsertIntoSortBuffer(int dse, int tim, char const *url, char const *body, int typ, int prio) { - Sortrem *new = MakeSortRem(dse, tim, body, typ, prio); + Sortrem *srem = MakeSortRem(dse, tim, url, body, typ, prio); Sortrem *cur = SortedQueue, *prev = NULL; int ShouldGoAfter; - if (!new) { + if (!srem) { Eprint("%s", GetErr(E_NO_MEM)); IssueSortedReminders(); SortByDate = 0; @@ -88,11 +99,11 @@ int InsertIntoSortBuffer(int dse, int tim, char const *body, int typ, int prio) /* Find the correct place in the sorted list */ if (!SortedQueue) { - SortedQueue = new; + SortedQueue = srem; return OK; } while (cur) { - ShouldGoAfter = CompareRems(new->trigdate, new->trigtime, new->priority, + ShouldGoAfter = CompareRems(srem->trigdate, srem->trigtime, srem->priority, cur->trigdate, cur->trigtime, cur->priority, SortByDate, SortByTime, SortByPrio, UntimedBeforeTimed); @@ -101,22 +112,21 @@ int InsertIntoSortBuffer(int dse, int tim, char const *body, int typ, int prio) cur = cur->next; } else { if (prev) { - prev->next = new; - new->next = cur; + prev->next = srem; + srem->next = cur; } else { - SortedQueue = new; - new->next = cur; + SortedQueue = srem; + srem->next = cur; } return OK; } - + } - prev->next = new; - new->next = cur; /* For safety - actually redundant */ + prev->next = srem; + srem->next = cur; /* For safety - actually redundant */ return OK; } - /***************************************************************/ /* */ /* IssueSortedReminders */ @@ -141,7 +151,13 @@ void IssueSortedReminders(void) IssueSortBanner(cur->trigdate); olddate = cur->trigdate; } + if (cur->url) { + printf("\x1B]8;;%s\x1B\\", cur->url); + } printf("%s", cur->text); + if (cur->url) { + printf("\x1B]8;;\x1B\\"); + } } break; @@ -150,7 +166,7 @@ void IssueSortedReminders(void) IssueSortBanner(cur->trigdate); olddate = cur->trigdate; } - FillParagraph(cur->text, NULL); + FillParagraph(cur->url, cur->text, NULL); break; case RUN_TYPE: @@ -159,6 +175,9 @@ void IssueSortedReminders(void) } free((char *) cur->text); + if (cur->url) { + free((char *) cur->url); + } free(cur); cur = next; } diff --git a/src/var.c b/src/var.c index fee0288b..2eafd628 100644 --- a/src/var.c +++ b/src/var.c @@ -1109,6 +1109,7 @@ static SysVar SysVarArr[] = { {"Tb", 0, SPECIAL_TYPE, trig_base_func, 0, 0 }, {"Td", 0, SPECIAL_TYPE, trig_day_func, 0, 0 }, {"TerminalBackground", 0, SPECIAL_TYPE, terminal_bg_func, 0, 0 }, + {"TerminalHyperlinks", 1, INT_TYPE, &TerminalHyperlinks, 0, 1 }, {"Thursday", 1, TRANS_TYPE, "Thursday", 0, 0 }, {"TimeSep", 1, SPECIAL_TYPE, time_sep_func, 0, 0 }, {"TimetIs64bit", 0, SPECIAL_TYPE, timet_is_64_func, 0, 0 }, diff --git a/tests/manpage-personal-dict b/tests/manpage-personal-dict index ba8b90f8..3dd88bd0 100644 --- a/tests/manpage-personal-dict +++ b/tests/manpage-personal-dict @@ -402,6 +402,7 @@ Tcl Td Terbeck TerminalBackground +TerminalHyperlinks Thronicke TimeSep TimetIs64bit diff --git a/tests/test-rem b/tests/test-rem index 8ea998e8..fca15b0c 100644 --- a/tests/test-rem +++ b/tests/test-rem @@ -961,6 +961,14 @@ SET $ParseUntriggered 1 REM 1 Jan 1994 MSG 1/0 = [1/0] EOF +# Calendars with hyperlinks +$REMIND -w,0,0 -@2 -c - 1 Jan 2020 <<'EOF' >> $OUT 2>&1 +SET $TerminalHyperlinks 1 +REM 15 INFO "Url: https://dianne.skoll.ca" MSG Hello, linky! +REM 16 INFO "Url: https://dianne.skoll.ca" MSF Hello, linky! +REM 17 INFO "Url: https://dianne.skoll.ca" CAL Hello, linky! +REM 18 INFO "Url: https://dianne.skoll.ca" SPECIAL COLOR 255 0 0 Hello, linky! +EOF cmp -s $OUT $CMP if [ "$?" = "0" ]; then echo "Remind: Acceptance tests ${GRN}PASSED${NRM}" diff --git a/tests/test.cmp b/tests/test.cmp index b1dfbb14..798d53a0 100644 --- a/tests/test.cmp +++ b/tests/test.cmp @@ -16759,7 +16759,10 @@ mbpad(" bad => "ÿ" mbpad("ÿ", "bar", 8, 1) => Invalid multibyte sequence ../tests/test.rem(1891): mbpad(): Invalid multibyte sequence -DynBuf Mallocs: 1114 mallocs; 31872128 bytes +]8;;https://dianne.skoll.ca\Hello, linky! +]8;;\]8;;https://dianne.skoll.ca\Hello, linky! +]8;;\]8;;https://dianne.skoll.ca\Hello, linky! +]8;;\DynBuf Mallocs: 1120 mallocs; 31872640 bytes Variable hash table statistics: Entries: 100146; Buckets: 87719; Non-empty Buckets: 66303 Maxlen: 5; Minlen: 0; Avglen: 1.142; Stddev: 0.878; Avg nonempty len: 1.510 @@ -16781,7 +16784,7 @@ Expression nodes high-water: 302076 Expression nodes leaked: 0 Parse level high-water: 34 Max expr node evaluations per line: 2001 -Total expression node evaluations: 106732 +Total expression node evaluations: 106733 Test 2 @@ -24950,6 +24953,7 @@ $T $Tb $Td $TerminalBackground +$TerminalHyperlinks $Thursday $TimeSep $TimetIs64bit @@ -40154,3 +40158,21 @@ February 28 -stdin-(2): `/': Division by zero -stdin-(2): `/': Division by zero # rem2ps end ++----------------------------------------------------------------------------+ +| January 2020‎ | ++----------+----------+----------+----------+----------+----------+----------+ +| Sunday‎ | Monday‎ | Tuesday‎ |Wednesday‎ | Thursday‎ | Friday‎ | Saturday‎ | ++----------+----------+----------+----------+----------+----------+----------+ +| | | |1 ‎ |2 ‎ |3 ‎ |4 ‎ | ++----------+----------+----------+----------+----------+----------+----------+ +|5 ‎ |6 ‎ |7 ‎ |8 ‎ |9 ‎ |10 ‎ |11 ‎ | ++----------+----------+----------+----------+----------+----------+----------+ +|12 ‎ |13 ‎ |14 ‎ |15 ‎ |16 ‎ |17 ‎ |18 ‎ | +| | | |]8;;https://dianne.skoll.ca\Hello,]8;;\‎ |]8;;https://dianne.skoll.ca\Hello,]8;;\‎ |]8;;https://dianne.skoll.ca\Hello,]8;;\‎ |]8;;https://dianne.skoll.ca\Hello,]8;;\‎ | +| | | |]8;;https://dianne.skoll.ca\linky!]8;;\‎ |]8;;https://dianne.skoll.ca\linky!]8;;\‎ |]8;;https://dianne.skoll.ca\linky!]8;;\‎ |]8;;https://dianne.skoll.ca\linky!]8;;\‎ | ++----------+----------+----------+----------+----------+----------+----------+ +|19 ‎ |20 ‎ |21 ‎ |22 ‎ |23 ‎ |24 ‎ |25 ‎ | ++----------+----------+----------+----------+----------+----------+----------+ +|26 ‎ |27 ‎ |28 ‎ |29 ‎ |30 ‎ |31 ‎ | | ++----------+----------+----------+----------+----------+----------+----------+ + \ No newline at end of file diff --git a/tests/test.rem b/tests/test.rem index 1f33a7c9..f40e305b 100644 --- a/tests/test.rem +++ b/tests/test.rem @@ -1891,6 +1891,11 @@ set a mbpad(bad, "bar", 8) set a mbpad(bad, "bar", 8, 1) DEBUG -x +SET $TerminalHyperlinks 1 +REM INFO "Url: https://dianne.skoll.ca" MSG Hello, linky! +REM INFO "Url: https://dianne.skoll.ca" MSF Hello, linky! +REM INFO "Url: https://dianne.skoll.ca" SPECIAL COLOR 255 0 0 Hello, linky! + # Don't want Remind to queue reminders EXIT