Re: cal: add option to highlight the current day
Johannes Thyssen Tishman <[email protected]> Fri, 31 Jul 2026 20:48:52 +0000
| Newsgroups | gmane.os.openbsd.tech |
|---|---|
| Message-ID | <[email protected]> |
2026-07-05T10:38:19+0000 Johannes Thyssen Tishman <[email protected]>: > 2026-07-04T09:41:36+0000 Job Snijders <[email protected]>: > > Hi Johannes, > > > > Thanks! I've longed for this functionality for some time now. Some > > comments below. > > Thanks for the feedback Job. I've answered a couple of your comments > inline and addressed all of them in the patch below. Is this better? > > > On Fri, Jul 03, 2026 at 04:37:29PM +0000, Johannes Thyssen Tishman wrote: > > > Please find below a patch to add an option (-h) to cal to allow > > > highlighting the current day. > > > > > > For testing purposes, the -h flag takes an optional argument specifying > > > the date to highlight, e.g., cal -h2026-10-10 oct. I intend to remove > > > this before committing if the patch is accepted. > > > > Since the program tests whether standout highlighting can be used, to > > me it would make most sense to highlight the current day by default. > > Highlighting of arbitrary days seems less useful. I suggest remove '-h' > > (bonus: it reduces the size of patch). > > The optional argument of the -h flag was mostly so testers could check > that the highlight was working for other days other than the current one > without having to change the system date. As mentioned above, my > intention was to drop this before committing. > > I've now updated the patch to highlight the current day by default. > However, this now conflicts with what Patrick wants. Can we find a > compromise here? I'm not sure I like an option to *disable* the > highlight. I'd prefer an option to *enable* it or make it the default > and have no option at all. > > > [...] > > > > void > > > +highlight_day(char *p, int day, int *hl_len) > > > +{ > > > + const char *term_so, *term_se; > > > + char cbuf[512]; > > > + char tbuf[1024], *b; > > > + > > > + term_se = term_so = NULL; > > > + > > > + /* > > > + * why NULL as name here? > > > + * termcap(3) doesn't seem document this > > > + */ > > > From termcap(3): > > > > The tgetstr routine returns the string entry for id, or zero if it is not > > available. Use tputs to output the returned string. The area parameter > > is used as follows: > > > > * It is assumed to be the address of a pointer to a buffer managed > > by the calling application. > > > > * However, ncurses checks to ensure that area is not NULL, and also > > that the resulting buffer pointer is not NULL. If either check > > fails, the area parameter is ignored. > > > > * If the checks succeed, ncurses also copies the return value to the > > buffer pointed to by area, and the area value will be updated to > > point past the null ending this value. > > > > * The return value itself is an address in the terminal description > > which is loaded into memory. > > Right, this is why I'm asking about the use of cbuf and b, which are not > used here or in the FreeBSD implementation, instead of just setting area > to NULL for tgetstr (I've now done this in the revised patch below). > However, I still don't understand why NULL for the second parameter of > tgetent. AFAIC, that is not described in termcap(3). > > > > + if (tgetent(tbuf, NULL) == 1) { > > > + b = cbuf; > > > + > > > + /* > > > + * why not NULL here for area instead of &b? > > > + * cbuf is never used > > > + */ > > > + term_so = tgetstr("so", &b); > > > + term_se = tgetstr("se", &b); > > > + } ping Index: Makefile =================================================================== RCS file: /cvs/src/usr.bin/cal/Makefile,v diff -u -p -r1.3 Makefile --- Makefile 21 Sep 1997 11:48:29 -0000 1.3 +++ Makefile 31 Jul 2026 20:47:00 -0000 @@ -1,5 +1,6 @@ # $OpenBSD: Makefile,v 1.3 1997/09/21 11:48:29 deraadt Exp $ -PROG= cal +PROG= cal +LDADD+= -lcurses .include <bsd.prog.mk> Index: cal.c =================================================================== RCS file: /cvs/src/usr.bin/cal/cal.c,v diff -u -p -r1.36 cal.c --- cal.c 2 Jul 2026 20:40:53 -0000 1.36 +++ cal.c 31 Jul 2026 20:47:00 -0000 @@ -42,6 +42,8 @@ #include <string.h> #include <time.h> #include <unistd.h> +#include <curses.h> +#include <term.h> #define THURSDAY 4 /* for reformation */ #define SATURDAY 6 /* 1 Jan 1 was a Saturday */ @@ -127,8 +129,14 @@ const char *day_headings = NULL; int julian; int mflag = 0; int wflag = 0; +int highlight = 0; +int highlight_len = 0; +const char *term_so = NULL; +const char *term_se = NULL; +struct tm *lt; void ascii_day(char *, int); +void highlight_day(char *, int); void center(const char *, int, int); void day_array(int, int, int *); int day_in_week(int, int, int); @@ -141,15 +149,18 @@ void trim_trailing_spaces(char *); void usage(void); void yearly(int); int parsemonth(const char *); +int is_today(int, int, int); +int can_highlight(void); int main(int argc, char *argv[]) { - struct tm *local_time; time_t now; int ch, month, year, yflag; const char *errstr; + highlight = can_highlight(); + if (pledge("stdio", NULL) == -1) err(1, "pledge"); @@ -190,6 +201,13 @@ main(int argc, char *argv[]) day_headings = DAY_HEADINGS_JS; } + (void)time(&now); + lt = localtime(&now); + if (lt == NULL) { + perror("localtime"); + exit(1); + } + month = 0; switch(argc) { case 2: @@ -202,9 +220,7 @@ main(int argc, char *argv[]) if (yflag) errx(1, "specifying a month conflicts with -y"); month = parsemonth(*argv); - (void)time(&now); - local_time = localtime(&now); - year = local_time->tm_year + 1900; + year = lt->tm_year + 1900; } else { year = strtonum(*argv, 1, 9999, &errstr); if (errstr) @@ -212,11 +228,9 @@ main(int argc, char *argv[]) } break; case 0: - (void)time(&now); - local_time = localtime(&now); - year = local_time->tm_year + 1900; + year = lt->tm_year + 1900; if (!yflag) - month = local_time->tm_mon + 1; + month = lt->tm_mon + 1; break; default: usage(); @@ -298,8 +312,13 @@ isoweek(int day, int month, int year) void monthly(int month, int year) { - int col, row, len, days[MAXDAYS], firstday; - char *p, lineout[30]; + int col, row, len, hl_len, d, days[MAXDAYS], firstday; + + /* + * At least 9 bytes for standout mode escape sequence. What is a safe + * size for this? Could this differ from terminal to terminal? + */ + char *p, lineout[48]; day_array(month, year, days); (void)snprintf(lineout, sizeof(lineout), "%s %d", @@ -310,15 +329,22 @@ monthly(int month, int year) lineout, day_headings); for (row = 0; row < 6; row++) { firstday = SPACE; + hl_len = 0; for (col = 0, p = lineout; col < 7; col++, p += julian ? J_DAY_LEN : DAY_LEN) { - if (firstday == SPACE && days[row * 7 + col] != SPACE) - firstday = days[row * 7 + col]; - ascii_day(p, days[row * 7 + col]); + d = days[row * 7 + col]; + if (firstday == SPACE && d != SPACE) + firstday = d; + if (is_today(d, month, year) && highlight) { + highlight_day(p, d); + hl_len = highlight_len; + p += hl_len; + } else + ascii_day(p, d); } *p = '\0'; trim_trailing_spaces(lineout); - (void)printf("%-20s", lineout); + (void)printf("%-*s", 20 + hl_len, lineout); if (wflag && firstday != SPACE) printf(" [%2d]", week(firstday, month, year)); printf("\n"); @@ -328,8 +354,13 @@ monthly(int month, int year) void j_yearly(int year) { - int col, *dp, i, month, row, which_cal; + int col, *dp, i, month, row, which_cal, hl_len; int days[12][MAXDAYS]; + + /* + * At least 9 bytes for standout mode escape sequence. What is a safe + * size for this? Could this differ from terminal to terminal? + */ char *p, lineout[80]; (void)snprintf(lineout, sizeof(lineout), "%d", year); @@ -346,15 +377,32 @@ j_yearly(int year) J_HEAD_SEP, "", day_headings); for (row = 0; row < 6; row++) { + hl_len = 0; for (which_cal = 0; which_cal < 2; which_cal++) { p = lineout + which_cal * (J_WEEK_LEN + 2); + p += hl_len; dp = &days[month + which_cal][row * 7]; - for (col = 0; col < 7; col++, p += J_DAY_LEN) - ascii_day(p, *dp++); + for (col = 0; col < 7; col++, p += J_DAY_LEN) { + if (is_today(*dp, month + which_cal + 1, + year) && highlight) { + highlight_day(p, *dp++); + hl_len = highlight_len; + p += hl_len; + } else + ascii_day(p, *dp++); + } } *p = '\0'; trim_trailing_spaces(lineout); (void)printf("%s\n", lineout); + + /* + * Highlighting a day causes escape sequences to + * misalign the rows. Rather than tracking offsets to + * overwrite rows day-by-day, clear the row. + */ + if (highlight) + (void)memset(lineout, ' ', sizeof(lineout) - 1); } } (void)printf("\n"); @@ -363,9 +411,14 @@ j_yearly(int year) void yearly(int year) { - int col, *dp, i, month, row, which_cal, week_len, wn, firstday; + int col, *dp, i, month, row, which_cal, week_len, hl_len, wn, firstday; int days[12][MAXDAYS]; - char *p, lineout[81]; + + /* + * At least 9 bytes for standout mode escape sequence. What is a safe + * size for this? Could this differ from terminal to terminal? + */ + char *p, lineout[96]; week_len = WEEK_LEN; if (wflag) @@ -386,15 +439,23 @@ yearly(int year) HEAD_SEP + (wflag ? WEEKNUMBER_LEN : 0), "", day_headings); for (row = 0; row < 6; row++) { + hl_len = 0; for (which_cal = 0; which_cal < 3; which_cal++) { p = lineout + which_cal * (week_len + 2); + p += hl_len; dp = &days[month + which_cal][row * 7]; firstday = SPACE; for (col = 0; col < 7; col++, p += DAY_LEN) { if (firstday == SPACE && *dp != SPACE) firstday = *dp; - ascii_day(p, *dp++); + if (is_today(*dp, month + which_cal + 1, + year) && highlight) { + highlight_day(p, *dp++); + hl_len = highlight_len; + p += hl_len; + } else + ascii_day(p, *dp++); } if (wflag && firstday != SPACE) { wn = week(firstday, @@ -408,6 +469,14 @@ yearly(int year) *p = '\0'; trim_trailing_spaces(lineout); (void)printf("%s\n", lineout); + + /* + * Highlighting a day causes escape sequences to + * misalign the rows. Rather than tracking offsets to + * overwrite rows day-by-day, clear the row. + */ + if (highlight) + (void)memset(lineout, ' ', sizeof(lineout) - 1); } } (void)printf("\n"); @@ -514,6 +583,23 @@ ascii_day(char *p, int day) } void +highlight_day(char *p, int day) +{ + /* highlight on */ + memcpy(p, term_so, strlen(term_so)); + p += strlen(term_so); + + /* the actual text */ + ascii_day(p, day); + p += (julian ? J_DAY_LEN : DAY_LEN) - 1; + + /* highlight off */ + memcpy(p, term_se, strlen(term_se)); + p += strlen(term_se); + *p = ' '; +} + +void trim_trailing_spaces(char *s) { char *p; @@ -560,4 +646,29 @@ parsemonth(const char *s) if (v <= 0 || v > 12) errx(1, "invalid month: use 1-12 or a name"); return (v); +} + +int +is_today(int day, int month, int year) +{ + return (year == lt->tm_year + 1900 && month == lt->tm_mon + 1 && + day == (julian ? lt->tm_yday + 1 : lt->tm_mday)); +} + +int +can_highlight(void) +{ + char tbuf[1024]; + + if (!isatty(STDOUT_FILENO)) + return 0; + if (tgetent(tbuf, NULL) == 1) { + term_so = tgetstr("so", NULL); + term_se = tgetstr("se", NULL); + } + if (term_so != NULL && term_se != NULL) { + highlight_len = strlen(term_so) + strlen(term_se); + return 1; + } + return 0; }