From c118c594e3689bac3c6a083ecc05c1866e65927c Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Wed, 27 Jan 2021 00:15:46 -0500 Subject: [PATCH] Add toggleable display of timestamps --- catgirl.1 | 13 +++++++- chat.c | 13 ++++++++ chat.h | 6 ++++ ui.c | 95 ++++++++++++++++++++++++++++++++++++++++--------------- 4 files changed, 101 insertions(+), 26 deletions(-) diff --git a/catgirl.1 b/catgirl.1 index b09d55e..d40b377 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -1,4 +1,4 @@ -.Dd January 25, 2021 +.Dd January 26, 2021 .Dt CATGIRL 1 .Os . @@ -15,6 +15,7 @@ .Op Fl N Ar notify .Op Fl O Ar open .Op Fl S Ar bind +.Op Fl T Ar timestamp .Op Fl a Ar plain .Op Fl c Ar cert .Op Fl h Ar host @@ -181,6 +182,14 @@ Bind to source address .Ar host when connecting to the server. . +.It Fl T Ar format , Cm timestamp Op = Ar format +Show timestamps by default, +in the specified +.Xr strftime 3 +.Ar format . +The default format is +.Qq \&%T . +. .It Fl a Ar user : Ns Ar pass , Cm sasl-plain = Ar user : Ns Ar pass Authenticate as .Ar user @@ -640,6 +649,8 @@ Insert a blank line in the window. Scroll to next highlight. .It Ic M-p Scroll to previous highlight. +.It Ic M-t +Toggle timestamps. .It Ic M-u Scroll to first unread line. .It Ic M-v diff --git a/chat.c b/chat.c index 6458925..7de427c 100644 --- a/chat.c +++ b/chat.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #include @@ -124,6 +125,16 @@ static void parseHash(char *str) { if (*str) hashBound = strtoul(&str[1], NULL, 0); } +static void parseTimestamp(const char *format) { + uiTime.enable = true; + if (!format) return; + char buf[TimeCap]; + uiTime.format = format; + struct tm *time = localtime(&(time_t) { -22100400 }); + uiTime.width = strftime(buf, sizeof(buf), format, time); + if (!uiTime.width) errx(EX_USAGE, "invalid timestamp format: %s", format); +} + #ifdef __OpenBSD__ static void unveilConfig(const char *name) { @@ -197,6 +208,7 @@ int main(int argc, char *argv[]) { { .val = 'O', .name = "open", required_argument }, { .val = 'R', .name = "restrict", no_argument }, { .val = 'S', .name = "bind", required_argument }, + { .val = 'T', .name = "timestamp", optional_argument }, { .val = 'a', .name = "sasl-plain", required_argument }, { .val = 'c', .name = "cert", required_argument }, { .val = 'e', .name = "sasl-external", no_argument }, @@ -234,6 +246,7 @@ int main(int argc, char *argv[]) { break; case 'O': utilPush(&urlOpenUtil, optarg); break; case 'R': self.restricted = true; break; case 'S': bind = optarg; + break; case 'T': parseTimestamp(optarg); break; case 'a': sasl = true; self.plain = optarg; break; case 'c': cert = optarg; break; case 'e': sasl = true; diff --git a/chat.h b/chat.h index 6ecd91a..d3647e7 100644 --- a/chat.h +++ b/chat.h @@ -279,6 +279,12 @@ const char *commandIsAction(uint id, const char *input); void commandCompleteAdd(void); enum Heat { Ice, Cold, Warm, Hot }; +enum { TimeCap = 64 }; +extern struct Time { + bool enable; + const char *format; + size_t width; +} uiTime; extern struct Util uiNotifyUtil; void uiInitEarly(void); void uiInitLate(void); diff --git a/ui.c b/ui.c index 5d3f070..5997571 100644 --- a/ui.c +++ b/ui.c @@ -69,6 +69,11 @@ enum { #define RIGHT (COLS - 1) #define MAIN_LINES (LINES - StatusLines - InputLines) +struct Time uiTime = { + .format = "%T", + .width = 8, +}; + static WINDOW *status; static WINDOW *main; static WINDOW *input; @@ -78,6 +83,7 @@ struct Window { int scroll; bool mark; bool mute; + bool time; enum Heat thresh; enum Heat heat; uint unreadSoft; @@ -129,15 +135,13 @@ static uint windowFor(uint id) { for (uint num = 0; num < windows.len; ++num) { if (windows.ptrs[num]->id == id) return num; } - struct Window *window = calloc(1, sizeof(*window)); if (!window) err(EX_OSERR, "malloc"); - window->id = id; window->mark = true; + window->time = uiTime.enable; window->thresh = Cold; window->buffer = bufferAlloc(); - return windowPush(window); } @@ -199,6 +203,7 @@ static short colorPair(short fg, short bg) { X(KeyMetaN, "\33n", NULL) \ X(KeyMetaP, "\33p", NULL) \ X(KeyMetaQ, "\33q", NULL) \ + X(KeyMetaT, "\33t", NULL) \ X(KeyMetaU, "\33u", NULL) \ X(KeyMetaV, "\33v", NULL) \ X(KeyMetaEnter, "\33\r", "\33\n") \ @@ -466,12 +471,36 @@ static size_t windowBottom(const struct Window *window) { return bottom; } -static void mainAdd(int y, const char *str) { +static int windowCols(const struct Window *window) { + if (!window->time) return COLS; + return COLS - (uiTime.width + 1); +} + +static void mainAdd(int y, bool time, const struct Line *line) { int ny, nx; wmove(main, y, 0); - styleAdd(main, str); + if (!line || !line->str[0]) { + wclrtoeol(main); + return; + } + if (time && line->time) { + char buf[TimeCap]; + strftime(buf, sizeof(buf), uiTime.format, localtime(&line->time)); + wattr_set( + main, + colorAttr(Colors[Gray]), colorPair(Colors[Gray], -1), + NULL + ); + waddstr(main, buf); + waddch(main, ' '); + } else if (time) { + whline(main, ' ', uiTime.width + 1); + wmove(main, y, uiTime.width + 1); + } + styleAdd(main, line->str); getyx(main, ny, nx); - if (ny == y) wclrtoeol(main); + if (ny != y) return; + wclrtoeol(main); (void)nx; } @@ -481,16 +510,14 @@ static void mainUpdate(void) { int y = 0; int marker = MAIN_LINES - SplitLines - MarkerLines; for (size_t i = windowTop(window); i < BufferCap; ++i) { - const struct Line *line = bufferHard(window->buffer, i); - mainAdd(y++, (line ? line->str : "")); + mainAdd(y++, window->time, bufferHard(window->buffer, i)); if (window->scroll && y == marker) break; } if (!window->scroll) return; y = MAIN_LINES - SplitLines; for (size_t i = BufferCap - SplitLines; i < BufferCap; ++i) { - const struct Line *line = bufferHard(window->buffer, i); - mainAdd(y++, (line ? line->str : "")); + mainAdd(y++, window->time, bufferHard(window->buffer, i)); } wattr_set(main, A_NORMAL, 0, NULL); mvwhline(main, marker, 0, ACS_BULLET, COLS); @@ -540,7 +567,10 @@ void uiWrite(uint id, enum Heat heat, const time_t *src, const char *str) { } if (window->mark && heat > Cold) { if (!window->unreadWarm++) { - int lines = bufferPush(window->buffer, COLS, false, Warm, ts, ""); + int lines = bufferPush( + window->buffer, windowCols(window), + window->thresh, Warm, ts, "" + ); if (window->scroll) windowScroll(window, lines); if (window->unreadSoft > 1) { window->unreadSoft++; @@ -550,7 +580,10 @@ void uiWrite(uint id, enum Heat heat, const time_t *src, const char *str) { if (heat > window->heat) window->heat = heat; statusUpdate(); } - int lines = bufferPush(window->buffer, COLS, window->thresh, heat, ts, str); + int lines = bufferPush( + window->buffer, windowCols(window), + window->thresh, heat, ts, str + ); window->unreadHard += lines; if (window->scroll) windowScroll(window, lines); if (window == windows.ptrs[windows.show]) mainUpdate(); @@ -583,7 +616,8 @@ static void windowReflow(struct Window *window) { const struct Line *line = bufferHard(window->buffer, windowTop(window)); if (line) num = line->num; window->unreadHard = bufferReflow( - window->buffer, COLS, window->thresh, window->unreadSoft + window->buffer, windowCols(window), + window->thresh, window->unreadSoft ); if (!window->scroll || !num) return; for (size_t i = 0; i < BufferCap; ++i) { @@ -595,12 +629,12 @@ static void windowReflow(struct Window *window) { } static void resize(void) { - statusUpdate(); wclear(main); wresize(main, MAIN_LINES, COLS); for (uint num = 0; num < windows.len; ++num) { windowReflow(windows.ptrs[num]); } + statusUpdate(); mainUpdate(); } @@ -620,13 +654,10 @@ static void windowList(const struct Window *window) { continue; } - struct tm *tm = localtime(&line->time); - if (!tm) err(EX_OSERR, "localtime"); - - char buf[sizeof("00:00:00")]; - strftime(buf, sizeof(buf), "%T", tm); + char buf[TimeCap]; + strftime(buf, sizeof(buf), uiTime.format, localtime(&line->time)); vid_attr(colorAttr(Colors[Gray]), colorPair(Colors[Gray], -1), NULL); - printf("[%s] ", buf); + printf("%s ", buf); bool align = false; struct Style style = StyleDefault; @@ -679,7 +710,7 @@ static void inputAdd(struct Style *style, const char *str) { static void inputUpdate(void) { size_t pos; char *buf = editBuffer(&pos); - uint id = windows.ptrs[windows.show]->id; + struct Window *window = windows.ptrs[windows.show]; const char *prefix = ""; const char *prompt = self.nick; @@ -688,9 +719,9 @@ static void inputUpdate(void) { struct Style stylePrompt = { .fg = self.color, .bg = Default }; struct Style styleInput = StyleDefault; - const char *privmsg = commandIsPrivmsg(id, buf); - const char *notice = commandIsNotice(id, buf); - const char *action = commandIsAction(id, buf); + const char *privmsg = commandIsPrivmsg(window->id, buf); + const char *notice = commandIsNotice(window->id, buf); + const char *action = commandIsAction(window->id, buf); if (privmsg) { prefix = "<"; suffix = "> "; skip = privmsg; @@ -703,7 +734,7 @@ static void inputUpdate(void) { stylePrompt.attr |= Italic; styleInput.attr |= Italic; skip = action; - } else if (id == Debug && buf[0] != '/') { + } else if (window->id == Debug && buf[0] != '/') { prompt = "<< "; stylePrompt.fg = Gray; } else { @@ -716,6 +747,10 @@ static void inputUpdate(void) { int y, x; wmove(input, 0, 0); + if (window->time && window->id != Network) { + whline(input, ' ', uiTime.width + 1); + wmove(input, 0, uiTime.width + 1); + } wattr_set(input, styleAttr(stylePrompt), stylePair(stylePrompt), NULL); waddstr(input, prefix); waddstr(input, prompt); @@ -807,6 +842,14 @@ static void scrollSearch(struct Window *window, const char *str, int dir) { } } +static void toggleTime(struct Window *window) { + window->time ^= true; + windowReflow(window); + statusUpdate(); + mainUpdate(); + inputUpdate(); +} + static void incThresh(struct Window *window, int n) { if (n > 0 && window->thresh == Hot) return; if (n < 0 && window->thresh == Ice) { @@ -815,6 +858,7 @@ static void incThresh(struct Window *window, int n) { window->thresh += n; } windowReflow(window); + statusUpdate(); mainUpdate(); statusUpdate(); } @@ -874,6 +918,7 @@ static void keyCode(int code) { break; case KeyMetaN: scrollHot(window, +1); break; case KeyMetaP: scrollHot(window, -1); break; case KeyMetaQ: edit(id, EditCollapse, 0); + break; case KeyMetaT: toggleTime(window); break; case KeyMetaU: scrollTo(window, window->unreadHard); break; case KeyMetaV: scrollPage(window, +1);