From e5363bcae0f726455fb4198cd21d46721ad5e39a Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 1 Feb 2020 19:37:48 -0500 Subject: Implement the beginnings of UI It takes so much code to do anything in curses... --- ui.c | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 ui.c (limited to 'ui.c') diff --git a/ui.c b/ui.c new file mode 100644 index 0000000..0295c8d --- /dev/null +++ b/ui.c @@ -0,0 +1,174 @@ +/* Copyright (C) 2020 C. McEnroe + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "chat.h" + +#ifndef A_ITALIC +#define A_ITALIC A_UNDERLINE +#endif + +#define BOTTOM (LINES - 1) +#define RIGHT (COLS - 1) +#define WINDOW_LINES (LINES - 2) + +static short colorPairs; + +static void colorInit(void) { + start_color(); + use_default_colors(); + for (short pair = 0; pair < 16; ++pair) { + init_pair(1 + pair, pair % COLORS, -1); + } + colorPairs = 17; +} + +static attr_t colorAttr(short fg) { + return (fg >= COLORS ? A_BOLD : A_NORMAL); +} + +static short colorPair(short fg, short bg) { + if (bg == -1) return 1 + fg; + for (short pair = 17; pair < colorPairs; ++pair) { + short f, b; + pair_content(pair, &f, &b); + if (f == fg && b == bg) return pair; + } + init_pair(colorPairs, fg, bg); + return colorPairs++; +} + +enum { + InputCols = 512, + PadLines = 512, +}; + +static WINDOW *status; +static WINDOW *input; + +struct Window { + size_t id; + WINDOW *pad; + enum Heat heat; + int unread; + int scroll; + bool mark; + struct Window *prev; + struct Window *next; +}; + +static struct { + struct Window *active; + struct Window *other; + struct Window *head; + struct Window *tail; +} windows; + +static void windowAdd(struct Window *window) { + if (windows.tail) windows.tail->next = window; + window->prev = windows.tail; + window->next = NULL; + windows.tail = window; + if (!windows.head) windows.head = window; +} + +static void windowRemove(struct Window *window) { + if (window->prev) window->prev->next = window->next; + if (window->next) window->next->prev = window->prev; + if (windows.head == window) windows.head = window->next; + if (windows.tail == window) windows.tail = window->prev; +} + +static struct Window *windowFor(size_t id) { + struct Window *window; + for (window = windows.head; window; window = window->next) { + if (window->id == id) return window; + } + window = malloc(sizeof(*window)); + if (!window) err(EX_OSERR, "malloc"); + window->id = id; + window->pad = newpad(PadLines, COLS); + wsetscrreg(window->pad, 0, PadLines - 1); + scrollok(window->pad, true); + wmove(window->pad, PadLines - 1, 0); + window->heat = Cold; + window->unread = 0; + window->scroll = PadLines; + window->mark = true; + windowAdd(window); + return window; +} + +void uiInit(void) { + initscr(); + cbreak(); + noecho(); + termInit(); + termNoFlow(); + def_prog_mode(); + colorInit(); + status = newwin(1, COLS, 0, 0); + input = newpad(1, InputCols); + keypad(input, true); + nodelay(input, true); + windows.active = windowFor(Network); +} + +void uiDraw(void) { + wnoutrefresh(status); + pnoutrefresh( + windows.active->pad, + windows.active->scroll - WINDOW_LINES, 0, + 1, 0, + BOTTOM - 1, RIGHT + ); + // TODO: Input scrolling. + pnoutrefresh( + input, + 0, 0, + BOTTOM, 0, + BOTTOM, RIGHT + ); + doupdate(); +} + +void uiWrite(size_t id, enum Heat heat, const struct tm *time, const char *str) { + (void)time; + struct Window *window = windowFor(id); + waddch(window->pad, '\n'); + waddstr(window->pad, str); +} + +void uiFormat( + size_t id, enum Heat heat, const struct tm *time, const char *format, ... +) { + char buf[1024]; + va_list ap; + va_start(ap, format); + int len = vsnprintf(buf, sizeof(buf), format, ap); + va_end(ap); + assert((size_t)len < sizeof(buf)); + uiWrite(id, heat, time, buf); +} -- cgit 1.4.0 From cd3dc4ef4caaad3a696ad731c197f50105119b31 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 1 Feb 2020 21:57:11 -0500 Subject: Parse IRC styling in UI Wow the colorPair thing actually works. Have I finally cracked curses colors? --- chat.c | 2 +- ui.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 74 insertions(+), 3 deletions(-) (limited to 'ui.c') diff --git a/chat.c b/chat.c index 47227d5..75cca62 100644 --- a/chat.c +++ b/chat.c @@ -76,7 +76,7 @@ int main(int argc, char *argv[]) { ircConfig(insecure, cert, priv); uiInit(); - uiFormat(Network, Cold, NULL, "Traveling..."); + uiFormat(Network, Cold, NULL, C "3Trave" U "ling" U C "0,3.." C "0,4."); uiDraw(); int irc = ircConnect(host, port); diff --git a/ui.c b/ui.c index 0295c8d..83c4bc7 100644 --- a/ui.c +++ b/ui.c @@ -15,12 +15,14 @@ */ #include +#include #include #include #include #include #include #include +#include #include #include @@ -56,7 +58,7 @@ static short colorPair(short fg, short bg) { pair_content(pair, &f, &b); if (f == fg && b == bg) return pair; } - init_pair(colorPairs, fg, bg); + init_pair(colorPairs, fg % COLORS, bg % COLORS); return colorPairs++; } @@ -154,11 +156,80 @@ void uiDraw(void) { doupdate(); } +struct Style { + attr_t attr; + enum Color fg, bg; +}; +static const struct Style Reset = { A_NORMAL, Default, Default }; + +static short mapColor(enum Color color) { + switch (color) { + break; case White: return 8 + COLOR_WHITE; + break; case Black: return 0 + COLOR_BLACK; + break; case Blue: return 0 + COLOR_BLUE; + break; case Green: return 0 + COLOR_GREEN; + break; case Red: return 8 + COLOR_RED; + break; case Brown: return 0 + COLOR_RED; + break; case Magenta: return 0 + COLOR_MAGENTA; + break; case Orange: return 0 + COLOR_YELLOW; + break; case Yellow: return 8 + COLOR_YELLOW; + break; case LightGreen: return 8 + COLOR_GREEN; + break; case Cyan: return 0 + COLOR_CYAN; + break; case LightCyan: return 8 + COLOR_CYAN; + break; case LightBlue: return 8 + COLOR_BLUE; + break; case Pink: return 8 + COLOR_MAGENTA; + break; case Gray: return 8 + COLOR_BLACK; + break; case LightGray: return 0 + COLOR_WHITE; + break; default: return -1; + } +} + +static void styleParse(struct Style *style, const char **str, size_t *len) { + switch (**str) { + break; case '\2': (*str)++; style->attr ^= A_BOLD; + break; case '\17': (*str)++; *style = Reset; + break; case '\26': (*str)++; style->attr ^= A_REVERSE; + break; case '\35': (*str)++; style->attr ^= A_ITALIC; + break; case '\37': (*str)++; style->attr ^= A_UNDERLINE; + break; case '\3': { + (*str)++; + if (!isdigit(**str)) { + style->fg = Default; + style->bg = Default; + break; + } + style->fg = *(*str)++ - '0'; + if (isdigit(**str)) style->fg = style->fg * 10 + *(*str)++ - '0'; + if ((*str)[0] != ',' || !isdigit((*str)[1])) break; + (*str)++; + style->bg = *(*str)++ - '0'; + if (isdigit(**str)) style->bg = style->bg * 10 + *(*str)++ - '0'; + } + } + *len = strcspn(*str, "\2\3\17\26\35\37"); +} + +static void styleAdd(WINDOW *win, const char *str) { + size_t len; + struct Style style = Reset; + while (*str) { + styleParse(&style, &str, &len); + wattr_set( + win, + style.attr | colorAttr(mapColor(style.fg)), + colorPair(mapColor(style.fg), mapColor(style.bg)), + NULL + ); + waddnstr(win, str, len); + str += len; + } +} + void uiWrite(size_t id, enum Heat heat, const struct tm *time, const char *str) { (void)time; struct Window *window = windowFor(id); waddch(window->pad, '\n'); - waddstr(window->pad, str); + styleAdd(window->pad, str); } void uiFormat( -- cgit 1.4.0 From 05256b68fef9d9b64b01afb60de31f9c47b60ca1 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 1 Feb 2020 22:40:55 -0500 Subject: Implement word wrap This actually wasn't that bad? --- chat.c | 14 +++++++++++++- ui.c | 22 ++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) (limited to 'ui.c') diff --git a/chat.c b/chat.c index 75cca62..ceaf1b5 100644 --- a/chat.c +++ b/chat.c @@ -76,7 +76,19 @@ int main(int argc, char *argv[]) { ircConfig(insecure, cert, priv); uiInit(); - uiFormat(Network, Cold, NULL, C "3Trave" U "ling" U C "0,3.." C "0,4."); + uiFormat(Network, Cold, NULL, C "3Trave" U "ling" U C "0,3.." C "0,4." R); + uiFormat( + Network, Cold, NULL, + "Jackdaws love my big sphinx of quartz. " + "The quick brown fox jumps over the lazy dog. " + "Jackdaws love my big sphinx of quartz. " + "Jackdaws love my big sphinx of quartz. " + "Jackdaws love my big sphinx of quartz. " + "The quick brown fox jumps over the lazy dog. " + "The quick brown fox jumps over the lazy dog. " + "Jackdaws love my big sphinx of quartz. " + "Jackdaws love my big sphinx of quartz. " + ); uiDraw(); int irc = ircConnect(host, port); diff --git a/ui.c b/ui.c index 83c4bc7..7ce0257 100644 --- a/ui.c +++ b/ui.c @@ -209,11 +209,33 @@ static void styleParse(struct Style *style, const char **str, size_t *len) { *len = strcspn(*str, "\2\3\17\26\35\37"); } +static int wordWidth(const char *str) { + size_t len = strcspn(str, " "); + // TODO: wcswidth. + return len; +} + static void styleAdd(WINDOW *win, const char *str) { + int _, x, width; + getmaxyx(win, _, width); + size_t len; struct Style style = Reset; while (*str) { + if (*str == ' ') { + const char *word = &str[strspn(str, " ")]; + getyx(win, _, x); + if (width - x - 1 < wordWidth(word)) { + waddch(win, '\n'); + str = word; + } + } + styleParse(&style, &str, &len); + size_t sp = strspn(str, " "); + sp += strcspn(&str[sp], " "); + if (sp < len) len = sp; + wattr_set( win, style.attr | colorAttr(mapColor(style.fg)), -- cgit 1.4.0 From c799310d67b825f2aacf7b573f23991654d1e6c4 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 01:54:51 -0500 Subject: Implement wordWidth --- ui.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 7ce0257..7b1e339 100644 --- a/ui.c +++ b/ui.c @@ -25,6 +25,8 @@ #include #include #include +#include +#include #include "chat.h" @@ -112,7 +114,6 @@ static struct Window *windowFor(size_t id) { if (!window) err(EX_OSERR, "malloc"); window->id = id; window->pad = newpad(PadLines, COLS); - wsetscrreg(window->pad, 0, PadLines - 1); scrollok(window->pad, true); wmove(window->pad, PadLines - 1, 0); window->heat = Cold; @@ -211,20 +212,28 @@ static void styleParse(struct Style *style, const char **str, size_t *len) { static int wordWidth(const char *str) { size_t len = strcspn(str, " "); - // TODO: wcswidth. - return len; + int width = 0; + while (len) { + wchar_t wc; + int n = mbtowc(&wc, str, len); + if (n < 1) return width + len; + width += (iswprint(wc) ? wcwidth(wc) : 0); + str += n; + len -= n; + } + return width; } static void styleAdd(WINDOW *win, const char *str) { - int _, x, width; - getmaxyx(win, _, width); + int y, x, width; + getmaxyx(win, y, width); size_t len; struct Style style = Reset; while (*str) { if (*str == ' ') { + getyx(win, y, x); const char *word = &str[strspn(str, " ")]; - getyx(win, _, x); if (width - x - 1 < wordWidth(word)) { waddch(win, '\n'); str = word; -- cgit 1.4.0 From c18dc35377d88f331de15ea4c6e1ab41505d30df Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 02:04:08 -0500 Subject: Wrap before the very edge of the screen --- ui.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 7b1e339..11ee426 100644 --- a/ui.c +++ b/ui.c @@ -234,7 +234,7 @@ static void styleAdd(WINDOW *win, const char *str) { if (*str == ' ') { getyx(win, y, x); const char *word = &str[strspn(str, " ")]; - if (width - x - 1 < wordWidth(word)) { + if (width - x - 1 <= wordWidth(word)) { waddch(win, '\n'); str = word; } -- cgit 1.4.0 From 09754ed91243f497065d888d19fc7c3c63ce19a9 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 02:28:14 -0500 Subject: Call reset_shell_mode on err This restores the terminal but doesn't clear the screen, so the error stays visible. --- ui.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 11ee426..90ba726 100644 --- a/ui.c +++ b/ui.c @@ -124,6 +124,11 @@ static struct Window *windowFor(size_t id) { return window; } +static void errExit(int eval) { + (void)eval; + reset_shell_mode(); +} + void uiInit(void) { initscr(); cbreak(); @@ -131,6 +136,7 @@ void uiInit(void) { termInit(); termNoFlow(); def_prog_mode(); + err_set_exit(errExit); colorInit(); status = newwin(1, COLS, 0, 0); input = newpad(1, InputCols); -- cgit 1.4.0 From ec83332e15d31c1ffbb7112ff6743f2a5c815c71 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 03:13:50 -0500 Subject: Implement window switching and status line --- chat.c | 15 ++------------- chat.h | 1 + handle.c | 22 +++++++++++++++++++--- ui.c | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 16 deletions(-) (limited to 'ui.c') diff --git a/chat.c b/chat.c index ceaf1b5..d4ed31c 100644 --- a/chat.c +++ b/chat.c @@ -76,19 +76,8 @@ int main(int argc, char *argv[]) { ircConfig(insecure, cert, priv); uiInit(); - uiFormat(Network, Cold, NULL, C "3Trave" U "ling" U C "0,3.." C "0,4." R); - uiFormat( - Network, Cold, NULL, - "Jackdaws love my big sphinx of quartz. " - "The quick brown fox jumps over the lazy dog. " - "Jackdaws love my big sphinx of quartz. " - "Jackdaws love my big sphinx of quartz. " - "Jackdaws love my big sphinx of quartz. " - "The quick brown fox jumps over the lazy dog. " - "The quick brown fox jumps over the lazy dog. " - "Jackdaws love my big sphinx of quartz. " - "Jackdaws love my big sphinx of quartz. " - ); + uiShowID(Network); + uiFormat(Network, Cold, NULL, "Traveling..."); uiDraw(); int irc = ircConnect(host, port); diff --git a/chat.h b/chat.h index fc5043d..9060f29 100644 --- a/chat.h +++ b/chat.h @@ -117,6 +117,7 @@ void handle(struct Message msg); enum Heat { Cold, Warm, Hot }; void uiInit(void); void uiDraw(void); +void uiShowID(size_t id); void uiWrite(size_t id, enum Heat heat, const struct tm *time, const char *str); void uiFormat( size_t id, enum Heat heat, const struct tm *time, const char *format, ... diff --git a/handle.c b/handle.c index 350a636..609867e 100644 --- a/handle.c +++ b/handle.c @@ -65,9 +65,11 @@ static void set(char **field, const char *value) { if (!*field) err(EX_OSERR, "strdup"); } -static void require(const struct Message *msg, bool origin, size_t len) { - if (origin && !msg->nick) { - errx(EX_PROTOCOL, "%s missing origin", msg->cmd); +static void require(struct Message *msg, bool origin, size_t len) { + if (origin) { + if (!msg->nick) errx(EX_PROTOCOL, "%s missing origin", msg->cmd); + if (!msg->user) msg->user = msg->nick; + if (!msg->host) msg->host = msg->user; } for (size_t i = 0; i < len; ++i) { if (msg->params[i]) continue; @@ -177,6 +179,19 @@ static void handleReplyMOTD(struct Message *msg) { uiFormat(Network, Cold, tagTime(msg), "%s", line); } +static void handleJoin(struct Message *msg) { + require(msg, true, 1); + size_t id = idFor(msg->params[0]); + if (self.nick && !strcmp(msg->nick, self.nick)) { + uiShowID(id); + } + uiFormat( + id, Cold, tagTime(msg), + C"%02d%s"C" arrives in "C"%02d%s"C, + hash(msg->user), msg->nick, hash(idNames[id]), idNames[id] + ); +} + static void handlePing(struct Message *msg) { require(msg, false, 1); ircFormat("PONG :%s\r\n", msg->params[0]); @@ -197,6 +212,7 @@ static const struct Handler { { "906", handleErrorSASLFail }, { "AUTHENTICATE", handleAuthenticate }, { "CAP", handleCap }, + { "JOIN", handleJoin }, { "PING", handlePing }, }; diff --git a/ui.c b/ui.c index 90ba726..3ae6592 100644 --- a/ui.c +++ b/ui.c @@ -262,6 +262,40 @@ static void styleAdd(WINDOW *win, const char *str) { } } +static void statusUpdate(void) { + wmove(status, 0, 0); + int num; + const struct Window *window; + for (num = 0, window = windows.head; window; ++num, window = window->next) { + if (!window->unread && window != windows.active) continue; + enum Color color = hash(idNames[window->id]); // FIXME: queries. + int unread; + char buf[256]; + snprintf( + buf, sizeof(buf), C"%d%s %d %s %n("C"%02d%d"C"%d) ", + color, (window == windows.active ? V : ""), + num, idNames[window->id], + &unread, (window->heat > Warm ? White : color), window->unread, + color + ); + if (!window->unread) buf[unread] = '\0'; + styleAdd(status, buf); + } + wclrtoeol(status); +} + +void uiShowID(size_t id) { + struct Window *window = windowFor(id); + window->heat = Cold; + window->unread = 0; + window->mark = false; + if (windows.active) windows.active->mark = true; + windows.other = windows.active; + windows.active = window; + touchwin(window->pad); + statusUpdate(); +} + void uiWrite(size_t id, enum Heat heat, const struct tm *time, const char *str) { (void)time; struct Window *window = windowFor(id); -- cgit 1.4.0 From 8bb9ea7b7ff2e98bbe629f9f2e63f1dcb70250e3 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 03:27:50 -0500 Subject: Add idColors --- chat.c | 7 +++++++ chat.h | 47 +++++++++++++++++++++++++---------------------- handle.c | 3 ++- ui.c | 8 ++++---- 4 files changed, 38 insertions(+), 27 deletions(-) (limited to 'ui.c') diff --git a/chat.c b/chat.c index d4ed31c..b61dd34 100644 --- a/chat.c +++ b/chat.c @@ -29,6 +29,13 @@ char *idNames[IDCap] = { [Debug] = "", [Network] = "", }; + +enum Color idColors[IDCap] = { + [None] = Black, + [Debug] = Red, + [Network] = Gray, +}; + size_t idNext = Network + 1; struct Self self; diff --git a/chat.h b/chat.h index 9060f29..4ced983 100644 --- a/chat.h +++ b/chat.h @@ -26,8 +26,21 @@ typedef unsigned char byte; +#define B "\2" +#define C "\3" +#define R "\17" +#define V "\26" +#define I "\35" +#define U "\37" +enum Color { + White, Black, Blue, Green, Red, Brown, Magenta, Orange, + Yellow, LightGreen, Cyan, LightCyan, LightBlue, Pink, Gray, LightGray, + Default = 99, +}; + enum { None, Debug, Network, IDCap = 256 }; extern char *idNames[IDCap]; +extern enum Color idColors[IDCap]; extern size_t idNext; static inline size_t idFind(const char *name) { @@ -36,6 +49,7 @@ static inline size_t idFind(const char *name) { } return None; } + static inline size_t idFor(const char *name) { size_t id = idFind(name); if (id) return id; @@ -83,28 +97,6 @@ struct Message { char *params[ParamCap]; }; -#define B "\2" -#define C "\3" -#define R "\17" -#define V "\26" -#define I "\35" -#define U "\37" -enum Color { - White, Black, Blue, Green, Red, Brown, Magenta, Orange, - Yellow, LightGreen, Cyan, LightCyan, LightBlue, Pink, Gray, LightGray, - Default = 99, -}; -static inline enum Color hash(const char *str) { - if (*str == '~') str++; - uint32_t hash = 0; - for (; *str; ++str) { - hash = (hash << 5) | (hash >> 27); - hash ^= *str; - hash *= 0x27220A95; - } - return 2 + hash % 14; -} - void ircConfig(bool insecure, const char *cert, const char *priv); int ircConnect(const char *host, const char *port); void ircRecv(void); @@ -140,6 +132,17 @@ void termTitle(const char *title); void termMode(enum TermMode mode, bool set); enum TermEvent termEvent(char ch); +static inline enum Color hash(const char *str) { + if (*str == '~') str++; + uint32_t hash = 0; + for (; *str; ++str) { + hash = (hash << 5) | (hash >> 27); + hash ^= *str; + hash *= 0x27220A95; + } + return 2 + hash % 14; +} + #define BASE64_SIZE(len) (1 + ((len) + 2) / 3 * 4) static const char Base64[64] = { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" diff --git a/handle.c b/handle.c index 609867e..f52f6f9 100644 --- a/handle.c +++ b/handle.c @@ -183,12 +183,13 @@ static void handleJoin(struct Message *msg) { require(msg, true, 1); size_t id = idFor(msg->params[0]); if (self.nick && !strcmp(msg->nick, self.nick)) { + idColors[id] = hash(msg->params[0]); uiShowID(id); } uiFormat( id, Cold, tagTime(msg), C"%02d%s"C" arrives in "C"%02d%s"C, - hash(msg->user), msg->nick, hash(idNames[id]), idNames[id] + hash(msg->user), msg->nick, idColors[id], idNames[id] ); } diff --git a/ui.c b/ui.c index 3ae6592..961e448 100644 --- a/ui.c +++ b/ui.c @@ -268,15 +268,15 @@ static void statusUpdate(void) { const struct Window *window; for (num = 0, window = windows.head; window; ++num, window = window->next) { if (!window->unread && window != windows.active) continue; - enum Color color = hash(idNames[window->id]); // FIXME: queries. int unread; char buf[256]; snprintf( buf, sizeof(buf), C"%d%s %d %s %n("C"%02d%d"C"%d) ", - color, (window == windows.active ? V : ""), + idColors[window->id], (window == windows.active ? V : ""), num, idNames[window->id], - &unread, (window->heat > Warm ? White : color), window->unread, - color + &unread, (window->heat > Warm ? White : idColors[window->id]), + window->unread, + idColors[window->id] ); if (!window->unread) buf[unread] = '\0'; styleAdd(status, buf); -- cgit 1.4.0 From 052cd2ed2688867f8b980d283ea3aa410d9dd6aa Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 03:34:05 -0500 Subject: Remove style string macros --- chat.h | 6 ------ handle.c | 2 +- irc.c | 2 +- ui.c | 4 ++-- 4 files changed, 4 insertions(+), 10 deletions(-) (limited to 'ui.c') diff --git a/chat.h b/chat.h index 4ced983..275fef9 100644 --- a/chat.h +++ b/chat.h @@ -26,12 +26,6 @@ typedef unsigned char byte; -#define B "\2" -#define C "\3" -#define R "\17" -#define V "\26" -#define I "\35" -#define U "\37" enum Color { White, Black, Blue, Green, Red, Brown, Magenta, Orange, Yellow, LightGreen, Cyan, LightCyan, LightBlue, Pink, Gray, LightGray, diff --git a/handle.c b/handle.c index f52f6f9..ef61129 100644 --- a/handle.c +++ b/handle.c @@ -188,7 +188,7 @@ static void handleJoin(struct Message *msg) { } uiFormat( id, Cold, tagTime(msg), - C"%02d%s"C" arrives in "C"%02d%s"C, + "\3%02d%s\3 arrives in \3%02d%s\3", hash(msg->user), msg->nick, idColors[id], idNames[id] ); } diff --git a/irc.c b/irc.c index f07fcc8..d8c6a21 100644 --- a/irc.c +++ b/irc.c @@ -105,7 +105,7 @@ static void debug(char dir, const char *line) { if (!self.debug) return; size_t len = strcspn(line, "\r\n"); uiFormat( - Debug, Cold, NULL, C"%d%c%c"C" %.*s", + Debug, Cold, NULL, "\3%d%c%c\3 %.*s", Gray, dir, dir, (int)len, line ); if (!isatty(STDERR_FILENO)) { diff --git a/ui.c b/ui.c index 961e448..3f74e14 100644 --- a/ui.c +++ b/ui.c @@ -271,8 +271,8 @@ static void statusUpdate(void) { int unread; char buf[256]; snprintf( - buf, sizeof(buf), C"%d%s %d %s %n("C"%02d%d"C"%d) ", - idColors[window->id], (window == windows.active ? V : ""), + buf, sizeof(buf), "\3%d%s %d %s %n(\3%02d%d\3%d) ", + idColors[window->id], (window == windows.active ? "\26" : ""), num, idNames[window->id], &unread, (window->heat > Warm ? White : idColors[window->id]), window->unread, -- cgit 1.4.0 From e8d0d71775e2a9602a233682e30246d7b7651e55 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 16:55:45 -0500 Subject: Add option to show style codes This will be used for the input window. --- ui.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 3f74e14..2445eb1 100644 --- a/ui.c +++ b/ui.c @@ -193,7 +193,7 @@ static short mapColor(enum Color color) { static void styleParse(struct Style *style, const char **str, size_t *len) { switch (**str) { - break; case '\2': (*str)++; style->attr ^= A_BOLD; + break; case '\2': (*str)++; style->attr ^= A_BOLD; break; case '\17': (*str)++; *style = Reset; break; case '\26': (*str)++; style->attr ^= A_REVERSE; break; case '\35': (*str)++; style->attr ^= A_ITALIC; @@ -230,7 +230,7 @@ static int wordWidth(const char *str) { return width; } -static void styleAdd(WINDOW *win, const char *str) { +static void styleAdd(WINDOW *win, const char *str, bool show) { int y, x, width; getmaxyx(win, y, width); @@ -246,7 +246,21 @@ static void styleAdd(WINDOW *win, const char *str) { } } + const char *code = str; styleParse(&style, &str, &len); + if (show) { + wattr_set(win, A_BOLD | A_REVERSE, 0, NULL); + switch (*code) { + break; case '\2': waddch(win, 'B'); + break; case '\3': waddch(win, 'C'); + break; case '\17': waddch(win, 'O'); + break; case '\26': waddch(win, 'R'); + break; case '\35': waddch(win, 'I'); + break; case '\37': waddch(win, 'U'); + } + if (str - code > 1) waddnstr(win, &code[1], str - &code[1]); + } + size_t sp = strspn(str, " "); sp += strcspn(&str[sp], " "); if (sp < len) len = sp; @@ -279,7 +293,7 @@ static void statusUpdate(void) { idColors[window->id] ); if (!window->unread) buf[unread] = '\0'; - styleAdd(status, buf); + styleAdd(status, buf, true); } wclrtoeol(status); } @@ -300,7 +314,7 @@ void uiWrite(size_t id, enum Heat heat, const struct tm *time, const char *str) (void)time; struct Window *window = windowFor(id); waddch(window->pad, '\n'); - styleAdd(window->pad, str); + styleAdd(window->pad, str, true); } void uiFormat( -- cgit 1.4.0 From a507ff40732b1f6fe9c5dbc1f1f17ef00bcccf5d Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 17:57:07 -0500 Subject: Set title in statusUpdate --- ui.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 2445eb1..2d5e454 100644 --- a/ui.c +++ b/ui.c @@ -296,6 +296,16 @@ static void statusUpdate(void) { styleAdd(status, buf, true); } wclrtoeol(status); + + int unread; + char buf[256]; + snprintf( + buf, sizeof(buf), "%s %s%n (%d)", + self.network, idNames[windows.active->id], + &unread, windows.active->unread + ); + if (!windows.active->unread) buf[unread] = '\0'; + termTitle(buf); } void uiShowID(size_t id) { -- cgit 1.4.0 From 8ec17d4f8ce2edab30d998d6b279f0e5cd840022 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 18:39:08 -0500 Subject: Use tsl/fsl capabilities for title if available Also manually fill them if TERM=xterm* because they really should be there. --- chat.h | 2 -- term.c | 14 -------------- ui.c | 11 +++++++++-- 3 files changed, 9 insertions(+), 18 deletions(-) (limited to 'ui.c') diff --git a/chat.h b/chat.h index f9de779..8a806f1 100644 --- a/chat.h +++ b/chat.h @@ -129,9 +129,7 @@ enum TermEvent { TermPasteStart, TermPasteEnd, }; -void termInit(void); void termNoFlow(void); -void termTitle(const char *title); void termMode(enum TermMode mode, bool set); enum TermEvent termEvent(char ch); diff --git a/term.c b/term.c index bf4a933..427cac6 100644 --- a/term.c +++ b/term.c @@ -17,19 +17,11 @@ #include #include #include -#include #include #include #include "chat.h" -static bool xterm; - -void termInit(void) { - const char *term = getenv("TERM"); - xterm = (term && !strncmp(term, "xterm", 5)); -} - void termNoFlow(void) { struct termios attr; int error = tcgetattr(STDIN_FILENO, &attr); @@ -39,12 +31,6 @@ void termNoFlow(void) { tcsetattr(STDIN_FILENO, TCSANOW, &attr); } -void termTitle(const char *title) { - if (!xterm) return; - printf("\33]0;%s\33\\", title); - fflush(stdout); -} - static void privateMode(const char *mode, bool set) { printf("\33[?%s%c", mode, (set ? 'h' : 'l')); fflush(stdout); diff --git a/ui.c b/ui.c index 2d5e454..f434289 100644 --- a/ui.c +++ b/ui.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -133,11 +134,15 @@ void uiInit(void) { initscr(); cbreak(); noecho(); - termInit(); termNoFlow(); def_prog_mode(); err_set_exit(errExit); colorInit(); + if (!to_status_line && !strncmp(termname(), "xterm", 5)) { + to_status_line = "\33]2;"; + from_status_line = "\7"; + } + status = newwin(1, COLS, 0, 0); input = newpad(1, InputCols); keypad(input, true); @@ -305,7 +310,9 @@ static void statusUpdate(void) { &unread, windows.active->unread ); if (!windows.active->unread) buf[unread] = '\0'; - termTitle(buf); + putp(to_status_line); + putp(buf); + putp(from_status_line); } void uiShowID(size_t id) { -- cgit 1.4.0 From 5c328c7a8801d6a4aded769092ead9715d4ecf98 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 19:34:35 -0500 Subject: Remove term.c in favor of more curses APIs --- Makefile | 1 - chat.h | 15 --------------- term.c | 66 ---------------------------------------------------------------- ui.c | 28 +++++++++++++++++++++++++-- 4 files changed, 26 insertions(+), 84 deletions(-) delete mode 100644 term.c (limited to 'ui.c') diff --git a/Makefile b/Makefile index ce27d4e..6ba0ba5 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,6 @@ LDLIBS = -lcurses -lcrypto -ltls OBJS += chat.o OBJS += handle.o OBJS += irc.o -OBJS += term.o OBJS += ui.o dev: tags all diff --git a/chat.h b/chat.h index 8a806f1..43f62fd 100644 --- a/chat.h +++ b/chat.h @@ -118,21 +118,6 @@ void uiFormat( size_t id, enum Heat heat, const struct tm *time, const char *format, ... ) __attribute__((format(printf, 4, 5))); -enum TermMode { - TermFocus, - TermPaste, -}; -enum TermEvent { - TermNone, - TermFocusIn, - TermFocusOut, - TermPasteStart, - TermPasteEnd, -}; -void termNoFlow(void); -void termMode(enum TermMode mode, bool set); -enum TermEvent termEvent(char ch); - static inline enum Color hash(const char *str) { if (*str == '~') str++; uint32_t hash = 0; diff --git a/term.c b/term.c deleted file mode 100644 index 427cac6..0000000 --- a/term.c +++ /dev/null @@ -1,66 +0,0 @@ -/* Copyright (C) 2018, 2020 C. McEnroe - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include -#include -#include -#include - -#include "chat.h" - -void termNoFlow(void) { - struct termios attr; - int error = tcgetattr(STDIN_FILENO, &attr); - if (error) return; - attr.c_iflag &= ~IXON; - attr.c_cc[VDISCARD] = _POSIX_VDISABLE; - tcsetattr(STDIN_FILENO, TCSANOW, &attr); -} - -static void privateMode(const char *mode, bool set) { - printf("\33[?%s%c", mode, (set ? 'h' : 'l')); - fflush(stdout); -} - -void termMode(enum TermMode mode, bool set) { - switch (mode) { - break; case TermFocus: privateMode("1004", set); - break; case TermPaste: privateMode("2004", set); - } -} - -enum { Esc = '\33' }; - -enum TermEvent termEvent(char ch) { - static int st; -#define T(st, ch) ((st) << 8 | (ch)) - switch (T(st, ch)) { - break; case T(0, Esc): st = 1; - break; case T(1, '['): st = 2; - break; case T(2, 'I'): st = 0; return TermFocusIn; - break; case T(2, 'O'): st = 0; return TermFocusOut; - break; case T(2, '2'): st = 3; - break; case T(3, '0'): st = 4; - break; case T(4, '0'): st = 5; - break; case T(5, '~'): st = 0; return TermPasteStart; - break; case T(4, '1'): st = 6; - break; case T(6, '~'): st = 0; return TermPasteEnd; - break; default: st = 0; - } - return 0; -#undef T -} diff --git a/ui.c b/ui.c index f434289..5d626ce 100644 --- a/ui.c +++ b/ui.c @@ -25,7 +25,9 @@ #include #include #include +#include #include +#include #include #include @@ -125,6 +127,23 @@ static struct Window *windowFor(size_t id) { return window; } +enum { + KeyFocusIn = KEY_MAX + 1, + KeyFocusOut, + KeyPasteOn, + KeyPasteOff, +}; + +static void disableFlowControl(void) { + struct termios term; + int error = tcgetattr(STDOUT_FILENO, &term); + if (error) err(EX_OSERR, "tcgetattr"); + term.c_iflag &= ~IXON; + term.c_cc[VDISCARD] = _POSIX_VDISABLE; + error = tcsetattr(STDOUT_FILENO, TCSADRAIN, &term); + if (error) err(EX_OSERR, "tcsetattr"); +} + static void errExit(int eval) { (void)eval; reset_shell_mode(); @@ -134,15 +153,20 @@ void uiInit(void) { initscr(); cbreak(); noecho(); - termNoFlow(); + disableFlowControl(); def_prog_mode(); err_set_exit(errExit); - colorInit(); + if (!to_status_line && !strncmp(termname(), "xterm", 5)) { to_status_line = "\33]2;"; from_status_line = "\7"; } + define_key("\33[I", KeyFocusIn); + define_key("\33[O", KeyFocusOut); + define_key("\33[200~", KeyPasteOn); + define_key("\33[201~", KeyPasteOff); + colorInit(); status = newwin(1, COLS, 0, 0); input = newpad(1, InputCols); keypad(input, true); -- cgit 1.4.0 From 5398a6ac9d31916ec1a399813032797988e308d2 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 19:38:37 -0500 Subject: Rearrange some UI code --- ui.c | 58 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 28 deletions(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 5d626ce..8bc3eae 100644 --- a/ui.c +++ b/ui.c @@ -41,32 +41,6 @@ #define RIGHT (COLS - 1) #define WINDOW_LINES (LINES - 2) -static short colorPairs; - -static void colorInit(void) { - start_color(); - use_default_colors(); - for (short pair = 0; pair < 16; ++pair) { - init_pair(1 + pair, pair % COLORS, -1); - } - colorPairs = 17; -} - -static attr_t colorAttr(short fg) { - return (fg >= COLORS ? A_BOLD : A_NORMAL); -} - -static short colorPair(short fg, short bg) { - if (bg == -1) return 1 + fg; - for (short pair = 17; pair < colorPairs; ++pair) { - short f, b; - pair_content(pair, &f, &b); - if (f == fg && b == bg) return pair; - } - init_pair(colorPairs, fg % COLORS, bg % COLORS); - return colorPairs++; -} - enum { InputCols = 512, PadLines = 512, @@ -115,18 +89,46 @@ static struct Window *windowFor(size_t id) { } window = malloc(sizeof(*window)); if (!window) err(EX_OSERR, "malloc"); + window->id = id; window->pad = newpad(PadLines, COLS); - scrollok(window->pad, true); - wmove(window->pad, PadLines - 1, 0); window->heat = Cold; window->unread = 0; window->scroll = PadLines; window->mark = true; + scrollok(window->pad, true); + wmove(window->pad, PadLines - 1, 0); + windowAdd(window); return window; } +static short colorPairs; + +static void colorInit(void) { + start_color(); + use_default_colors(); + for (short pair = 0; pair < 16; ++pair) { + init_pair(1 + pair, pair % COLORS, -1); + } + colorPairs = 17; +} + +static attr_t colorAttr(short fg) { + return (fg >= COLORS ? A_BOLD : A_NORMAL); +} + +static short colorPair(short fg, short bg) { + if (bg == -1) return 1 + fg; + for (short pair = 17; pair < colorPairs; ++pair) { + short f, b; + pair_content(pair, &f, &b); + if (f == fg && b == bg) return pair; + } + init_pair(colorPairs, fg % COLORS, bg % COLORS); + return colorPairs++; +} + enum { KeyFocusIn = KEY_MAX + 1, KeyFocusOut, -- cgit 1.4.0 From 81ac0c59f3ce68053d83462577bae7e57c21cc36 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 20:23:36 -0500 Subject: Track unread and window heat --- ui.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 8bc3eae..b9aadec 100644 --- a/ui.c +++ b/ui.c @@ -324,7 +324,7 @@ static void statusUpdate(void) { idColors[window->id] ); if (!window->unread) buf[unread] = '\0'; - styleAdd(status, buf, true); + styleAdd(status, buf, false); } wclrtoeol(status); @@ -342,11 +342,11 @@ static void statusUpdate(void) { } void uiShowID(size_t id) { + windows.active->mark = true; struct Window *window = windowFor(id); window->heat = Cold; window->unread = 0; window->mark = false; - if (windows.active) windows.active->mark = true; windows.other = windows.active; windows.active = window; touchwin(window->pad); @@ -357,7 +357,14 @@ void uiWrite(size_t id, enum Heat heat, const struct tm *time, const char *str) (void)time; struct Window *window = windowFor(id); waddch(window->pad, '\n'); - styleAdd(window->pad, str, true); + if (window->mark && heat > Cold) { + if (!window->unread++) { + waddch(window->pad, '\n'); + } + window->heat = heat; + statusUpdate(); + } + styleAdd(window->pad, str, false); } void uiFormat( -- cgit 1.4.0 From c9470b59a151f639e7985ca545bd67182e7a88d8 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 23:20:19 -0500 Subject: Add sequences for toggling focus/paste modes --- chat.h | 2 ++ ui.c | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) (limited to 'ui.c') diff --git a/chat.h b/chat.h index 43f62fd..76d69c9 100644 --- a/chat.h +++ b/chat.h @@ -111,6 +111,8 @@ void handle(struct Message msg); enum Heat { Cold, Warm, Hot }; void uiInit(void); +void uiShow(void); +void uiHide(void); void uiDraw(void); void uiShowID(size_t id); void uiWrite(size_t id, enum Heat heat, const struct tm *time, const char *str); diff --git a/ui.c b/ui.c index b9aadec..072ee84 100644 --- a/ui.c +++ b/ui.c @@ -136,6 +136,24 @@ enum { KeyPasteOff, }; +// XXX: Assuming terminals will be fine with these even if they're unsupported, +// since they're "private" modes. +static const char *EnterFocusMode = "\33[?1004h"; +static const char *ExitFocusMode = "\33[?1004l"; +static const char *EnterPasteMode = "\33[?2004h"; +static const char *ExitPasteMode = "\33[?2004l"; + +void uiShow(void) { + putp(EnterFocusMode); + putp(EnterPasteMode); +} + +void uiHide(void) { + putp(ExitFocusMode); + putp(ExitPasteMode); + endwin(); +} + static void disableFlowControl(void) { struct termios term; int error = tcgetattr(STDOUT_FILENO, &term); @@ -174,6 +192,7 @@ void uiInit(void) { keypad(input, true); nodelay(input, true); windows.active = windowFor(Network); + //uiShow(); } void uiDraw(void) { -- cgit 1.4.0 From 26e9dd9adfd4df90cd4cc6ef14d91cdad2efb239 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 3 Feb 2020 18:41:52 -0500 Subject: Use time_t rather than struct tm --- chat.h | 4 ++-- handle.c | 11 ++++++----- ui.c | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) (limited to 'ui.c') diff --git a/chat.h b/chat.h index 76d69c9..9165c13 100644 --- a/chat.h +++ b/chat.h @@ -115,9 +115,9 @@ void uiShow(void); void uiHide(void); void uiDraw(void); void uiShowID(size_t id); -void uiWrite(size_t id, enum Heat heat, const struct tm *time, const char *str); +void uiWrite(size_t id, enum Heat heat, const time_t *time, const char *str); void uiFormat( - size_t id, enum Heat heat, const struct tm *time, const char *format, ... + size_t id, enum Heat heat, const time_t *time, const char *format, ... ) __attribute__((format(printf, 4, 5))); static inline enum Color hash(const char *str) { diff --git a/handle.c b/handle.c index 4bc2e3d..ef49f7c 100644 --- a/handle.c +++ b/handle.c @@ -71,12 +71,13 @@ static void require(struct Message *msg, bool origin, size_t len) { } } -static const struct tm *tagTime(const struct Message *msg) { +static const time_t *tagTime(const struct Message *msg) { + static time_t time; + struct tm tm; if (!msg->tags[TagTime]) return NULL; - static struct tm time; - char *rest = strptime(msg->tags[TagTime], "%FT%T", &time); - time.tm_gmtoff = 0; - return (rest ? &time : NULL); + if (!strptime(msg->tags[TagTime], "%FT%T", &tm)) return NULL; + time = timegm(&tm); + return &time; } typedef void Handler(struct Message *msg); diff --git a/ui.c b/ui.c index 072ee84..e2746f1 100644 --- a/ui.c +++ b/ui.c @@ -372,7 +372,7 @@ void uiShowID(size_t id) { statusUpdate(); } -void uiWrite(size_t id, enum Heat heat, const struct tm *time, const char *str) { +void uiWrite(size_t id, enum Heat heat, const time_t *time, const char *str) { (void)time; struct Window *window = windowFor(id); waddch(window->pad, '\n'); @@ -387,7 +387,7 @@ void uiWrite(size_t id, enum Heat heat, const struct tm *time, const char *str) } void uiFormat( - size_t id, enum Heat heat, const struct tm *time, const char *format, ... + size_t id, enum Heat heat, const time_t *time, const char *format, ... ) { char buf[1024]; va_list ap; -- cgit 1.4.0 From 43845c61156bf27955891d68c2e1a2504786b587 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Tue, 4 Feb 2020 03:58:56 -0500 Subject: Add beginnings of input handling --- chat.c | 12 +++++++++++- chat.h | 1 + ui.c | 45 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 56 insertions(+), 2 deletions(-) (limited to 'ui.c') diff --git a/chat.c b/chat.c index 162f68f..3402621 100644 --- a/chat.c +++ b/chat.c @@ -15,7 +15,9 @@ */ #include +#include #include +#include #include #include #include @@ -98,8 +100,16 @@ int main(int argc, char *argv[]) { ircFormat("NICK :%s\r\n", nick); ircFormat("USER %s 0 * :%s\r\n", user, real); + struct pollfd fds[2] = { + { .events = POLLIN, .fd = STDIN_FILENO }, + { .events = POLLIN, .fd = irc }, + }; for (;;) { + int nfds = poll(fds, 2, -1); + if (nfds < 0 && errno != EINTR) err(EX_IOERR, "poll"); + + if (fds[0].revents) uiRead(); + if (fds[1].revents) ircRecv(); uiDraw(); - ircRecv(); } } diff --git a/chat.h b/chat.h index 9165c13..8de5df1 100644 --- a/chat.h +++ b/chat.h @@ -115,6 +115,7 @@ void uiShow(void); void uiHide(void); void uiDraw(void); void uiShowID(size_t id); +void uiRead(void); void uiWrite(size_t id, enum Heat heat, const time_t *time, const char *str); void uiFormat( size_t id, enum Heat heat, const time_t *time, const char *format, ... diff --git a/ui.c b/ui.c index e2746f1..d69e706 100644 --- a/ui.c +++ b/ui.c @@ -14,6 +14,8 @@ * along with this program. If not, see . */ +#define _XOPEN_SOURCE_EXTENDED + #include #include #include @@ -192,7 +194,7 @@ void uiInit(void) { keypad(input, true); nodelay(input, true); windows.active = windowFor(Network); - //uiShow(); + uiShow(); } void uiDraw(void) { @@ -397,3 +399,44 @@ void uiFormat( assert((size_t)len < sizeof(buf)); uiWrite(id, heat, time, buf); } + +static void keyCode(int code) { + switch (code) { + break; case KEY_RESIZE:; // TODO + break; case KeyFocusIn:; // TODO + break; case KeyFocusOut: windows.active->mark = true; + break; case KeyPasteOn:; // TODO + break; case KeyPasteOff:; // TODO + } +} + +static void keyMeta(wchar_t ch) { + switch (ch) { + break; case L'm': uiWrite(windows.active->id, Cold, NULL, ""); + } +} + +static void keyChar(wchar_t ch) { + switch (ch) { + break; case CTRL(L'L'): clearok(curscr, true); + } +} + +void uiRead(void) { + int ret; + wint_t ch; + static bool meta; + while (ERR != (ret = wget_wch(input, &ch))) { + if (ret == KEY_CODE_YES) { + keyCode(ch); + } else if (ch == '\33') { + meta = true; + continue; + } else if (meta) { + keyMeta(ch); + } else { + keyChar(ch); + } + meta = false; + } +} -- cgit 1.4.0 From d57e7868760e9962a4973a4b64f88dfe3cf4e363 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Tue, 4 Feb 2020 04:09:54 -0500 Subject: Factor out unmark --- ui.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index d69e706..6a834cd 100644 --- a/ui.c +++ b/ui.c @@ -362,16 +362,20 @@ static void statusUpdate(void) { putp(from_status_line); } +static void unmark(void) { + windows.active->heat = Cold; + windows.active->unread = 0; + windows.active->mark = false; + statusUpdate(); +} + void uiShowID(size_t id) { - windows.active->mark = true; struct Window *window = windowFor(id); - window->heat = Cold; - window->unread = 0; - window->mark = false; + touchwin(window->pad); windows.other = windows.active; windows.active = window; - touchwin(window->pad); - statusUpdate(); + windows.other->mark = true; + unmark(); } void uiWrite(size_t id, enum Heat heat, const time_t *time, const char *str) { @@ -403,7 +407,7 @@ void uiFormat( static void keyCode(int code) { switch (code) { break; case KEY_RESIZE:; // TODO - break; case KeyFocusIn:; // TODO + break; case KeyFocusIn: unmark(); break; case KeyFocusOut: windows.active->mark = true; break; case KeyPasteOn:; // TODO break; case KeyPasteOff:; // TODO -- cgit 1.4.0 From a65841c3cb2f367448528242b187c699cb97e0a4 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Tue, 4 Feb 2020 04:41:11 -0500 Subject: Switch windows with M-0 through M-9 --- ui.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 6a834cd..0c2a64e 100644 --- a/ui.c +++ b/ui.c @@ -369,8 +369,7 @@ static void unmark(void) { statusUpdate(); } -void uiShowID(size_t id) { - struct Window *window = windowFor(id); +static void windowShow(struct Window *window) { touchwin(window->pad); windows.other = windows.active; windows.active = window; @@ -378,6 +377,19 @@ void uiShowID(size_t id) { unmark(); } +void uiShowID(size_t id) { + windowShow(windowFor(id)); +} + +void uiShowNum(size_t num) { + struct Window *window = windows.head; + for (size_t i = 0; i < num; ++i) { + window = window->next; + if (!window) return; + } + windowShow(window); +} + void uiWrite(size_t id, enum Heat heat, const time_t *time, const char *str) { (void)time; struct Window *window = windowFor(id); @@ -417,6 +429,9 @@ static void keyCode(int code) { static void keyMeta(wchar_t ch) { switch (ch) { break; case L'm': uiWrite(windows.active->id, Cold, NULL, ""); + break; default: { + if (ch >= L'0' && ch <= L'9') uiShowNum(ch - L'0'); + } } } -- cgit 1.4.0 From d57df09511a5e4136559ccdd01ab56e906827f96 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Tue, 4 Feb 2020 19:50:23 -0500 Subject: Align word wrapping with tab character Also fixes handling whitespace directly after control codes. --- handle.c | 2 +- irc.c | 2 +- ui.c | 17 +++++++++++++---- 3 files changed, 15 insertions(+), 6 deletions(-) (limited to 'ui.c') diff --git a/handle.c b/handle.c index ef49f7c..29d1500 100644 --- a/handle.c +++ b/handle.c @@ -212,7 +212,7 @@ static void handlePrivmsg(struct Message *msg) { if (query && !network) idColors[id] = hash(msg->user); uiFormat( id, Warm, tagTime(msg), - "\3%d%s%s%s\3 %s", + "\3%d%s%s%s\3\t%s", hash(msg->user), (action ? "* " : notice ? "-" : "<"), msg->nick, diff --git a/irc.c b/irc.c index d8c6a21..2d6f00b 100644 --- a/irc.c +++ b/irc.c @@ -105,7 +105,7 @@ static void debug(char dir, const char *line) { if (!self.debug) return; size_t len = strcspn(line, "\r\n"); uiFormat( - Debug, Cold, NULL, "\3%d%c%c\3 %.*s", + Debug, Cold, NULL, "\3%d%c%c\3\t%.*s", Gray, dir, dir, (int)len, line ); if (!isatty(STDERR_FILENO)) { diff --git a/ui.c b/ui.c index 0c2a64e..e93c08c 100644 --- a/ui.c +++ b/ui.c @@ -287,14 +287,24 @@ static void styleAdd(WINDOW *win, const char *str, bool show) { getmaxyx(win, y, width); size_t len; + int align = 0; struct Style style = Reset; while (*str) { - if (*str == ' ') { + if (*str == '\t') { + waddch(win, ' '); + getyx(win, y, align); + str++; + } else if (*str == ' ') { getyx(win, y, x); const char *word = &str[strspn(str, " ")]; if (width - x - 1 <= wordWidth(word)) { waddch(win, '\n'); + getyx(win, y, x); + wmove(win, y, align); str = word; + } else { + waddch(win, ' '); + str++; } } @@ -313,9 +323,8 @@ static void styleAdd(WINDOW *win, const char *str, bool show) { if (str - code > 1) waddnstr(win, &code[1], str - &code[1]); } - size_t sp = strspn(str, " "); - sp += strcspn(&str[sp], " "); - if (sp < len) len = sp; + size_t ws = strcspn(str, "\t "); + if (ws < len) len = ws; wattr_set( win, -- cgit 1.4.0 From 9944dc484bf8cc7d5f1c506610a0593202bb5f92 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Tue, 4 Feb 2020 20:23:55 -0500 Subject: Split showing style codes and word wrapping --- Makefile | 1 + chat.c | 2 +- chat.h | 4 ++ edit.c | 30 ++++++++++++ ui.c | 168 +++++++++++++++++++++++++++++++++++++++++---------------------- 5 files changed, 146 insertions(+), 59 deletions(-) create mode 100644 edit.c (limited to 'ui.c') diff --git a/Makefile b/Makefile index 6ba0ba5..63f719d 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ CFLAGS += -std=c11 -Wall -Wextra -Wpedantic LDLIBS = -lcurses -lcrypto -ltls OBJS += chat.o +OBJS += edit.o OBJS += handle.o OBJS += irc.o OBJS += ui.o diff --git a/chat.c b/chat.c index 1656a53..545eca6 100644 --- a/chat.c +++ b/chat.c @@ -41,7 +41,7 @@ enum Color idColors[IDCap] = { size_t idNext = Network + 1; -struct Self self; +struct Self self = { .color = Default }; static volatile sig_atomic_t signals[NSIG]; static void signalHandler(int signal) { diff --git a/chat.h b/chat.h index 8de5df1..c754357 100644 --- a/chat.h +++ b/chat.h @@ -72,6 +72,7 @@ extern struct Self { char *chanTypes; char *prefixes; char *nick; + enum Color color; } self; static inline void set(char **field, const char *value) { @@ -121,6 +122,9 @@ void uiFormat( size_t id, enum Heat heat, const time_t *time, const char *format, ... ) __attribute__((format(printf, 4, 5))); +const char *editHead(void); +const char *editTail(void); + static inline enum Color hash(const char *str) { if (*str == '~') str++; uint32_t hash = 0; diff --git a/edit.c b/edit.c new file mode 100644 index 0000000..446e0e9 --- /dev/null +++ b/edit.c @@ -0,0 +1,30 @@ +/* Copyright (C) 2020 C. McEnroe + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include "chat.h" + +const char *editHead(void) { + return "foo\0033bar"; +} + +const char *editTail(void) { + return "baz\3"; +} diff --git a/ui.c b/ui.c index e93c08c..568df8d 100644 --- a/ui.c +++ b/ui.c @@ -268,71 +268,18 @@ static void styleParse(struct Style *style, const char **str, size_t *len) { *len = strcspn(*str, "\2\3\17\26\35\37"); } -static int wordWidth(const char *str) { - size_t len = strcspn(str, " "); - int width = 0; - while (len) { - wchar_t wc; - int n = mbtowc(&wc, str, len); - if (n < 1) return width + len; - width += (iswprint(wc) ? wcwidth(wc) : 0); - str += n; - len -= n; - } - return width; -} - -static void styleAdd(WINDOW *win, const char *str, bool show) { - int y, x, width; - getmaxyx(win, y, width); - +static void statusAdd(const char *str) { size_t len; - int align = 0; struct Style style = Reset; while (*str) { - if (*str == '\t') { - waddch(win, ' '); - getyx(win, y, align); - str++; - } else if (*str == ' ') { - getyx(win, y, x); - const char *word = &str[strspn(str, " ")]; - if (width - x - 1 <= wordWidth(word)) { - waddch(win, '\n'); - getyx(win, y, x); - wmove(win, y, align); - str = word; - } else { - waddch(win, ' '); - str++; - } - } - - const char *code = str; styleParse(&style, &str, &len); - if (show) { - wattr_set(win, A_BOLD | A_REVERSE, 0, NULL); - switch (*code) { - break; case '\2': waddch(win, 'B'); - break; case '\3': waddch(win, 'C'); - break; case '\17': waddch(win, 'O'); - break; case '\26': waddch(win, 'R'); - break; case '\35': waddch(win, 'I'); - break; case '\37': waddch(win, 'U'); - } - if (str - code > 1) waddnstr(win, &code[1], str - &code[1]); - } - - size_t ws = strcspn(str, "\t "); - if (ws < len) len = ws; - wattr_set( - win, + status, style.attr | colorAttr(mapColor(style.fg)), colorPair(mapColor(style.fg), mapColor(style.bg)), NULL ); - waddnstr(win, str, len); + waddnstr(status, str, len); str += len; } } @@ -354,7 +301,7 @@ static void statusUpdate(void) { idColors[window->id] ); if (!window->unread) buf[unread] = '\0'; - styleAdd(status, buf, false); + statusAdd(buf); } wclrtoeol(status); @@ -399,6 +346,61 @@ void uiShowNum(size_t num) { windowShow(window); } +static int wordWidth(const char *str) { + size_t len = strcspn(str, " "); + int width = 0; + while (len) { + wchar_t wc; + int n = mbtowc(&wc, str, len); + if (n < 1) return width + len; + width += (iswprint(wc) ? wcwidth(wc) : 0); + str += n; + len -= n; + } + return width; +} + +static void wordWrap(WINDOW *win, const char *str) { + int y, x, width; + getmaxyx(win, y, width); + + size_t len; + int align = 0; + struct Style style = Reset; + while (*str) { + if (*str == '\t') { + waddch(win, ' '); + getyx(win, y, align); + str++; + } else if (*str == ' ') { + getyx(win, y, x); + const char *word = &str[strspn(str, " ")]; + if (width - x - 1 <= wordWidth(word)) { + waddch(win, '\n'); + getyx(win, y, x); + wmove(win, y, align); + str = word; + } else { + waddch(win, ' '); + str++; + } + } + + styleParse(&style, &str, &len); + size_t ws = strcspn(str, "\t "); + if (ws < len) len = ws; + + wattr_set( + win, + style.attr | colorAttr(mapColor(style.fg)), + colorPair(mapColor(style.fg), mapColor(style.bg)), + NULL + ); + waddnstr(win, str, len); + str += len; + } +} + void uiWrite(size_t id, enum Heat heat, const time_t *time, const char *str) { (void)time; struct Window *window = windowFor(id); @@ -410,7 +412,7 @@ void uiWrite(size_t id, enum Heat heat, const time_t *time, const char *str) { window->heat = heat; statusUpdate(); } - styleAdd(window->pad, str, false); + wordWrap(window->pad, str); } void uiFormat( @@ -425,6 +427,55 @@ void uiFormat( uiWrite(id, heat, time, buf); } +static void inputAdd(struct Style *style, const char *str) { + size_t len; + while (*str) { + const char *code = str; + styleParse(style, &str, &len); + wattr_set(input, A_BOLD | A_REVERSE, 0, NULL); + switch (*code) { + break; case '\2': waddch(input, 'B'); + break; case '\3': waddch(input, 'C'); + break; case '\17': waddch(input, 'O'); + break; case '\26': waddch(input, 'R'); + break; case '\35': waddch(input, 'I'); + break; case '\37': waddch(input, 'U'); + } + if (str - code > 1) waddnstr(input, &code[1], str - &code[1]); + wattr_set( + input, + style->attr | colorAttr(mapColor(style->fg)), + colorPair(mapColor(style->fg), mapColor(style->bg)), + NULL + ); + waddnstr(input, str, len); + str += len; + } +} + +static void inputUpdate(void) { + wmove(input, 0, 0); + wattr_set( + input, + colorAttr(mapColor(self.color)), + colorPair(mapColor(self.color), -1), + NULL + ); + if (self.nick) { + waddch(input, '<'); + waddstr(input, self.nick); + waddstr(input, "> "); + } + + int y, x; + struct Style style = Reset; + inputAdd(&style, editHead()); + getyx(input, y, x); + inputAdd(&style, editTail()); + wclrtoeol(input); + wmove(input, y, x); +} + static void keyCode(int code) { switch (code) { break; case KEY_RESIZE:; // TODO @@ -467,4 +518,5 @@ void uiRead(void) { } meta = false; } + inputUpdate(); } -- cgit 1.4.0 From 55757243f48da0f0c6d2382ffe57d364c7b803c9 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Tue, 4 Feb 2020 20:46:16 -0500 Subject: Call inputUpdate when switching windows Because changing windows (to or ) will affect the prompt. --- ui.c | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 568df8d..172120b 100644 --- a/ui.c +++ b/ui.c @@ -325,27 +325,6 @@ static void unmark(void) { statusUpdate(); } -static void windowShow(struct Window *window) { - touchwin(window->pad); - windows.other = windows.active; - windows.active = window; - windows.other->mark = true; - unmark(); -} - -void uiShowID(size_t id) { - windowShow(windowFor(id)); -} - -void uiShowNum(size_t num) { - struct Window *window = windows.head; - for (size_t i = 0; i < num; ++i) { - window = window->next; - if (!window) return; - } - windowShow(window); -} - static int wordWidth(const char *str) { size_t len = strcspn(str, " "); int width = 0; @@ -462,6 +441,7 @@ static void inputUpdate(void) { NULL ); if (self.nick) { + // TODO: Check if input is command or action. waddch(input, '<'); waddstr(input, self.nick); waddstr(input, "> "); @@ -476,6 +456,28 @@ static void inputUpdate(void) { wmove(input, y, x); } +static void windowShow(struct Window *window) { + touchwin(window->pad); + windows.other = windows.active; + windows.active = window; + windows.other->mark = true; + inputUpdate(); + unmark(); +} + +void uiShowID(size_t id) { + windowShow(windowFor(id)); +} + +void uiShowNum(size_t num) { + struct Window *window = windows.head; + for (size_t i = 0; i < num; ++i) { + window = window->next; + if (!window) return; + } + windowShow(window); +} + static void keyCode(int code) { switch (code) { break; case KEY_RESIZE:; // TODO -- cgit 1.4.0 From 104b3ffd4fe23ab530e728a7ef855cfc7d3c5595 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Tue, 4 Feb 2020 20:56:27 -0500 Subject: Model keyCtrl like keyMeta --- ui.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 172120b..8f813b4 100644 --- a/ui.c +++ b/ui.c @@ -497,9 +497,9 @@ static void keyMeta(wchar_t ch) { } } -static void keyChar(wchar_t ch) { +static void keyCtrl(wchar_t ch) { switch (ch) { - break; case CTRL(L'L'): clearok(curscr, true); + break; case L'L': clearok(curscr, true); } } @@ -515,8 +515,10 @@ void uiRead(void) { continue; } else if (meta) { keyMeta(ch); + } else if (iswcntrl(ch)) { + keyCtrl(ch ^ L'@'); } else { - keyChar(ch); + // TODO: Insert. } meta = false; } -- cgit 1.4.0 From 4cce893eab7403821ff211f64a7df05051fd6f52 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Wed, 5 Feb 2020 00:20:39 -0500 Subject: Add extremely basic editing and message sending --- Makefile | 1 + chat.h | 12 ++++++++++-- command.c | 32 ++++++++++++++++++++++++++++++++ edit.c | 32 +++++++++++++++++++++++++------- ui.c | 5 ++++- 5 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 command.c (limited to 'ui.c') diff --git a/Makefile b/Makefile index 63f719d..05f8bb8 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ CFLAGS += -std=c11 -Wall -Wextra -Wpedantic LDLIBS = -lcurses -lcrypto -ltls OBJS += chat.o +OBJS += command.o OBJS += edit.o OBJS += handle.o OBJS += irc.o diff --git a/chat.h b/chat.h index c754357..c8b31c2 100644 --- a/chat.h +++ b/chat.h @@ -20,6 +20,7 @@ #include #include #include +#include #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) #define BIT(x) x##Bit, x = 1 << x##Bit, x##Bit_ = x##Bit @@ -109,6 +110,7 @@ void ircFormat(const char *format, ...) __attribute__((format(printf, 1, 2))); void handle(struct Message msg); +void command(size_t id, char *input); enum Heat { Cold, Warm, Hot }; void uiInit(void); @@ -122,8 +124,14 @@ void uiFormat( size_t id, enum Heat heat, const time_t *time, const char *format, ... ) __attribute__((format(printf, 4, 5))); -const char *editHead(void); -const char *editTail(void); +enum Edit { + EditKill, + EditInsert, + EditEnter, +}; +void edit(size_t id, enum Edit op, wchar_t ch); +char *editHead(void); +char *editTail(void); static inline enum Color hash(const char *str) { if (*str == '~') str++; diff --git a/command.c b/command.c new file mode 100644 index 0000000..ab05587 --- /dev/null +++ b/command.c @@ -0,0 +1,32 @@ +/* Copyright (C) 2020 C. McEnroe + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "chat.h" + +void command(size_t id, char *input) { + ircFormat("PRIVMSG %s :%s\r\n", idNames[id], input); + struct Message msg = { + .nick = self.nick, + // TODO: .user, + .cmd = "PRIVMSG", + .params[0] = idNames[id], + .params[1] = input, + }; + handle(msg); +} diff --git a/edit.c b/edit.c index e142507..68593d1 100644 --- a/edit.c +++ b/edit.c @@ -23,22 +23,40 @@ #include "chat.h" enum { Cap = 512 }; -static wchar_t buf[Cap] = L"foo\0033bar\3baz"; -static size_t len = 12; -static size_t pos = 6; +static wchar_t buf[Cap]; +static size_t len; +static size_t pos; -const char *editHead(void) { +char *editHead(void) { static char mbs[MB_LEN_MAX * Cap]; const wchar_t *ptr = buf; - size_t n = wcsnrtombs(mbs, &ptr, pos, sizeof(mbs), NULL); + size_t n = wcsnrtombs(mbs, &ptr, pos, sizeof(mbs) - 1, NULL); assert(n != (size_t)-1); + mbs[n] = '\0'; return mbs; } -const char *editTail(void) { +char *editTail(void) { static char mbs[MB_LEN_MAX * Cap]; const wchar_t *ptr = &buf[pos]; - size_t n = wcsnrtombs(mbs, &ptr, len - pos, sizeof(mbs), NULL); + size_t n = wcsnrtombs(mbs, &ptr, len - pos, sizeof(mbs) - 1, NULL); assert(n != (size_t)-1); + mbs[n] = '\0'; return mbs; } + +void edit(size_t id, enum Edit op, wchar_t ch) { + switch (op) { + break; case EditKill: len = pos = 0; + break; case EditInsert: { + if (len == Cap) break; + buf[pos++] = ch; + len++; + } + break; case EditEnter: { + pos = 0; + command(id, editTail()); + len = 0; + } + } +} diff --git a/ui.c b/ui.c index 8f813b4..b764f84 100644 --- a/ui.c +++ b/ui.c @@ -498,8 +498,11 @@ static void keyMeta(wchar_t ch) { } static void keyCtrl(wchar_t ch) { + size_t id = windows.active->id; switch (ch) { + break; case L'J': edit(id, EditEnter, 0); break; case L'L': clearok(curscr, true); + break; case L'U': edit(id, EditKill, 0); } } @@ -518,7 +521,7 @@ void uiRead(void) { } else if (iswcntrl(ch)) { keyCtrl(ch ^ L'@'); } else { - // TODO: Insert. + edit(windows.active->id, EditInsert, ch); } meta = false; } -- cgit 1.4.0 From 42210e079bcdea26261af577d0802fdc4c3d03b6 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Wed, 5 Feb 2020 02:03:21 -0500 Subject: Reflow text on window resize --- chat.c | 2 ++ ui.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 53 insertions(+), 17 deletions(-) (limited to 'ui.c') diff --git a/chat.c b/chat.c index 545eca6..a8b2fa2 100644 --- a/chat.c +++ b/chat.c @@ -122,6 +122,8 @@ int main(int argc, char *argv[]) { if (signals[SIGHUP] || signals[SIGINT] || signals[SIGTERM]) { break; } + // FIXME: Display doesn't update properly when receiving many of these + // until some input? if (signals[SIGWINCH]) { signals[SIGWINCH] = 0; cursesWinch(SIGWINCH); diff --git a/ui.c b/ui.c index b764f84..12205cc 100644 --- a/ui.c +++ b/ui.c @@ -35,6 +35,9 @@ #include "chat.h" +// Annoying stuff from : +#undef lines + #ifndef A_ITALIC #define A_ITALIC A_UNDERLINE #endif @@ -43,16 +46,28 @@ #define RIGHT (COLS - 1) #define WINDOW_LINES (LINES - 2) -enum { - InputCols = 512, - PadLines = 512, -}; - static WINDOW *status; static WINDOW *input; +enum { BufferCap = 512 }; +struct Buffer { + time_t times[BufferCap]; + char *lines[BufferCap]; + size_t len; +}; +static_assert(!(BufferCap & (BufferCap - 1)), "BufferCap is power of two"); + +static void bufferPush(struct Buffer *buffer, time_t time, const char *line) { + size_t i = buffer->len++ % BufferCap; + free(buffer->lines[i]); + buffer->times[i] = time; + buffer->lines[i] = strdup(line); + if (!buffer->lines[i]) err(EX_OSERR, "strdup"); +} + struct Window { size_t id; + struct Buffer buffer; WINDOW *pad; enum Heat heat; int unread; @@ -89,17 +104,15 @@ static struct Window *windowFor(size_t id) { for (window = windows.head; window; window = window->next) { if (window->id == id) return window; } - window = malloc(sizeof(*window)); + window = calloc(1, sizeof(*window)); if (!window) err(EX_OSERR, "malloc"); window->id = id; - window->pad = newpad(PadLines, COLS); - window->heat = Cold; - window->unread = 0; - window->scroll = PadLines; - window->mark = true; + window->pad = newpad(BufferCap, COLS); scrollok(window->pad, true); - wmove(window->pad, PadLines - 1, 0); + wmove(window->pad, BufferCap - 1, 0); + window->scroll = BufferCap; + window->mark = true; windowAdd(window); return window; @@ -190,7 +203,7 @@ void uiInit(void) { colorInit(); status = newwin(1, COLS, 0, 0); - input = newpad(1, InputCols); + input = newpad(1, 512); keypad(input, true); nodelay(input, true); windows.active = windowFor(Network); @@ -380,9 +393,11 @@ static void wordWrap(WINDOW *win, const char *str) { } } -void uiWrite(size_t id, enum Heat heat, const time_t *time, const char *str) { - (void)time; +void uiWrite(size_t id, enum Heat heat, const time_t *src, const char *str) { struct Window *window = windowFor(id); + time_t clock = (src ? *src : time(NULL)); + bufferPush(&window->buffer, clock, str); + waddch(window->pad, '\n'); if (window->mark && heat > Cold) { if (!window->unread++) { @@ -406,6 +421,25 @@ void uiFormat( uiWrite(id, heat, time, buf); } +static void reflow(struct Window *window) { + werase(window->pad); + wmove(window->pad, BufferCap - 1, 0); + size_t len = window->buffer.len; + for (size_t i = (len > BufferCap ? len - BufferCap : 0); i < len; ++i) { + waddch(window->pad, '\n'); + wordWrap(window->pad, window->buffer.lines[i % BufferCap]); + } +} + +static void resize(void) { + // FIXME: Only reflow when COLS changes. + for (struct Window *window = windows.head; window; window = window->next) { + wresize(window->pad, BufferCap, COLS); + reflow(window); + } + statusUpdate(); +} + static void inputAdd(struct Style *style, const char *str) { size_t len; while (*str) { @@ -480,7 +514,7 @@ void uiShowNum(size_t num) { static void keyCode(int code) { switch (code) { - break; case KEY_RESIZE:; // TODO + break; case KEY_RESIZE: resize(); break; case KeyFocusIn: unmark(); break; case KeyFocusOut: windows.active->mark = true; break; case KeyPasteOn:; // TODO @@ -490,7 +524,7 @@ static void keyCode(int code) { static void keyMeta(wchar_t ch) { switch (ch) { - break; case L'm': uiWrite(windows.active->id, Cold, NULL, ""); + break; case L'm': waddch(windows.active->pad, '\n'); break; default: { if (ch >= L'0' && ch <= L'9') uiShowNum(ch - L'0'); } -- cgit 1.4.0 From 6e679bdf26fa4dae651aade985155bd424761a3a Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Wed, 5 Feb 2020 04:24:13 -0500 Subject: Modulo colors before comparing pairs Otherwise a new pair is allocated every time a high color is requested. --- ui.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 12205cc..894e04d 100644 --- a/ui.c +++ b/ui.c @@ -135,12 +135,14 @@ static attr_t colorAttr(short fg) { static short colorPair(short fg, short bg) { if (bg == -1) return 1 + fg; + fg %= COLORS; + bg %= COLORS; for (short pair = 17; pair < colorPairs; ++pair) { short f, b; pair_content(pair, &f, &b); if (f == fg && b == bg) return pair; } - init_pair(colorPairs, fg % COLORS, bg % COLORS); + init_pair(colorPairs, fg, bg); return colorPairs++; } -- cgit 1.4.0 From eb91347308c90ec07b4680aeb2a693c475daea9d Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Wed, 5 Feb 2020 18:01:57 -0500 Subject: Only reflow text when COLS changes --- ui.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 894e04d..da28b76 100644 --- a/ui.c +++ b/ui.c @@ -434,7 +434,9 @@ static void reflow(struct Window *window) { } static void resize(void) { - // FIXME: Only reflow when COLS changes. + int height, width; + getmaxyx(windows.active->pad, height, width); + if (width == COLS) return; for (struct Window *window = windows.head; window; window = window->next) { wresize(window->pad, BufferCap, COLS); reflow(window); -- cgit 1.4.0 From a7b0ed99079065f54db83160dfe651ce9d50a568 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Wed, 5 Feb 2020 18:18:25 -0500 Subject: Scroll the input window I was wondering if I should instead make input wrap, but then wordWrap would need to both support showing formatting and persisting styles across to strings, and it would need to move the window pad up and down a bunch, etc. --- ui.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index da28b76..12c8541 100644 --- a/ui.c +++ b/ui.c @@ -220,10 +220,11 @@ void uiDraw(void) { 1, 0, BOTTOM - 1, RIGHT ); - // TODO: Input scrolling. + int y, x; + getyx(input, y, x); pnoutrefresh( input, - 0, 0, + 0, (x > RIGHT ? x - RIGHT : 0), BOTTOM, 0, BOTTOM, RIGHT ); -- cgit 1.4.0 From b2d35edcb22a9a41235229b41b180a50b51b5908 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Wed, 5 Feb 2020 19:00:54 -0500 Subject: Change prompt depending on command --- chat.h | 3 +++ command.c | 21 +++++++++++++++++++++ ui.c | 23 ++++++++++++++++++----- 3 files changed, 42 insertions(+), 5 deletions(-) (limited to 'ui.c') diff --git a/chat.h b/chat.h index 90c7da8..b73cf40 100644 --- a/chat.h +++ b/chat.h @@ -112,6 +112,9 @@ void ircFormat(const char *format, ...) void handle(struct Message msg); void command(size_t id, char *input); +const char *commandIsPrivmsg(size_t id, const char *input); +const char *commandIsNotice(size_t id, const char *input); +const char *commandIsAction(size_t id, const char *input); enum Heat { Cold, Warm, Hot }; void uiInit(void); diff --git a/command.c b/command.c index ef27888..928f470 100644 --- a/command.c +++ b/command.c @@ -19,6 +19,27 @@ #include "chat.h" +const char *commandIsPrivmsg(size_t id, const char *input) { + if (id == Network || id == Debug) return NULL; + if (input[0] != '/') return input; + const char *space = strchr(&input[1], ' '); + const char *slash = strchr(&input[1], '/'); + if (slash && (!space || slash < space)) return input; + return NULL; +} + +const char *commandIsNotice(size_t id, const char *input) { + if (id == Network || id == Debug) return NULL; + if (strncmp(input, "/notice ", 8)) return NULL; + return &input[8]; +} + +const char *commandIsAction(size_t id, const char *input) { + if (id == Network || id == Debug) return NULL; + if (strncmp(input, "/me ", 4)) return NULL; + return &input[4]; +} + void command(size_t id, char *input) { if (id == Debug) { ircFormat("%s\r\n", input); diff --git a/ui.c b/ui.c index 12c8541..daa6dec 100644 --- a/ui.c +++ b/ui.c @@ -479,16 +479,29 @@ static void inputUpdate(void) { colorPair(mapColor(self.color), -1), NULL ); + const char *head = editHead(); + const char *skip = NULL; if (self.nick) { - // TODO: Check if input is command or action. - waddch(input, '<'); - waddstr(input, self.nick); - waddstr(input, "> "); + size_t id = windows.active->id; + if (NULL != (skip = commandIsPrivmsg(id, head))) { + waddch(input, '<'); + waddstr(input, self.nick); + waddstr(input, "> "); + } else if (NULL != (skip = commandIsNotice(id, head))) { + waddch(input, '-'); + waddstr(input, self.nick); + waddstr(input, "- "); + } else if (NULL != (skip = commandIsAction(id, head))) { + waddstr(input, "* "); + waddstr(input, self.nick); + waddch(input, ' '); + } } + if (skip) head = skip; int y, x; struct Style style = Reset; - inputAdd(&style, editHead()); + inputAdd(&style, head); getyx(input, y, x); inputAdd(&style, editTail()); wclrtoeol(input); -- cgit 1.4.0 From a5a162b9c6f9c3672c6c1e0882d70191610943c8 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Thu, 6 Feb 2020 02:25:58 -0500 Subject: Disable SUSP Frees up C-z and suspending an IRC client is silly anyway. --- ui.c | 1 + 1 file changed, 1 insertion(+) (limited to 'ui.c') diff --git a/ui.c b/ui.c index daa6dec..048eade 100644 --- a/ui.c +++ b/ui.c @@ -176,6 +176,7 @@ static void disableFlowControl(void) { int error = tcgetattr(STDOUT_FILENO, &term); if (error) err(EX_OSERR, "tcgetattr"); term.c_iflag &= ~IXON; + term.c_cc[VSUSP] = _POSIX_VDISABLE; term.c_cc[VDISCARD] = _POSIX_VDISABLE; error = tcsetattr(STDOUT_FILENO, TCSADRAIN, &term); if (error) err(EX_OSERR, "tcsetattr"); -- cgit 1.4.0 From 68440d50c624a6cfa52027635bcd9207b48504e6 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Thu, 6 Feb 2020 04:23:49 -0500 Subject: Beep on hot --- ui.c | 1 + 1 file changed, 1 insertion(+) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 048eade..b0e30b9 100644 --- a/ui.c +++ b/ui.c @@ -411,6 +411,7 @@ void uiWrite(size_t id, enum Heat heat, const time_t *src, const char *str) { statusUpdate(); } wordWrap(window->pad, str); + if (heat > Warm) beep(); } void uiFormat( -- cgit 1.4.0 From 34514cf2ee6dd020ca812653ce3a23e737d2bf62 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Thu, 6 Feb 2020 18:48:49 -0500 Subject: Render actions in italic Also render italic as normal if it's unsupported, as that is what would happen anyway if curses has A_ITALIC but the terminal has no sitm. That format string is kinda bad. --- handle.c | 8 +++++--- ui.c | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'ui.c') diff --git a/handle.c b/handle.c index 49011f5..ab482fd 100644 --- a/handle.c +++ b/handle.c @@ -294,14 +294,16 @@ static void handlePrivmsg(struct Message *msg) { bool notice = (msg->cmd[0] == 'N'); bool action = isAction(msg); bool mention = !mine && isMention(msg); + const char *italic = (action ? "\35" : ""); + const char *reverse = (mention ? "\26" : ""); uiFormat( id, (!notice && (mention || query) ? Hot : Warm), tagTime(msg), - "%s\3%d%s%s%s\17\t%s", - (mention ? "\26" : ""), - hash(msg->user), + "%s%s\3%d%s%s%s\3%s\t%s", + italic, reverse, hash(msg->user), (action ? "* " : notice ? "-" : "<"), msg->nick, (action ? "" : notice ? "-" : ">"), + reverse, msg->params[1] ); } diff --git a/ui.c b/ui.c index b0e30b9..9f0bb88 100644 --- a/ui.c +++ b/ui.c @@ -39,7 +39,7 @@ #undef lines #ifndef A_ITALIC -#define A_ITALIC A_UNDERLINE +#define A_ITALIC A_NORMAL #endif #define BOTTOM (LINES - 1) -- cgit 1.4.0 From 9cff026b5a7ed15e5e34f51e796908e77a3bc3b4 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Thu, 6 Feb 2020 19:05:51 -0500 Subject: Show input in italics for actions and set Debug prompt --- ui.c | 58 +++++++++++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 25 deletions(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 9f0bb88..96f722a 100644 --- a/ui.c +++ b/ui.c @@ -474,36 +474,44 @@ static void inputAdd(struct Style *style, const char *str) { } static void inputUpdate(void) { - wmove(input, 0, 0); - wattr_set( - input, - colorAttr(mapColor(self.color)), - colorPair(mapColor(self.color), -1), - NULL - ); + size_t id = windows.active->id; + const char *nick = self.nick; const char *head = editHead(); const char *skip = NULL; - if (self.nick) { - size_t id = windows.active->id; - if (NULL != (skip = commandIsPrivmsg(id, head))) { - waddch(input, '<'); - waddstr(input, self.nick); - waddstr(input, "> "); - } else if (NULL != (skip = commandIsNotice(id, head))) { - waddch(input, '-'); - waddstr(input, self.nick); - waddstr(input, "- "); - } else if (NULL != (skip = commandIsAction(id, head))) { - waddstr(input, "* "); - waddstr(input, self.nick); - waddch(input, ' '); - } + const char *pre = ""; + const char *suf = " "; + struct Style style = { .fg = self.color, .bg = Default }; + if (NULL != (skip = commandIsPrivmsg(id, head))) { + pre = "<"; + suf = "> "; + } else if (NULL != (skip = commandIsNotice(id, head))) { + pre = "-"; + suf = "- "; + } else if (NULL != (skip = commandIsAction(id, head))) { + style.attr |= A_ITALIC; + pre = "* "; + } else if (id == Debug) { + skip = head; + style.fg = Gray; + pre = "<<"; + nick = NULL; } - if (skip) head = skip; int y, x; - struct Style style = Reset; - inputAdd(&style, head); + wmove(input, 0, 0); + if (skip) { + wattr_set( + input, + style.attr | colorAttr(mapColor(style.fg)), + colorPair(mapColor(style.fg), mapColor(style.bg)), + NULL + ); + waddstr(input, pre); + if (nick) waddstr(input, nick); + waddstr(input, suf); + } + style.fg = Default; + inputAdd(&style, (skip ? skip : head)); getyx(input, y, x); inputAdd(&style, editTail()); wclrtoeol(input); -- cgit 1.4.0 From ea7e919a1d4816486da7ee9a77767652b475bb10 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Thu, 6 Feb 2020 23:11:35 -0500 Subject: Color notices LightGray in input --- ui.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 96f722a..b0f95aa 100644 --- a/ui.c +++ b/ui.c @@ -481,15 +481,18 @@ static void inputUpdate(void) { const char *pre = ""; const char *suf = " "; struct Style style = { .fg = self.color, .bg = Default }; + struct Style reset = Reset; if (NULL != (skip = commandIsPrivmsg(id, head))) { pre = "<"; suf = "> "; } else if (NULL != (skip = commandIsNotice(id, head))) { pre = "-"; suf = "- "; + reset.fg = LightGray; } else if (NULL != (skip = commandIsAction(id, head))) { style.attr |= A_ITALIC; pre = "* "; + reset.attr |= A_ITALIC; } else if (id == Debug) { skip = head; style.fg = Gray; @@ -510,7 +513,7 @@ static void inputUpdate(void) { if (nick) waddstr(input, nick); waddstr(input, suf); } - style.fg = Default; + style = reset; inputAdd(&style, (skip ? skip : head)); getyx(input, y, x); inputAdd(&style, editTail()); -- cgit 1.4.0 From 273207b19f4ff1302ec0c95bbb60d49f2af3415c Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Thu, 6 Feb 2020 23:11:48 -0500 Subject: Flush stdout after using putp --- ui.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'ui.c') diff --git a/ui.c b/ui.c index b0f95aa..ffe8748 100644 --- a/ui.c +++ b/ui.c @@ -163,6 +163,7 @@ static const char *ExitPasteMode = "\33[?2004l"; void uiShow(void) { putp(EnterFocusMode); putp(EnterPasteMode); + fflush(stdout); } void uiHide(void) { @@ -333,6 +334,7 @@ static void statusUpdate(void) { putp(to_status_line); putp(buf); putp(from_status_line); + fflush(stdout); } static void unmark(void) { -- cgit 1.4.0 From af244ad3cd19fb50bf9b9855f02d81e61441ab50 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Fri, 7 Feb 2020 01:55:26 -0500 Subject: Add some real line editing operations --- chat.h | 5 +++++ edit.c | 28 ++++++++++++++++++++++++---- ui.c | 12 ++++++++++++ 3 files changed, 41 insertions(+), 4 deletions(-) (limited to 'ui.c') diff --git a/chat.h b/chat.h index 8279d1b..a327620 100644 --- a/chat.h +++ b/chat.h @@ -133,7 +133,12 @@ void uiFormat( ) __attribute__((format(printf, 4, 5))); enum Edit { + EditHome, + EditEnd, + EditLeft, + EditRight, EditKill, + EditErase, EditInsert, EditEnter, }; diff --git a/edit.c b/edit.c index 68593d1..b6edb98 100644 --- a/edit.c +++ b/edit.c @@ -45,13 +45,33 @@ char *editTail(void) { return mbs; } +static void reserve(size_t index, size_t count) { + if (len + count > Cap) return; + memmove(&buf[index + count], &buf[index], sizeof(*buf) * (len - index)); + len += count; +} + +static void delete(size_t index, size_t count) { + if (index + count > len) return; + memmove( + &buf[index], &buf[index + count], sizeof(*buf) * (len - index - count) + ); + len -= count; +} + void edit(size_t id, enum Edit op, wchar_t ch) { switch (op) { - break; case EditKill: len = pos = 0; + break; case EditHome: pos = 0; + break; case EditEnd: pos = len; + break; case EditLeft: if (pos) pos--; + break; case EditRight: if (pos < len) pos++; + + break; case EditKill: len = pos = 0; + break; case EditErase: if (pos) delete(--pos, 1); + break; case EditInsert: { - if (len == Cap) break; - buf[pos++] = ch; - len++; + reserve(pos, 1); + if (pos < Cap) buf[pos++] = ch; } break; case EditEnter: { pos = 0; diff --git a/ui.c b/ui.c index ffe8748..f73020a 100644 --- a/ui.c +++ b/ui.c @@ -546,12 +546,20 @@ void uiShowNum(size_t num) { } static void keyCode(int code) { + size_t id = windows.active->id; switch (code) { break; case KEY_RESIZE: resize(); break; case KeyFocusIn: unmark(); break; case KeyFocusOut: windows.active->mark = true; break; case KeyPasteOn:; // TODO break; case KeyPasteOff:; // TODO + + break; case KEY_BACKSPACE: edit(id, EditErase, 0); + break; case KEY_END: edit(id, EditEnd, 0); + break; case KEY_ENTER: edit(id, EditEnter, 0); + break; case KEY_HOME: edit(id, EditHome, 0); + break; case KEY_LEFT: edit(id, EditLeft, 0); + break; case KEY_RIGHT: edit(id, EditRight, 0); } } @@ -567,6 +575,10 @@ static void keyMeta(wchar_t ch) { static void keyCtrl(wchar_t ch) { size_t id = windows.active->id; switch (ch) { + break; case L'?': edit(id, EditErase, 0); + break; case L'A': edit(id, EditHome, 0); + break; case L'E': edit(id, EditEnd, 0); + break; case L'H': edit(id, EditErase, 0); break; case L'J': edit(id, EditEnter, 0); break; case L'L': clearok(curscr, true); break; case L'U': edit(id, EditKill, 0); -- cgit 1.4.0 From 4343f35f9c837444ce6edfede212b89dea422544 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Fri, 7 Feb 2020 02:46:40 -0500 Subject: Add key bindings for IRC formatting --- catgirl.1 | 41 +++++++++++++++++++++++++++++++++++++++++ ui.c | 53 ++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 77 insertions(+), 17 deletions(-) (limited to 'ui.c') diff --git a/catgirl.1 b/catgirl.1 index 991f5b1..e746150 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -161,6 +161,47 @@ Insert a blank line in the window. Switch to window by number 0\(en9. .El . +.Ss IRC Formatting +.Bl -tag -width Ds -compact +.It Ic C-z b +Toggle bold. +.It Ic C-z c +Set or reset color. +.It Ic C-z i +Toggle italics. +.It Ic C-z o +Reset formatting. +.It Ic C-z r +Toggle reverse color. +.It Ic C-z u +Toggle underline. +.El +. +.Pp +To set colors, follow +.Ic C-z c +by one or two digits for the foreground color, +optionally followed by a comma +and one or two digits for the background color. +To reset color, follow +.Ic C-z c +by a non-digit. +. +.Pp +The color numbers are as follows: +.Pp +.Bl -column "99" "orange (dark yellow)" "15" "pink (light magenta)" +.It \ 0 Ta white Ta \ 8 Ta yellow +.It \ 1 Ta black Ta \ 9 Ta light green +.It \ 2 Ta blue Ta 10 Ta cyan +.It \ 3 Ta green Ta 11 Ta light cyan +.It \ 4 Ta red Ta 12 Ta light blue +.It \ 5 Ta brown (dark red) Ta 13 Ta pink (light magenta) +.It \ 6 Ta magenta Ta 14 Ta gray +.It \ 7 Ta orange (dark yellow) Ta 15 Ta light gray +.It 99 Ta default +.El +. .Sh FILES .Bl -tag -width Ds .It Pa $XDG_CONFIG_DIRS/catgirl diff --git a/ui.c b/ui.c index f73020a..d4ef241 100644 --- a/ui.c +++ b/ui.c @@ -261,14 +261,16 @@ static short mapColor(enum Color color) { } } +enum { B = '\2', C = '\3', O = '\17', R = '\26', I = '\35', U = '\37' }; + static void styleParse(struct Style *style, const char **str, size_t *len) { switch (**str) { - break; case '\2': (*str)++; style->attr ^= A_BOLD; - break; case '\17': (*str)++; *style = Reset; - break; case '\26': (*str)++; style->attr ^= A_REVERSE; - break; case '\35': (*str)++; style->attr ^= A_ITALIC; - break; case '\37': (*str)++; style->attr ^= A_UNDERLINE; - break; case '\3': { + break; case B: (*str)++; style->attr ^= A_BOLD; + break; case O: (*str)++; *style = Reset; + break; case R: (*str)++; style->attr ^= A_REVERSE; + break; case I: (*str)++; style->attr ^= A_ITALIC; + break; case U: (*str)++; style->attr ^= A_UNDERLINE; + break; case C: { (*str)++; if (!isdigit(**str)) { style->fg = Default; @@ -283,7 +285,7 @@ static void styleParse(struct Style *style, const char **str, size_t *len) { if (isdigit(**str)) style->bg = style->bg * 10 + *(*str)++ - '0'; } } - *len = strcspn(*str, "\2\3\17\26\35\37"); + *len = strcspn(*str, (const char[]) { B, C, O, R, I, U, '\0' }); } static void statusAdd(const char *str) { @@ -456,12 +458,12 @@ static void inputAdd(struct Style *style, const char *str) { styleParse(style, &str, &len); wattr_set(input, A_BOLD | A_REVERSE, 0, NULL); switch (*code) { - break; case '\2': waddch(input, 'B'); - break; case '\3': waddch(input, 'C'); - break; case '\17': waddch(input, 'O'); - break; case '\26': waddch(input, 'R'); - break; case '\35': waddch(input, 'I'); - break; case '\37': waddch(input, 'U'); + break; case B: waddch(input, 'B'); + break; case C: waddch(input, 'C'); + break; case O: waddch(input, 'O'); + break; case R: waddch(input, 'R'); + break; case I: waddch(input, 'I'); + break; case U: waddch(input, 'U'); } if (str - code > 1) waddnstr(input, &code[1], str - &code[1]); wattr_set( @@ -574,7 +576,7 @@ static void keyMeta(wchar_t ch) { static void keyCtrl(wchar_t ch) { size_t id = windows.active->id; - switch (ch) { + switch (ch ^ L'@') { break; case L'?': edit(id, EditErase, 0); break; case L'A': edit(id, EditHome, 0); break; case L'E': edit(id, EditEnd, 0); @@ -585,10 +587,22 @@ static void keyCtrl(wchar_t ch) { } } +static void keyStyle(wchar_t ch) { + size_t id = windows.active->id; + switch (iswcntrl(ch) ? ch ^ L'@' : towupper(ch)) { + break; case L'B': edit(id, EditInsert, B); + break; case L'C': edit(id, EditInsert, C); + break; case L'I': edit(id, EditInsert, I); + break; case L'O': edit(id, EditInsert, O); + break; case L'R': edit(id, EditInsert, R); + break; case L'U': edit(id, EditInsert, U); + } +} + void uiRead(void) { int ret; wint_t ch; - static bool meta; + static bool meta, style; while (ERR != (ret = wget_wch(input, &ch))) { if (ret == KEY_CODE_YES) { keyCode(ch); @@ -597,12 +611,17 @@ void uiRead(void) { continue; } else if (meta) { keyMeta(ch); + } else if (ch == (L'Z' ^ L'@')) { + style = true; + continue; + } else if (style) { + keyStyle(ch); } else if (iswcntrl(ch)) { - keyCtrl(ch ^ L'@'); + keyCtrl(ch); } else { edit(windows.active->id, EditInsert, ch); } - meta = false; + meta = style = false; } inputUpdate(); } -- cgit 1.4.0 From aed762368d8500fdb364e316e63486f801a453f7 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Fri, 7 Feb 2020 20:28:22 -0500 Subject: Show one cell to the right of the input cursor --- ui.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index d4ef241..f71672b 100644 --- a/ui.c +++ b/ui.c @@ -226,7 +226,7 @@ void uiDraw(void) { getyx(input, y, x); pnoutrefresh( input, - 0, (x > RIGHT ? x - RIGHT : 0), + 0, (x + 1 > RIGHT ? x + 1 - RIGHT : 0), BOTTOM, 0, BOTTOM, RIGHT ); -- cgit 1.4.0 From 5881a96638b63475bf48d506dbb659c481279790 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Fri, 7 Feb 2020 20:29:32 -0500 Subject: Only treat the first tab as the alignment point --- ui.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index f71672b..f556719 100644 --- a/ui.c +++ b/ui.c @@ -368,7 +368,7 @@ static void wordWrap(WINDOW *win, const char *str) { int align = 0; struct Style style = Reset; while (*str) { - if (*str == '\t') { + if (*str == '\t' && !align) { waddch(win, ' '); getyx(win, y, align); str++; -- cgit 1.4.0 From a26c9ae0bc4b74b250031492b6c5a0bee39dd4d8 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Fri, 7 Feb 2020 20:41:27 -0500 Subject: Use define_key for meta keys This will allow distinguishing meta from escape via ESCDELAY (which should probably be set to something quite a lot shorter than its default). --- ui.c | 60 +++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 25 deletions(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index f556719..147381e 100644 --- a/ui.c +++ b/ui.c @@ -146,13 +146,6 @@ static short colorPair(short fg, short bg) { return colorPairs++; } -enum { - KeyFocusIn = KEY_MAX + 1, - KeyFocusOut, - KeyPasteOn, - KeyPasteOff, -}; - // XXX: Assuming terminals will be fine with these even if they're unsupported, // since they're "private" modes. static const char *EnterFocusMode = "\33[?1004h"; @@ -188,6 +181,30 @@ static void errExit(int eval) { reset_shell_mode(); } +#define ENUM_KEY \ + X(KeyMeta0, "\0330") \ + X(KeyMeta1, "\0331") \ + X(KeyMeta2, "\0332") \ + X(KeyMeta3, "\0333") \ + X(KeyMeta4, "\0334") \ + X(KeyMeta5, "\0335") \ + X(KeyMeta6, "\0336") \ + X(KeyMeta7, "\0337") \ + X(KeyMeta8, "\0338") \ + X(KeyMeta9, "\0339") \ + X(KeyMetaM, "\33m") \ + X(KeyFocusIn, "\33[I") \ + X(KeyFocusOut, "\33[O") \ + X(KeyPasteOn, "\33[200~") \ + X(KeyPasteOff, "\33[201~") + +enum { + KeyMax = KEY_MAX, +#define X(id, seq) id, + ENUM_KEY +#undef X +}; + void uiInit(void) { initscr(); cbreak(); @@ -200,10 +217,9 @@ void uiInit(void) { to_status_line = "\33]2;"; from_status_line = "\7"; } - define_key("\33[I", KeyFocusIn); - define_key("\33[O", KeyFocusOut); - define_key("\33[200~", KeyPasteOn); - define_key("\33[201~", KeyPasteOff); +#define X(id, seq) define_key(seq, id); + ENUM_KEY +#undef X colorInit(); status = newwin(1, COLS, 0, 0); @@ -556,20 +572,19 @@ static void keyCode(int code) { break; case KeyPasteOn:; // TODO break; case KeyPasteOff:; // TODO + break; case KeyMetaM: waddch(windows.active->pad, '\n'); + break; case KEY_BACKSPACE: edit(id, EditErase, 0); break; case KEY_END: edit(id, EditEnd, 0); break; case KEY_ENTER: edit(id, EditEnter, 0); break; case KEY_HOME: edit(id, EditHome, 0); break; case KEY_LEFT: edit(id, EditLeft, 0); break; case KEY_RIGHT: edit(id, EditRight, 0); - } -} - -static void keyMeta(wchar_t ch) { - switch (ch) { - break; case L'm': waddch(windows.active->pad, '\n'); + break; default: { - if (ch >= L'0' && ch <= L'9') uiShowNum(ch - L'0'); + if (code >= KeyMeta0 && code <= KeyMeta9) { + uiShowNum(code - KeyMeta0); + } } } } @@ -602,15 +617,10 @@ static void keyStyle(wchar_t ch) { void uiRead(void) { int ret; wint_t ch; - static bool meta, style; + static bool style; while (ERR != (ret = wget_wch(input, &ch))) { if (ret == KEY_CODE_YES) { keyCode(ch); - } else if (ch == '\33') { - meta = true; - continue; - } else if (meta) { - keyMeta(ch); } else if (ch == (L'Z' ^ L'@')) { style = true; continue; @@ -621,7 +631,7 @@ void uiRead(void) { } else { edit(windows.active->id, EditInsert, ch); } - meta = style = false; + style = false; } inputUpdate(); } -- cgit 1.4.0 From fe5fd897052abd1909d1536056936a0417666459 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Fri, 7 Feb 2020 21:30:25 -0500 Subject: Populate completion with commands --- Makefile | 1 + chat.c | 1 + chat.h | 8 +++++ command.c | 6 ++++ complete.c | 110 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ edit.c | 3 ++ ui.c | 1 + 7 files changed, 130 insertions(+) create mode 100644 complete.c (limited to 'ui.c') diff --git a/Makefile b/Makefile index 5380d20..48aba7b 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ LDLIBS = -lcrypto -ltls -lncursesw OBJS += chat.o OBJS += command.o +OBJS += complete.o OBJS += config.o OBJS += edit.o OBJS += handle.o diff --git a/chat.c b/chat.c index c487722..91da6e3 100644 --- a/chat.c +++ b/chat.c @@ -110,6 +110,7 @@ int main(int argc, char *argv[]) { set(&self.network, host); set(&self.chanTypes, "#&"); set(&self.prefixes, "@+"); + commandComplete(); FILE *certFile = NULL; FILE *privFile = NULL; diff --git a/chat.h b/chat.h index a327620..f164e7a 100644 --- a/chat.h +++ b/chat.h @@ -118,6 +118,7 @@ void command(size_t id, char *input); const char *commandIsPrivmsg(size_t id, const char *input); const char *commandIsNotice(size_t id, const char *input); const char *commandIsAction(size_t id, const char *input); +void commandComplete(void); enum Heat { Cold, Warm, Hot }; void uiInit(void); @@ -140,12 +141,19 @@ enum Edit { EditKill, EditErase, EditInsert, + EditComplete, EditEnter, }; void edit(size_t id, enum Edit op, wchar_t ch); char *editHead(void); char *editTail(void); +const char *complete(size_t id, const char *prefix); +void completeAccept(void); +void completeReject(void); +void completeAdd(size_t id, const char *str, enum Color color); +void completeTouch(size_t id, const char *str, enum Color color); + FILE *configOpen(const char *path, const char *mode); int getopt_config( int argc, char *const *argv, diff --git a/command.c b/command.c index 3215322..41aacc9 100644 --- a/command.c +++ b/command.c @@ -136,3 +136,9 @@ void command(size_t id, char *input) { } } } + +void commandComplete(void) { + for (size_t i = 0; i < ARRAY_LEN(Commands); ++i) { + completeAdd(None, Commands[i].cmd, Default); + } +} diff --git a/complete.c b/complete.c new file mode 100644 index 0000000..b8f2dfc --- /dev/null +++ b/complete.c @@ -0,0 +1,110 @@ +/* Copyright (C) 2020 C. McEnroe + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include + +#include "chat.h" + +struct Node { + size_t id; + char *str; + enum Color color; + struct Node *prev; + struct Node *next; +}; + +static struct Node *alloc(size_t id, const char *str, enum Color color) { + struct Node *node = malloc(sizeof(*node)); + if (!node) err(EX_OSERR, "malloc"); + node->id = id; + node->str = strdup(str); + if (!node->str) err(EX_OSERR, "strdup"); + node->color = color; + node->prev = NULL; + node->next = NULL; + return node; +} + +static struct Node *head; +static struct Node *tail; + +static struct Node *detach(struct Node *node) { + if (node->prev) node->prev->next = node->next; + if (node->next) node->next->prev = node->prev; + if (head == node) head = node->next; + if (tail == node) tail = node->prev; + node->prev = NULL; + node->next = NULL; + return node; +} + +static struct Node *prepend(struct Node *node) { + node->prev = NULL; + node->next = head; + if (head) head->prev = node; + head = node; + if (!tail) tail = node; + return node; +} + +static struct Node *append(struct Node *node) { + node->next = NULL; + node->prev = tail; + if (tail) tail->next = node; + tail = node; + if (!head) head = node; + return node; +} + +static struct Node *find(size_t id, const char *str) { + for (struct Node *node = head; node; node = node->next) { + if (node->id == id && !strcmp(node->str, str)) return node; + } + return NULL; +} + +void completeAdd(size_t id, const char *str, enum Color color) { + if (!find(id, str)) append(alloc(id, str, color)); +} + +void completeTouch(size_t id, const char *str, enum Color color) { + struct Node *node = find(id, str); + prepend(node ? detach(node) : alloc(id, str, color)); +} + +static struct Node *match; + +const char *complete(size_t id, const char *prefix) { + for (match = (match ? match->next : head); match; match = match->next) { + if (match->id && match->id != id) continue; + if (strncasecmp(match->str, prefix, strlen(prefix))) continue; + return match->str; + } + return NULL; +} + +void completeAccept(void) { + if (match) prepend(detach(match)); + match = NULL; +} + +void completeReject(void) { + match = NULL; +} diff --git a/edit.c b/edit.c index b6edb98..0c50f33 100644 --- a/edit.c +++ b/edit.c @@ -73,6 +73,9 @@ void edit(size_t id, enum Edit op, wchar_t ch) { reserve(pos, 1); if (pos < Cap) buf[pos++] = ch; } + break; case EditComplete: { + // TODO + } break; case EditEnter: { pos = 0; command(id, editTail()); diff --git a/ui.c b/ui.c index 147381e..5a8f155 100644 --- a/ui.c +++ b/ui.c @@ -596,6 +596,7 @@ static void keyCtrl(wchar_t ch) { break; case L'A': edit(id, EditHome, 0); break; case L'E': edit(id, EditEnd, 0); break; case L'H': edit(id, EditErase, 0); + break; case L'I': edit(id, EditComplete, 0); break; case L'J': edit(id, EditEnter, 0); break; case L'L': clearok(curscr, true); break; case L'U': edit(id, EditKill, 0); -- cgit 1.4.0 From ef9bea6d601742b8e91eda59b914f8653463ef24 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Fri, 7 Feb 2020 22:25:09 -0500 Subject: Use atexit instead of err_set_exit Unsurprisingly, err_set_exit doesn't exist in GNU's err.h, but since it's safe to call reset_shell_mode on any kind of exit, just use atexit. --- ui.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 5a8f155..d9b067b 100644 --- a/ui.c +++ b/ui.c @@ -176,8 +176,7 @@ static void disableFlowControl(void) { if (error) err(EX_OSERR, "tcsetattr"); } -static void errExit(int eval) { - (void)eval; +static void errExit(void) { reset_shell_mode(); } @@ -211,7 +210,7 @@ void uiInit(void) { noecho(); disableFlowControl(); def_prog_mode(); - err_set_exit(errExit); + atexit(errExit); if (!to_status_line && !strncmp(termname(), "xterm", 5)) { to_status_line = "\33]2;"; -- cgit 1.4.0 From 71b05365368b400593c974a4e69a9369c4496036 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Fri, 7 Feb 2020 23:55:46 -0500 Subject: Revert "Only treat the first tab as the alignment point" This reverts commit 5881a96638b63475bf48d506dbb659c481279790. --- ui.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index d9b067b..374fa93 100644 --- a/ui.c +++ b/ui.c @@ -383,7 +383,7 @@ static void wordWrap(WINDOW *win, const char *str) { int align = 0; struct Style style = Reset; while (*str) { - if (*str == '\t' && !align) { + if (*str == '\t') { waddch(win, ' '); getyx(win, y, align); str++; -- cgit 1.4.0 From 0705f0931094706a3b758f33f487cf25a6f03cab Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Fri, 7 Feb 2020 23:56:41 -0500 Subject: Only treat the first tab as the alignment point --- ui.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 374fa93..e9ca3ef 100644 --- a/ui.c +++ b/ui.c @@ -384,9 +384,14 @@ static void wordWrap(WINDOW *win, const char *str) { struct Style style = Reset; while (*str) { if (*str == '\t') { - waddch(win, ' '); - getyx(win, y, align); - str++; + if (align) { + waddch(win, '\t'); + str++; + } else { + waddch(win, ' '); + getyx(win, y, align); + str++; + } } else if (*str == ' ') { getyx(win, y, x); const char *word = &str[strspn(str, " ")]; -- cgit 1.4.0 From 55e721da42bb57833e9c99e2b87cf50d6c035f07 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 8 Feb 2020 02:13:02 -0500 Subject: Check return values of newwin/newpad --- ui.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index e9ca3ef..6d1338b 100644 --- a/ui.c +++ b/ui.c @@ -109,6 +109,7 @@ static struct Window *windowFor(size_t id) { window->id = id; window->pad = newpad(BufferCap, COLS); + if (!window->pad) err(EX_OSERR, "newpad"); scrollok(window->pad, true); wmove(window->pad, BufferCap - 1, 0); window->scroll = BufferCap; @@ -211,20 +212,25 @@ void uiInit(void) { disableFlowControl(); def_prog_mode(); atexit(errExit); + colorInit(); if (!to_status_line && !strncmp(termname(), "xterm", 5)) { to_status_line = "\33]2;"; from_status_line = "\7"; } + #define X(id, seq) define_key(seq, id); ENUM_KEY #undef X - colorInit(); status = newwin(1, COLS, 0, 0); + if (!status) err(EX_OSERR, "newwin"); + input = newpad(1, 512); + if (!input) err(EX_OSERR, "newpad"); keypad(input, true); nodelay(input, true); + windows.active = windowFor(Network); uiShow(); } -- cgit 1.4.0 From 943502ea82b3965b4f652146ca03262ac6390f83 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 8 Feb 2020 02:26:00 -0500 Subject: Add /close --- catgirl.1 | 2 ++ chat.h | 2 ++ command.c | 12 ++++++++++++ ui.c | 28 ++++++++++++++++++++++++++++ 4 files changed, 44 insertions(+) (limited to 'ui.c') diff --git a/catgirl.1 b/catgirl.1 index 0702f58..9314e7a 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -146,6 +146,8 @@ Send a raw IRC command. . .Ss UI Commands .Bl -tag -width Ds +.It Ic /close Op Ar name | num +Close the named, numbered or current window. .It Ic /window Ar name Switch to window by name. .It Ic /window Ar num , Ic / Ns Ar num diff --git a/chat.h b/chat.h index 081370e..9daa38c 100644 --- a/chat.h +++ b/chat.h @@ -128,6 +128,8 @@ void uiHide(void); void uiDraw(void); void uiShowID(size_t id); void uiShowNum(size_t num); +void uiCloseID(size_t id); +void uiCloseNum(size_t id); void uiRead(void); void uiWrite(size_t id, enum Heat heat, const time_t *time, const char *str); void uiFormat( diff --git a/command.c b/command.c index 9047e95..e33c57e 100644 --- a/command.c +++ b/command.c @@ -100,10 +100,22 @@ static void commandWindow(size_t id, char *params) { } } +static void commandClose(size_t id, char *params) { + if (!params) { + uiCloseID(id); + } else if (isdigit(params[0])) { + uiCloseNum(strtoul(params, NULL, 10)); + } else { + id = idFind(params); + if (id) uiCloseID(id); + } +} + static const struct Handler { const char *cmd; Command *fn; } Commands[] = { + { "/close", commandClose }, { "/join", commandJoin }, { "/me", commandMe }, { "/nick", commandNick }, diff --git a/ui.c b/ui.c index 6d1338b..c738617 100644 --- a/ui.c +++ b/ui.c @@ -573,6 +573,34 @@ void uiShowNum(size_t num) { windowShow(window); } +static void windowClose(struct Window *window) { + if (window->id == Network) return; + if (windows.active == window) { + windowShow(window->prev ? window->prev : window->next); + } + if (windows.other == window) windows.other = NULL; + windowRemove(window); + for (size_t i = 0; i < BufferCap; ++i) { + free(window->buffer.lines[i]); + } + delwin(window->pad); + free(window); + statusUpdate(); +} + +void uiCloseID(size_t id) { + windowClose(windowFor(id)); +} + +void uiCloseNum(size_t num) { + struct Window *window = windows.head; + for (size_t i = 0; i < num; ++i) { + window = window->next; + if (!window) return; + } + windowClose(window); +} + static void keyCode(int code) { size_t id = windows.active->id; switch (code) { -- cgit 1.4.0 From 29bd788660af90855f6acce411506aeaf14f8808 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 8 Feb 2020 16:56:49 -0500 Subject: Simplify(?) reflow buffer loop --- ui.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index c738617..29062be 100644 --- a/ui.c +++ b/ui.c @@ -459,10 +459,12 @@ void uiFormat( static void reflow(struct Window *window) { werase(window->pad); wmove(window->pad, BufferCap - 1, 0); - size_t len = window->buffer.len; - for (size_t i = (len > BufferCap ? len - BufferCap : 0); i < len; ++i) { + struct Buffer *buffer = &window->buffer; + for (size_t i = 0; i < BufferCap; ++i) { + char *line = buffer->lines[(buffer->len + i) % BufferCap]; + if (!line) continue; waddch(window->pad, '\n'); - wordWrap(window->pad, window->buffer.lines[i % BufferCap]); + wordWrap(window->pad, line); } } -- cgit 1.4.0 From e0714a9b7e88bc2de9ec4b4c0d0a74dc3012b847 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 00:39:09 -0500 Subject: Switch to "other" window if closing active window --- ui.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 29062be..23bf929 100644 --- a/ui.c +++ b/ui.c @@ -578,7 +578,11 @@ void uiShowNum(size_t num) { static void windowClose(struct Window *window) { if (window->id == Network) return; if (windows.active == window) { - windowShow(window->prev ? window->prev : window->next); + if (windows.other && windows.other != window) { + windowShow(windows.other); + } else { + windowShow(window->prev ? window->prev : window->next); + } } if (windows.other == window) windows.other = NULL; windowRemove(window); -- cgit 1.4.0 From 2d62ea9e30e7249e7f3e5bc3e60f5c8e97b3e2cc Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 01:28:24 -0500 Subject: Simplify edit buffer conversion and input rendering --- chat.h | 3 +-- edit.c | 25 +++++++++++----------- ui.c | 76 ++++++++++++++++++++++++++++++++++++------------------------------ 3 files changed, 55 insertions(+), 49 deletions(-) (limited to 'ui.c') diff --git a/chat.h b/chat.h index 896549e..914fde6 100644 --- a/chat.h +++ b/chat.h @@ -156,8 +156,7 @@ enum Edit { EditEnter, }; void edit(size_t id, enum Edit op, wchar_t ch); -char *editHead(void); -char *editTail(void); +char *editBuffer(size_t *pos); const char *complete(size_t id, const char *prefix); void completeAccept(void); diff --git a/edit.c b/edit.c index 0c50f33..38dadcd 100644 --- a/edit.c +++ b/edit.c @@ -27,21 +27,22 @@ static wchar_t buf[Cap]; static size_t len; static size_t pos; -char *editHead(void) { +char *editBuffer(size_t *mbsPos) { static char mbs[MB_LEN_MAX * Cap]; + const wchar_t *ptr = buf; - size_t n = wcsnrtombs(mbs, &ptr, pos, sizeof(mbs) - 1, NULL); - assert(n != (size_t)-1); - mbs[n] = '\0'; - return mbs; -} + size_t mbsLen = wcsnrtombs(mbs, &ptr, pos, sizeof(mbs) - 1, NULL); + assert(mbsLen != (size_t)-1); + if (mbsPos) *mbsPos = mbsLen; -char *editTail(void) { - static char mbs[MB_LEN_MAX * Cap]; - const wchar_t *ptr = &buf[pos]; - size_t n = wcsnrtombs(mbs, &ptr, len - pos, sizeof(mbs) - 1, NULL); + ptr = &buf[pos]; + size_t n = wcsnrtombs( + &mbs[mbsLen], &ptr, len - pos, sizeof(mbs) - mbsLen - 1, NULL + ); assert(n != (size_t)-1); - mbs[n] = '\0'; + mbsLen += n; + + mbs[mbsLen] = '\0'; return mbs; } @@ -78,7 +79,7 @@ void edit(size_t id, enum Edit op, wchar_t ch) { } break; case EditEnter: { pos = 0; - command(id, editTail()); + command(id, editBuffer(NULL)); len = 0; } } diff --git a/ui.c b/ui.c index 23bf929..c342339 100644 --- a/ui.c +++ b/ui.c @@ -507,48 +507,54 @@ static void inputAdd(struct Style *style, const char *str) { static void inputUpdate(void) { size_t id = windows.active->id; - const char *nick = self.nick; - const char *head = editHead(); + size_t pos; + char *buf = editBuffer(&pos); + const char *skip = NULL; - const char *pre = ""; - const char *suf = " "; - struct Style style = { .fg = self.color, .bg = Default }; - struct Style reset = Reset; - if (NULL != (skip = commandIsPrivmsg(id, head))) { - pre = "<"; - suf = "> "; - } else if (NULL != (skip = commandIsNotice(id, head))) { - pre = "-"; - suf = "- "; - reset.fg = LightGray; - } else if (NULL != (skip = commandIsAction(id, head))) { - style.attr |= A_ITALIC; - pre = "* "; - reset.attr |= A_ITALIC; + struct Style init = { .fg = self.color, .bg = Default }; + struct Style rest = Reset; + const char *prefix = ""; + const char *prompt = (self.nick ? self.nick : ""); + const char *suffix = ""; + if (NULL != (skip = commandIsPrivmsg(id, buf))) { + prefix = "<"; suffix = "> "; + } else if (NULL != (skip = commandIsNotice(id, buf))) { + prefix = "-"; suffix = "- "; + rest.fg = LightGray; + } else if (NULL != (skip = commandIsAction(id, buf))) { + init.attr |= A_ITALIC; + prefix = "* "; suffix = " "; + rest.attr |= A_ITALIC; } else if (id == Debug) { - skip = head; - style.fg = Gray; - pre = "<<"; - nick = NULL; + skip = buf; + init.fg = Gray; + prompt = "<< "; + } else { + prompt = ""; + } + if (skip && skip > &buf[pos]) { + skip = NULL; + prefix = prompt = suffix = ""; } int y, x; wmove(input, 0, 0); - if (skip) { - wattr_set( - input, - style.attr | colorAttr(mapColor(style.fg)), - colorPair(mapColor(style.fg), mapColor(style.bg)), - NULL - ); - waddstr(input, pre); - if (nick) waddstr(input, nick); - waddstr(input, suf); - } - style = reset; - inputAdd(&style, (skip ? skip : head)); + wattr_set( + input, + init.attr | colorAttr(mapColor(init.fg)), + colorPair(mapColor(init.fg), mapColor(init.bg)), + NULL + ); + waddstr(input, prefix); + waddstr(input, prompt); + waddstr(input, suffix); + struct Style style = rest; + char p = buf[pos]; + buf[pos] = '\0'; + inputAdd(&style, (skip ? skip : buf)); getyx(input, y, x); - inputAdd(&style, editTail()); + buf[pos] = p; + inputAdd(&style, &buf[pos]); wclrtoeol(input); wmove(input, y, x); } -- cgit 1.4.0 From e6e2021d480adab9dd35873810f040524d97092a Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 03:41:46 -0500 Subject: Add C-b and C-f --- catgirl.1 | 4 ++++ ui.c | 2 ++ 2 files changed, 6 insertions(+) (limited to 'ui.c') diff --git a/catgirl.1 b/catgirl.1 index fd00105..f68e6c3 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -199,8 +199,12 @@ Switch to window by number. .Bl -tag -width Ds -compact .It Ic C-a Move to beginning of line. +.It Ic C-b +Move left. .It Ic C-e Move to end of line. +.It Ic C-f +Move right. .It Ic C-u Delete line. .El diff --git a/ui.c b/ui.c index c342339..4478478 100644 --- a/ui.c +++ b/ui.c @@ -644,7 +644,9 @@ static void keyCtrl(wchar_t ch) { switch (ch ^ L'@') { break; case L'?': edit(id, EditErase, 0); break; case L'A': edit(id, EditHome, 0); + break; case L'B': edit(id, EditLeft, 0); break; case L'E': edit(id, EditEnd, 0); + break; case L'F': edit(id, EditRight, 0); break; case L'H': edit(id, EditErase, 0); break; case L'I': edit(id, EditComplete, 0); break; case L'J': edit(id, EditEnter, 0); -- cgit 1.4.0 From 282de9af30793f9935fe521ad95ffc253bd4f474 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 03:45:44 -0500 Subject: Add C-d --- chat.h | 1 + edit.c | 5 +++-- ui.c | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) (limited to 'ui.c') diff --git a/chat.h b/chat.h index 914fde6..d6d9e1c 100644 --- a/chat.h +++ b/chat.h @@ -151,6 +151,7 @@ enum Edit { EditRight, EditKill, EditErase, + EditDelete, EditInsert, EditComplete, EditEnter, diff --git a/edit.c b/edit.c index f058f0a..c30e725 100644 --- a/edit.c +++ b/edit.c @@ -138,8 +138,9 @@ void edit(size_t id, enum Edit op, wchar_t ch) { break; case EditLeft: if (pos) pos--; break; case EditRight: if (pos < len) pos++; - break; case EditKill: len = pos = 0; - break; case EditErase: if (pos) delete(--pos, 1); + break; case EditKill: len = pos = 0; + break; case EditErase: if (pos) delete(--pos, 1); + break; case EditDelete: delete(pos, 1); break; case EditInsert: { reserve(pos, 1); diff --git a/ui.c b/ui.c index 4478478..8e502ca 100644 --- a/ui.c +++ b/ui.c @@ -625,6 +625,7 @@ static void keyCode(int code) { break; case KeyMetaM: waddch(windows.active->pad, '\n'); break; case KEY_BACKSPACE: edit(id, EditErase, 0); + break; case KEY_DC: edit(id, EditDelete, 0); break; case KEY_END: edit(id, EditEnd, 0); break; case KEY_ENTER: edit(id, EditEnter, 0); break; case KEY_HOME: edit(id, EditHome, 0); @@ -645,6 +646,7 @@ static void keyCtrl(wchar_t ch) { break; case L'?': edit(id, EditErase, 0); break; case L'A': edit(id, EditHome, 0); break; case L'B': edit(id, EditLeft, 0); + break; case L'D': edit(id, EditDelete, 0); break; case L'E': edit(id, EditEnd, 0); break; case L'F': edit(id, EditRight, 0); break; case L'H': edit(id, EditErase, 0); -- cgit 1.4.0 From d7c96fc81b71b77b30511d6526fe3acaa84c39ee Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 03:56:18 -0500 Subject: Add C-k Also rename all the edit ops to something consistent. --- catgirl.1 | 6 +++++- chat.h | 15 ++++++++------- edit.c | 17 +++++++++-------- ui.c | 29 +++++++++++++++-------------- 4 files changed, 37 insertions(+), 30 deletions(-) (limited to 'ui.c') diff --git a/catgirl.1 b/catgirl.1 index f68e6c3..a356fe0 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -201,12 +201,16 @@ Switch to window by number. Move to beginning of line. .It Ic C-b Move left. +.It Ic C-d +Delete next character. .It Ic C-e Move to end of line. .It Ic C-f Move right. +.It Ic C-k +Delete to end of line. .It Ic C-u -Delete line. +Delete to beginning of line. .El . .Ss Window Keys diff --git a/chat.h b/chat.h index d6d9e1c..aa1bcc1 100644 --- a/chat.h +++ b/chat.h @@ -145,13 +145,14 @@ void uiFormat( ) __attribute__((format(printf, 4, 5))); enum Edit { - EditHome, - EditEnd, - EditLeft, - EditRight, - EditKill, - EditErase, - EditDelete, + EditHead, + EditTail, + EditPrev, + EditNext, + EditKillPrev, + EditKillNext, + EditDeletePrev, + EditDeleteNext, EditInsert, EditComplete, EditEnter, diff --git a/edit.c b/edit.c index c30e725..7fcff40 100644 --- a/edit.c +++ b/edit.c @@ -133,14 +133,15 @@ static void tabReject(void) { void edit(size_t id, enum Edit op, wchar_t ch) { size_t init = pos; switch (op) { - break; case EditHome: pos = 0; - break; case EditEnd: pos = len; - break; case EditLeft: if (pos) pos--; - break; case EditRight: if (pos < len) pos++; - - break; case EditKill: len = pos = 0; - break; case EditErase: if (pos) delete(--pos, 1); - break; case EditDelete: delete(pos, 1); + break; case EditHead: pos = 0; + break; case EditTail: pos = len; + break; case EditPrev: if (pos) pos--; + break; case EditNext: if (pos < len) pos++; + + break; case EditDeletePrev: if (pos) delete(--pos, 1); + break; case EditDeleteNext: delete(pos, 1); + break; case EditKillPrev: delete(0, pos); pos = 0; + break; case EditKillNext: delete(pos, len - pos); break; case EditInsert: { reserve(pos, 1); diff --git a/ui.c b/ui.c index 8e502ca..d83a1f3 100644 --- a/ui.c +++ b/ui.c @@ -624,13 +624,13 @@ static void keyCode(int code) { break; case KeyMetaM: waddch(windows.active->pad, '\n'); - break; case KEY_BACKSPACE: edit(id, EditErase, 0); - break; case KEY_DC: edit(id, EditDelete, 0); - break; case KEY_END: edit(id, EditEnd, 0); + break; case KEY_BACKSPACE: edit(id, EditDeletePrev, 0); + break; case KEY_DC: edit(id, EditDeleteNext, 0); + break; case KEY_END: edit(id, EditTail, 0); break; case KEY_ENTER: edit(id, EditEnter, 0); - break; case KEY_HOME: edit(id, EditHome, 0); - break; case KEY_LEFT: edit(id, EditLeft, 0); - break; case KEY_RIGHT: edit(id, EditRight, 0); + break; case KEY_HOME: edit(id, EditHead, 0); + break; case KEY_LEFT: edit(id, EditPrev, 0); + break; case KEY_RIGHT: edit(id, EditNext, 0); break; default: { if (code >= KeyMeta0 && code <= KeyMeta9) { @@ -643,17 +643,18 @@ static void keyCode(int code) { static void keyCtrl(wchar_t ch) { size_t id = windows.active->id; switch (ch ^ L'@') { - break; case L'?': edit(id, EditErase, 0); - break; case L'A': edit(id, EditHome, 0); - break; case L'B': edit(id, EditLeft, 0); - break; case L'D': edit(id, EditDelete, 0); - break; case L'E': edit(id, EditEnd, 0); - break; case L'F': edit(id, EditRight, 0); - break; case L'H': edit(id, EditErase, 0); + break; case L'?': edit(id, EditDeletePrev, 0); + break; case L'A': edit(id, EditHead, 0); + break; case L'B': edit(id, EditPrev, 0); + break; case L'D': edit(id, EditDeleteNext, 0); + break; case L'E': edit(id, EditTail, 0); + break; case L'F': edit(id, EditNext, 0); + break; case L'H': edit(id, EditDeletePrev, 0); break; case L'I': edit(id, EditComplete, 0); break; case L'J': edit(id, EditEnter, 0); + break; case L'K': edit(id, EditKillNext, 0); break; case L'L': clearok(curscr, true); - break; case L'U': edit(id, EditKill, 0); + break; case L'U': edit(id, EditKillPrev, 0); } } -- cgit 1.4.0 From b08c2d03efa08bd319a0665d12bef34df08ab283 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 04:20:07 -0500 Subject: Add M-b and M-f --- catgirl.1 | 4 ++++ chat.h | 2 ++ edit.c | 8 ++++++++ ui.c | 4 ++++ 4 files changed, 18 insertions(+) (limited to 'ui.c') diff --git a/catgirl.1 b/catgirl.1 index a356fe0..9cb208e 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -211,6 +211,10 @@ Move right. Delete to end of line. .It Ic C-u Delete to beginning of line. +.It Ic M-b +Move to previous word. +.It Ic M-f +Move to next word. .El . .Ss Window Keys diff --git a/chat.h b/chat.h index aa1bcc1..6b68eae 100644 --- a/chat.h +++ b/chat.h @@ -149,6 +149,8 @@ enum Edit { EditTail, EditPrev, EditNext, + EditPrevWord, + EditNextWord, EditKillPrev, EditKillNext, EditDeletePrev, diff --git a/edit.c b/edit.c index 7fcff40..38b2dea 100644 --- a/edit.c +++ b/edit.c @@ -137,6 +137,14 @@ void edit(size_t id, enum Edit op, wchar_t ch) { break; case EditTail: pos = len; break; case EditPrev: if (pos) pos--; break; case EditNext: if (pos < len) pos++; + break; case EditPrevWord: { + if (pos) pos--; + while (pos && buf[pos - 1] != L' ') pos--; + } + break; case EditNextWord: { + if (pos < len) pos++; + while (pos < len && buf[pos] != L' ') pos++; + } break; case EditDeletePrev: if (pos) delete(--pos, 1); break; case EditDeleteNext: delete(pos, 1); diff --git a/ui.c b/ui.c index d83a1f3..7811e88 100644 --- a/ui.c +++ b/ui.c @@ -192,6 +192,8 @@ static void errExit(void) { X(KeyMeta7, "\0337") \ X(KeyMeta8, "\0338") \ X(KeyMeta9, "\0339") \ + X(KeyMetaB, "\033b") \ + X(KeyMetaF, "\033f") \ X(KeyMetaM, "\33m") \ X(KeyFocusIn, "\33[I") \ X(KeyFocusOut, "\33[O") \ @@ -622,6 +624,8 @@ static void keyCode(int code) { break; case KeyPasteOn:; // TODO break; case KeyPasteOff:; // TODO + break; case KeyMetaB: edit(id, EditPrevWord, 0); + break; case KeyMetaF: edit(id, EditNextWord, 0); break; case KeyMetaM: waddch(windows.active->pad, '\n'); break; case KEY_BACKSPACE: edit(id, EditDeletePrev, 0); -- cgit 1.4.0 From 3cd830681e25022a8a3936ca9fe58d149fbe493a Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 04:22:41 -0500 Subject: Rename kill ops --- chat.h | 4 ++-- edit.c | 4 ++-- ui.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'ui.c') diff --git a/chat.h b/chat.h index 6b68eae..ac56f51 100644 --- a/chat.h +++ b/chat.h @@ -151,8 +151,8 @@ enum Edit { EditNext, EditPrevWord, EditNextWord, - EditKillPrev, - EditKillNext, + EditDeleteHead, + EditDeleteTail, EditDeletePrev, EditDeleteNext, EditInsert, diff --git a/edit.c b/edit.c index 38b2dea..7b20079 100644 --- a/edit.c +++ b/edit.c @@ -148,8 +148,8 @@ void edit(size_t id, enum Edit op, wchar_t ch) { break; case EditDeletePrev: if (pos) delete(--pos, 1); break; case EditDeleteNext: delete(pos, 1); - break; case EditKillPrev: delete(0, pos); pos = 0; - break; case EditKillNext: delete(pos, len - pos); + break; case EditDeleteHead: delete(0, pos); pos = 0; + break; case EditDeleteTail: delete(pos, len - pos); break; case EditInsert: { reserve(pos, 1); diff --git a/ui.c b/ui.c index 7811e88..e3b9cb5 100644 --- a/ui.c +++ b/ui.c @@ -656,9 +656,9 @@ static void keyCtrl(wchar_t ch) { break; case L'H': edit(id, EditDeletePrev, 0); break; case L'I': edit(id, EditComplete, 0); break; case L'J': edit(id, EditEnter, 0); - break; case L'K': edit(id, EditKillNext, 0); + break; case L'K': edit(id, EditDeleteTail, 0); break; case L'L': clearok(curscr, true); - break; case L'U': edit(id, EditKillPrev, 0); + break; case L'U': edit(id, EditDeleteHead, 0); } } -- cgit 1.4.0 From 5e637324c9f2b16a602c1b66081390624598c703 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 04:32:32 -0500 Subject: Add C-w and M-d --- catgirl.1 | 4 ++++ chat.h | 2 ++ edit.c | 17 +++++++++++++++-- ui.c | 7 +++++-- 4 files changed, 26 insertions(+), 4 deletions(-) (limited to 'ui.c') diff --git a/catgirl.1 b/catgirl.1 index 9cb208e..6f8256b 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -211,8 +211,12 @@ Move right. Delete to end of line. .It Ic C-u Delete to beginning of line. +.It Ic C-w +Delete previous word. .It Ic M-b Move to previous word. +.It Ic M-d +Delete next word. .It Ic M-f Move to next word. .El diff --git a/chat.h b/chat.h index ac56f51..fc18b15 100644 --- a/chat.h +++ b/chat.h @@ -155,6 +155,8 @@ enum Edit { EditDeleteTail, EditDeletePrev, EditDeleteNext, + EditDeletePrevWord, + EditDeleteNextWord, EditInsert, EditComplete, EditEnter, diff --git a/edit.c b/edit.c index 7b20079..47478ec 100644 --- a/edit.c +++ b/edit.c @@ -146,10 +146,23 @@ void edit(size_t id, enum Edit op, wchar_t ch) { while (pos < len && buf[pos] != L' ') pos++; } - break; case EditDeletePrev: if (pos) delete(--pos, 1); - break; case EditDeleteNext: delete(pos, 1); break; case EditDeleteHead: delete(0, pos); pos = 0; break; case EditDeleteTail: delete(pos, len - pos); + break; case EditDeletePrev: if (pos) delete(--pos, 1); + break; case EditDeleteNext: delete(pos, 1); + break; case EditDeletePrevWord: { + if (!pos) break; + size_t word = pos - 1; + while (word && buf[word - 1] != L' ') word--; + delete(word, pos - word); + pos = word; + } + break; case EditDeleteNextWord: { + if (pos == len) break; + size_t word = pos + 1; + while (word < len && buf[word] != L' ') word++; + delete(pos, word - pos); + } break; case EditInsert: { reserve(pos, 1); diff --git a/ui.c b/ui.c index e3b9cb5..65b4760 100644 --- a/ui.c +++ b/ui.c @@ -192,8 +192,9 @@ static void errExit(void) { X(KeyMeta7, "\0337") \ X(KeyMeta8, "\0338") \ X(KeyMeta9, "\0339") \ - X(KeyMetaB, "\033b") \ - X(KeyMetaF, "\033f") \ + X(KeyMetaB, "\33b") \ + X(KeyMetaD, "\33d") \ + X(KeyMetaF, "\33f") \ X(KeyMetaM, "\33m") \ X(KeyFocusIn, "\33[I") \ X(KeyFocusOut, "\33[O") \ @@ -625,6 +626,7 @@ static void keyCode(int code) { break; case KeyPasteOff:; // TODO break; case KeyMetaB: edit(id, EditPrevWord, 0); + break; case KeyMetaD: edit(id, EditDeleteNextWord, 0); break; case KeyMetaF: edit(id, EditNextWord, 0); break; case KeyMetaM: waddch(windows.active->pad, '\n'); @@ -659,6 +661,7 @@ static void keyCtrl(wchar_t ch) { break; case L'K': edit(id, EditDeleteTail, 0); break; case L'L': clearok(curscr, true); break; case L'U': edit(id, EditDeleteHead, 0); + break; case L'W': edit(id, EditDeletePrevWord, 0); } } -- cgit 1.4.0 From 2aa2005339750e64a587f6117ae21960e975e211 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 07:09:51 -0500 Subject: Add C-y This is weechat's binding for it. --- catgirl.1 | 2 ++ chat.h | 1 + edit.c | 26 ++++++++++++++++++++++---- ui.c | 7 +++++-- 4 files changed, 30 insertions(+), 6 deletions(-) (limited to 'ui.c') diff --git a/catgirl.1 b/catgirl.1 index 752a9d2..2a3828d 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -226,6 +226,8 @@ Delete to end of line. Delete to beginning of line. .It Ic C-w Delete previous word. +.It Ic C-y +Paste previously deleted text. .It Ic M-b Move to previous word. .It Ic M-d diff --git a/chat.h b/chat.h index fc18b15..24360f0 100644 --- a/chat.h +++ b/chat.h @@ -157,6 +157,7 @@ enum Edit { EditDeleteNext, EditDeletePrevWord, EditDeleteNextWord, + EditPaste, EditInsert, EditComplete, EditEnter, diff --git a/edit.c b/edit.c index 47478ec..16fa910 100644 --- a/edit.c +++ b/edit.c @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -46,14 +47,24 @@ char *editBuffer(size_t *mbsPos) { return mbs; } -static void reserve(size_t index, size_t count) { - if (len + count > Cap) return; +static struct { + wchar_t buf[Cap]; + size_t len; +} cut; + +static bool reserve(size_t index, size_t count) { + if (len + count > Cap) return false; memmove(&buf[index + count], &buf[index], sizeof(*buf) * (len - index)); len += count; + return true; } static void delete(size_t index, size_t count) { if (index + count > len) return; + if (count > 1) { + memcpy(cut.buf, &buf[index], sizeof(*buf) * count); + cut.len = count; + } memmove( &buf[index], &buf[index + count], sizeof(*buf) * (len - index - count) ); @@ -163,10 +174,17 @@ void edit(size_t id, enum Edit op, wchar_t ch) { while (word < len && buf[word] != L' ') word++; delete(pos, word - pos); } + break; case EditPaste: { + if (reserve(pos, cut.len)) { + memcpy(&buf[pos], cut.buf, sizeof(*buf) * cut.len); + pos += cut.len; + } + } break; case EditInsert: { - reserve(pos, 1); - if (pos < Cap) buf[pos++] = ch; + if (reserve(pos, 1)) { + buf[pos++] = ch; + } } break; case EditComplete: { tabComplete(id); diff --git a/ui.c b/ui.c index 65b4760..d946854 100644 --- a/ui.c +++ b/ui.c @@ -166,12 +166,14 @@ void uiHide(void) { endwin(); } -static void disableFlowControl(void) { +// Gain use of C-q, C-s, C-z, C-y, C-o. +static void acquireKeys(void) { struct termios term; int error = tcgetattr(STDOUT_FILENO, &term); if (error) err(EX_OSERR, "tcgetattr"); term.c_iflag &= ~IXON; term.c_cc[VSUSP] = _POSIX_VDISABLE; + term.c_cc[VDSUSP] = _POSIX_VDISABLE; term.c_cc[VDISCARD] = _POSIX_VDISABLE; error = tcsetattr(STDOUT_FILENO, TCSADRAIN, &term); if (error) err(EX_OSERR, "tcsetattr"); @@ -212,7 +214,7 @@ void uiInit(void) { initscr(); cbreak(); noecho(); - disableFlowControl(); + acquireKeys(); def_prog_mode(); atexit(errExit); colorInit(); @@ -662,6 +664,7 @@ static void keyCtrl(wchar_t ch) { break; case L'L': clearok(curscr, true); break; case L'U': edit(id, EditDeleteHead, 0); break; case L'W': edit(id, EditDeletePrevWord, 0); + break; case L'Y': edit(id, EditPaste, 0); } } -- cgit 1.4.0 From 26eefa35c90760536a2045a5d097e7670613c4b0 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 07:46:30 -0500 Subject: Add C-n and C-p --- catgirl.1 | 4 ++++ ui.c | 3 +++ 2 files changed, 7 insertions(+) (limited to 'ui.c') diff --git a/catgirl.1 b/catgirl.1 index 2a3828d..5648c92 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -242,6 +242,10 @@ Complete nick, channel or command. .Bl -tag -width Ds -compact .It Ic C-l Redraw the UI. +.It Ic C-n +Switch to next window. +.It Ic C-p +Switch to previous window. .It Ic M-m Insert a blank line in the window. .It Ic M- Ns Ar n diff --git a/ui.c b/ui.c index d946854..8d0f0f7 100644 --- a/ui.c +++ b/ui.c @@ -565,6 +565,7 @@ static void inputUpdate(void) { } static void windowShow(struct Window *window) { + if (!window) return; touchwin(window->pad); windows.other = windows.active; windows.active = window; @@ -662,6 +663,8 @@ static void keyCtrl(wchar_t ch) { break; case L'J': edit(id, EditEnter, 0); break; case L'K': edit(id, EditDeleteTail, 0); break; case L'L': clearok(curscr, true); + break; case L'N': windowShow(windows.active->next); + break; case L'P': windowShow(windows.active->prev); break; case L'U': edit(id, EditDeleteHead, 0); break; case L'W': edit(id, EditDeletePrevWord, 0); break; case L'Y': edit(id, EditPaste, 0); -- cgit 1.4.0 From 16316679a136bae76c73e328858eceb7fffae72c Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 08:14:22 -0500 Subject: Add M-a --- catgirl.1 | 2 ++ ui.c | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+) (limited to 'ui.c') diff --git a/catgirl.1 b/catgirl.1 index 5648c92..e5d17b4 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -246,6 +246,8 @@ Redraw the UI. Switch to next window. .It Ic C-p Switch to previous window. +.It Ic M-a +Cycle through unread windows. .It Ic M-m Insert a blank line in the window. .It Ic M- Ns Ar n diff --git a/ui.c b/ui.c index 8d0f0f7..ace7c1e 100644 --- a/ui.c +++ b/ui.c @@ -194,6 +194,7 @@ static void errExit(void) { X(KeyMeta7, "\0337") \ X(KeyMeta8, "\0338") \ X(KeyMeta9, "\0339") \ + X(KeyMetaA, "\33a") \ X(KeyMetaB, "\33b") \ X(KeyMetaD, "\33d") \ X(KeyMetaF, "\33f") \ @@ -619,6 +620,29 @@ void uiCloseNum(size_t num) { windowClose(window); } +static void showAuto(void) { + static bool origin; + if (!origin) { + windows.other = windows.active; + origin = true; + } + struct Window *other = windows.other; + for (struct Window *window = windows.head; window; window = window->next) { + if (window->heat < Hot) continue; + windowShow(window); + windows.other = other; + return; + } + for (struct Window *window = windows.head; window; window = window->next) { + if (window->heat < Warm) continue; + windowShow(window); + windows.other = other; + return; + } + windowShow(windows.other); + origin = false; +} + static void keyCode(int code) { size_t id = windows.active->id; switch (code) { @@ -628,6 +652,7 @@ static void keyCode(int code) { break; case KeyPasteOn:; // TODO break; case KeyPasteOff:; // TODO + break; case KeyMetaA: showAuto(); break; case KeyMetaB: edit(id, EditPrevWord, 0); break; case KeyMetaD: edit(id, EditDeleteNextWord, 0); break; case KeyMetaF: edit(id, EditNextWord, 0); -- cgit 1.4.0 From 8ce6d4c37715f37b95dcb5a302438e7db4c00ad3 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 08:17:05 -0500 Subject: Add M-/ --- catgirl.1 | 2 ++ ui.c | 3 +++ 2 files changed, 5 insertions(+) (limited to 'ui.c') diff --git a/catgirl.1 b/catgirl.1 index e5d17b4..f11cae0 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -246,6 +246,8 @@ Redraw the UI. Switch to next window. .It Ic C-p Switch to previous window. +.It Ic M-/ +Switch to previously selected window. .It Ic M-a Cycle through unread windows. .It Ic M-m diff --git a/ui.c b/ui.c index ace7c1e..33d6a15 100644 --- a/ui.c +++ b/ui.c @@ -199,6 +199,7 @@ static void errExit(void) { X(KeyMetaD, "\33d") \ X(KeyMetaF, "\33f") \ X(KeyMetaM, "\33m") \ + X(KeyMetaSlash, "\33/") \ X(KeyFocusIn, "\33[I") \ X(KeyFocusOut, "\33[O") \ X(KeyPasteOn, "\33[200~") \ @@ -652,6 +653,8 @@ static void keyCode(int code) { break; case KeyPasteOn:; // TODO break; case KeyPasteOff:; // TODO + break; case KeyMetaSlash: windowShow(windows.other); + break; case KeyMetaA: showAuto(); break; case KeyMetaB: edit(id, EditPrevWord, 0); break; case KeyMetaD: edit(id, EditDeleteNextWord, 0); -- cgit 1.4.0 From 8451543b98c086daf7436c0f6d192f1d665680c2 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 08:52:17 -0500 Subject: Implement scrolling! --- ui.c | 44 +++++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 11 deletions(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 33d6a15..b4a2dee 100644 --- a/ui.c +++ b/ui.c @@ -367,13 +367,22 @@ static void statusUpdate(void) { fflush(stdout); } -static void unmark(void) { - windows.active->heat = Cold; - windows.active->unread = 0; - windows.active->mark = false; +static void unmark(struct Window *window) { + if (window->scroll < BufferCap) return; + window->heat = Cold; + window->unread = 0; + window->mark = false; statusUpdate(); } +static void windowScroll(struct Window *window, int n) { + if (window->scroll == BufferCap) window->mark = true; + window->scroll += n; + if (window->scroll < WINDOW_LINES) window->scroll = WINDOW_LINES; + if (window->scroll > BufferCap) window->scroll = BufferCap; + if (window->scroll == BufferCap) unmark(window); +} + static int wordWidth(const char *str) { size_t len = strcspn(str, " "); int width = 0; @@ -388,11 +397,12 @@ static int wordWidth(const char *str) { return width; } -static void wordWrap(WINDOW *win, const char *str) { +static int wordWrap(WINDOW *win, const char *str) { int y, x, width; getmaxyx(win, y, width); size_t len; + int lines = 0; int align = 0; struct Style style = Reset; while (*str) { @@ -409,6 +419,7 @@ static void wordWrap(WINDOW *win, const char *str) { getyx(win, y, x); const char *word = &str[strspn(str, " ")]; if (width - x - 1 <= wordWidth(word)) { + lines++; waddch(win, '\n'); getyx(win, y, x); wmove(win, y, align); @@ -432,6 +443,7 @@ static void wordWrap(WINDOW *win, const char *str) { waddnstr(win, str, len); str += len; } + return lines; } void uiWrite(size_t id, enum Heat heat, const time_t *src, const char *str) { @@ -439,15 +451,20 @@ void uiWrite(size_t id, enum Heat heat, const time_t *src, const char *str) { time_t clock = (src ? *src : time(NULL)); bufferPush(&window->buffer, clock, str); + int lines = 1; waddch(window->pad, '\n'); if (window->mark && heat > Cold) { if (!window->unread++) { + lines++; waddch(window->pad, '\n'); } window->heat = heat; statusUpdate(); } - wordWrap(window->pad, str); + lines += wordWrap(window->pad, str); + if (window->scroll < BufferCap) { + windowScroll(window, -lines); + } if (heat > Warm) beep(); } @@ -573,7 +590,7 @@ static void windowShow(struct Window *window) { windows.active = window; windows.other->mark = true; inputUpdate(); - unmark(); + unmark(windows.active); } void uiShowID(size_t id) { @@ -645,11 +662,12 @@ static void showAuto(void) { } static void keyCode(int code) { - size_t id = windows.active->id; + struct Window *window = windows.active; + size_t id = window->id; switch (code) { break; case KEY_RESIZE: resize(); - break; case KeyFocusIn: unmark(); - break; case KeyFocusOut: windows.active->mark = true; + break; case KeyFocusIn: unmark(window); + break; case KeyFocusOut: window->mark = true; break; case KeyPasteOn:; // TODO break; case KeyPasteOff:; // TODO @@ -659,15 +677,19 @@ static void keyCode(int code) { break; case KeyMetaB: edit(id, EditPrevWord, 0); break; case KeyMetaD: edit(id, EditDeleteNextWord, 0); break; case KeyMetaF: edit(id, EditNextWord, 0); - break; case KeyMetaM: waddch(windows.active->pad, '\n'); + break; case KeyMetaM: waddch(window->pad, '\n'); break; case KEY_BACKSPACE: edit(id, EditDeletePrev, 0); break; case KEY_DC: edit(id, EditDeleteNext, 0); + break; case KEY_DOWN: windowScroll(window, +1); break; case KEY_END: edit(id, EditTail, 0); break; case KEY_ENTER: edit(id, EditEnter, 0); break; case KEY_HOME: edit(id, EditHead, 0); break; case KEY_LEFT: edit(id, EditPrev, 0); + break; case KEY_NPAGE: windowScroll(window, +(WINDOW_LINES - 2)); + break; case KEY_PPAGE: windowScroll(window, -(WINDOW_LINES - 2)); break; case KEY_RIGHT: edit(id, EditNext, 0); + break; case KEY_UP: windowScroll(window, -1); break; default: { if (code >= KeyMeta0 && code <= KeyMeta9) { -- cgit 1.4.0 From 11f2de1a29ce3df9f661898436b3db2868137bf9 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 09:18:26 -0500 Subject: Add The Scroll Bar --- ui.c | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index b4a2dee..28749c8 100644 --- a/ui.c +++ b/ui.c @@ -48,6 +48,7 @@ static WINDOW *status; static WINDOW *input; +static WINDOW *scrollBar; enum { BufferCap = 512 }; struct Buffer { @@ -119,6 +120,10 @@ static struct Window *windowFor(size_t id) { return window; } +static bool windowScrolled(struct Window *window) { + return window->scroll < BufferCap; +} + static short colorPairs; static void colorInit(void) { @@ -238,18 +243,27 @@ void uiInit(void) { keypad(input, true); nodelay(input, true); + scrollBar = newwin(1, COLS, LINES - 2, 0); + short fg = 8 + COLOR_BLACK; + wbkgd(scrollBar, '~' | colorAttr(fg) | COLOR_PAIR(colorPair(fg, -1))); + windows.active = windowFor(Network); uiShow(); } void uiDraw(void) { wnoutrefresh(status); + int scrolled = windowScrolled(windows.active); pnoutrefresh( windows.active->pad, - windows.active->scroll - WINDOW_LINES, 0, + windows.active->scroll - WINDOW_LINES + scrolled, 0, 1, 0, - BOTTOM - 1, RIGHT + BOTTOM - 1 - scrolled, RIGHT ); + if (scrolled) { + touchwin(scrollBar); + wnoutrefresh(scrollBar); + } int y, x; getyx(input, y, x); pnoutrefresh( @@ -368,10 +382,11 @@ static void statusUpdate(void) { } static void unmark(struct Window *window) { - if (window->scroll < BufferCap) return; - window->heat = Cold; - window->unread = 0; - window->mark = false; + if (!windowScrolled(window)) { + window->heat = Cold; + window->unread = 0; + window->mark = false; + } statusUpdate(); } @@ -462,7 +477,7 @@ void uiWrite(size_t id, enum Heat heat, const time_t *src, const char *str) { statusUpdate(); } lines += wordWrap(window->pad, str); - if (window->scroll < BufferCap) { + if (windowScrolled(window)) { windowScroll(window, -lines); } if (heat > Warm) beep(); -- cgit 1.4.0 From 347fabc2fef5890d4ce16e83eb820ed828789163 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 11:50:56 -0500 Subject: Invert the direction of window->scroll --- ui.c | 58 +++++++++++++++++++++++++++------------------------------- 1 file changed, 27 insertions(+), 31 deletions(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 28749c8..050ec2e 100644 --- a/ui.c +++ b/ui.c @@ -44,11 +44,11 @@ #define BOTTOM (LINES - 1) #define RIGHT (COLS - 1) -#define WINDOW_LINES (LINES - 2) +#define PAGE_LINES (LINES - 2) static WINDOW *status; +static WINDOW *marker; static WINDOW *input; -static WINDOW *scrollBar; enum { BufferCap = 512 }; struct Buffer { @@ -66,6 +66,7 @@ static void bufferPush(struct Buffer *buffer, time_t time, const char *line) { if (!buffer->lines[i]) err(EX_OSERR, "strdup"); } +enum { WindowLines = BufferCap }; struct Window { size_t id; struct Buffer buffer; @@ -109,21 +110,16 @@ static struct Window *windowFor(size_t id) { if (!window) err(EX_OSERR, "malloc"); window->id = id; - window->pad = newpad(BufferCap, COLS); + window->pad = newpad(WindowLines, COLS); if (!window->pad) err(EX_OSERR, "newpad"); scrollok(window->pad, true); wmove(window->pad, BufferCap - 1, 0); - window->scroll = BufferCap; window->mark = true; windowAdd(window); return window; } -static bool windowScrolled(struct Window *window) { - return window->scroll < BufferCap; -} - static short colorPairs; static void colorInit(void) { @@ -238,31 +234,31 @@ void uiInit(void) { status = newwin(1, COLS, 0, 0); if (!status) err(EX_OSERR, "newwin"); + marker = newwin(1, COLS, LINES - 2, 0); + short fg = 8 + COLOR_BLACK; + wbkgd(marker, '~' | colorAttr(fg) | COLOR_PAIR(colorPair(fg, -1))); + input = newpad(1, 512); if (!input) err(EX_OSERR, "newpad"); keypad(input, true); nodelay(input, true); - scrollBar = newwin(1, COLS, LINES - 2, 0); - short fg = 8 + COLOR_BLACK; - wbkgd(scrollBar, '~' | colorAttr(fg) | COLOR_PAIR(colorPair(fg, -1))); - windows.active = windowFor(Network); uiShow(); } void uiDraw(void) { wnoutrefresh(status); - int scrolled = windowScrolled(windows.active); + struct Window *window = windows.active; pnoutrefresh( - windows.active->pad, - windows.active->scroll - WINDOW_LINES + scrolled, 0, + window->pad, + WindowLines - window->scroll - PAGE_LINES + !!window->scroll, 0, 1, 0, - BOTTOM - 1 - scrolled, RIGHT + BOTTOM - 1 - !!window->scroll, RIGHT ); - if (scrolled) { - touchwin(scrollBar); - wnoutrefresh(scrollBar); + if (window->scroll) { + touchwin(marker); + wnoutrefresh(marker); } int y, x; getyx(input, y, x); @@ -382,7 +378,7 @@ static void statusUpdate(void) { } static void unmark(struct Window *window) { - if (!windowScrolled(window)) { + if (!window->scroll) { window->heat = Cold; window->unread = 0; window->mark = false; @@ -391,11 +387,13 @@ static void unmark(struct Window *window) { } static void windowScroll(struct Window *window, int n) { - if (window->scroll == BufferCap) window->mark = true; + if (!window->scroll) window->mark = true; window->scroll += n; - if (window->scroll < WINDOW_LINES) window->scroll = WINDOW_LINES; - if (window->scroll > BufferCap) window->scroll = BufferCap; - if (window->scroll == BufferCap) unmark(window); + if (window->scroll > WindowLines - PAGE_LINES) { + window->scroll = WindowLines - PAGE_LINES; + } + if (window->scroll < 0) window->scroll = 0; + if (!window->scroll) unmark(window); } static int wordWidth(const char *str) { @@ -477,9 +475,7 @@ void uiWrite(size_t id, enum Heat heat, const time_t *src, const char *str) { statusUpdate(); } lines += wordWrap(window->pad, str); - if (windowScrolled(window)) { - windowScroll(window, -lines); - } + if (window->scroll) windowScroll(window, lines); if (heat > Warm) beep(); } @@ -696,15 +692,15 @@ static void keyCode(int code) { break; case KEY_BACKSPACE: edit(id, EditDeletePrev, 0); break; case KEY_DC: edit(id, EditDeleteNext, 0); - break; case KEY_DOWN: windowScroll(window, +1); + break; case KEY_DOWN: windowScroll(window, -1); break; case KEY_END: edit(id, EditTail, 0); break; case KEY_ENTER: edit(id, EditEnter, 0); break; case KEY_HOME: edit(id, EditHead, 0); break; case KEY_LEFT: edit(id, EditPrev, 0); - break; case KEY_NPAGE: windowScroll(window, +(WINDOW_LINES - 2)); - break; case KEY_PPAGE: windowScroll(window, -(WINDOW_LINES - 2)); + break; case KEY_NPAGE: windowScroll(window, -(PAGE_LINES - 2)); + break; case KEY_PPAGE: windowScroll(window, +(PAGE_LINES - 2)); break; case KEY_RIGHT: edit(id, EditNext, 0); - break; case KEY_UP: windowScroll(window, -1); + break; case KEY_UP: windowScroll(window, +1); break; default: { if (code >= KeyMeta0 && code <= KeyMeta9) { -- cgit 1.4.0 From f0e2c089c943abc2d298646e9fd988aa2a1c0c16 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 12:13:51 -0500 Subject: Add M-u --- catgirl.1 | 2 ++ ui.c | 28 +++++++++++++++++++--------- 2 files changed, 21 insertions(+), 9 deletions(-) (limited to 'ui.c') diff --git a/catgirl.1 b/catgirl.1 index f11cae0..eb7310d 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -254,6 +254,8 @@ Cycle through unread windows. Insert a blank line in the window. .It Ic M- Ns Ar n Switch to window by number 0\(en9. +.It Ic M-u +Scroll to first unread line. .El . .Ss IRC Formatting diff --git a/ui.c b/ui.c index 050ec2e..9abfffc 100644 --- a/ui.c +++ b/ui.c @@ -71,10 +71,11 @@ struct Window { size_t id; struct Buffer buffer; WINDOW *pad; + bool mark; enum Heat heat; - int unread; + int unreadCount; + int unreadLines; int scroll; - bool mark; struct Window *prev; struct Window *next; }; @@ -200,6 +201,7 @@ static void errExit(void) { X(KeyMetaD, "\33d") \ X(KeyMetaF, "\33f") \ X(KeyMetaM, "\33m") \ + X(KeyMetaU, "\33u") \ X(KeyMetaSlash, "\33/") \ X(KeyFocusIn, "\33[I") \ X(KeyFocusOut, "\33[O") \ @@ -347,7 +349,7 @@ static void statusUpdate(void) { int num; const struct Window *window; for (num = 0, window = windows.head; window; ++num, window = window->next) { - if (!window->unread && window != windows.active) continue; + if (!window->unreadCount && window != windows.active) continue; int unread; char buf[256]; snprintf( @@ -355,10 +357,10 @@ static void statusUpdate(void) { idColors[window->id], (window == windows.active ? "\26" : ""), num, idNames[window->id], &unread, (window->heat > Warm ? White : idColors[window->id]), - window->unread, + window->unreadCount, idColors[window->id] ); - if (!window->unread) buf[unread] = '\0'; + if (!window->unreadCount) buf[unread] = '\0'; statusAdd(buf); } wclrtoeol(status); @@ -368,9 +370,9 @@ static void statusUpdate(void) { snprintf( buf, sizeof(buf), "%s %s%n (%d)", self.network, idNames[windows.active->id], - &unread, windows.active->unread + &unread, windows.active->unreadCount ); - if (!windows.active->unread) buf[unread] = '\0'; + if (!windows.active->unreadCount) buf[unread] = '\0'; putp(to_status_line); putp(buf); putp(from_status_line); @@ -380,7 +382,7 @@ static void statusUpdate(void) { static void unmark(struct Window *window) { if (!window->scroll) { window->heat = Cold; - window->unread = 0; + window->unreadCount = 0; window->mark = false; } statusUpdate(); @@ -396,6 +398,11 @@ static void windowScroll(struct Window *window, int n) { if (!window->scroll) unmark(window); } +static void windowScrollUnread(struct Window *window) { + window->scroll = 0; + windowScroll(window, window->unreadLines - PAGE_LINES); +} + static int wordWidth(const char *str) { size_t len = strcspn(str, " "); int width = 0; @@ -466,8 +473,9 @@ void uiWrite(size_t id, enum Heat heat, const time_t *src, const char *str) { int lines = 1; waddch(window->pad, '\n'); + if (window->mark && !window->unreadCount) window->unreadLines = 0; if (window->mark && heat > Cold) { - if (!window->unread++) { + if (!window->unreadCount++) { lines++; waddch(window->pad, '\n'); } @@ -475,6 +483,7 @@ void uiWrite(size_t id, enum Heat heat, const time_t *src, const char *str) { statusUpdate(); } lines += wordWrap(window->pad, str); + if (window->mark) window->unreadLines += lines; if (window->scroll) windowScroll(window, lines); if (heat > Warm) beep(); } @@ -689,6 +698,7 @@ static void keyCode(int code) { break; case KeyMetaD: edit(id, EditDeleteNextWord, 0); break; case KeyMetaF: edit(id, EditNextWord, 0); break; case KeyMetaM: waddch(window->pad, '\n'); + break; case KeyMetaU: windowScrollUnread(window); break; case KEY_BACKSPACE: edit(id, EditDeletePrev, 0); break; case KEY_DC: edit(id, EditDeleteNext, 0); -- cgit 1.4.0 From 5254e1035c5945407ee354276f839426fc17e432 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 14:09:27 -0500 Subject: Add /help Now with automatic search! Also had to fix the SIGCHLD handling... --- catgirl.1 | 6 ++++++ chat.c | 2 ++ command.c | 19 +++++++++++++++++++ ui.c | 7 +++++++ 4 files changed, 34 insertions(+) (limited to 'ui.c') diff --git a/catgirl.1 b/catgirl.1 index eb7310d..5772db3 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -179,6 +179,12 @@ or matching Toggle logging in the .Sy window. +.It Ic /help Op Ar search +View this manual. +Type +.Ic q +to return to +.Nm . .It Ic /open Op Ar count Open each of .Ar count diff --git a/chat.c b/chat.c index dbad242..ff74485 100644 --- a/chat.c +++ b/chat.c @@ -191,6 +191,7 @@ int main(int argc, char *argv[]) { if (signals[SIGINT] || signals[SIGTERM]) break; if (signals[SIGCHLD]) { + signals[SIGCHLD] = 0; int status; while (0 < waitpid(-1, &status, WNOHANG)) { if (WIFEXITED(status) && WEXITSTATUS(status)) { @@ -206,6 +207,7 @@ int main(int argc, char *argv[]) { ); } } + uiShow(); } if (signals[SIGWINCH]) { diff --git a/command.c b/command.c index f88a6d5..44d0d54 100644 --- a/command.c +++ b/command.c @@ -18,6 +18,7 @@ #include #include #include +#include #include "chat.h" @@ -158,6 +159,23 @@ static void commandCopy(size_t id, char *params) { urlCopyMatch(id, params); } +static void commandHelp(size_t id, char *params) { + (void)id; + uiHide(); + + pid_t pid = fork(); + if (pid < 0) err(EX_OSERR, "fork"); + if (pid) return; + + char buf[256]; + snprintf(buf, sizeof(buf), "ip%s$", (params ? params : "COMMANDS")); + setenv("LESS", buf, 1); + execlp("man", "man", "1", "catgirl", NULL); + dup2(procPipe[1], STDERR_FILENO); + warn("man"); + _exit(EX_UNAVAILABLE); +} + static const struct Handler { const char *cmd; Command *fn; @@ -165,6 +183,7 @@ static const struct Handler { { "/close", commandClose }, { "/copy", commandCopy }, { "/debug", commandDebug }, + { "/help", commandHelp }, { "/join", commandJoin }, { "/me", commandMe }, { "/names", commandNames }, diff --git a/ui.c b/ui.c index 9abfffc..66a9c59 100644 --- a/ui.c +++ b/ui.c @@ -156,13 +156,18 @@ static const char *ExitFocusMode = "\33[?1004l"; static const char *EnterPasteMode = "\33[?2004h"; static const char *ExitPasteMode = "\33[?2004l"; +static bool hidden; + void uiShow(void) { putp(EnterFocusMode); putp(EnterPasteMode); fflush(stdout); + hidden = false; + uiDraw(); } void uiHide(void) { + hidden = true; putp(ExitFocusMode); putp(ExitPasteMode); endwin(); @@ -250,6 +255,7 @@ void uiInit(void) { } void uiDraw(void) { + if (hidden) return; wnoutrefresh(status); struct Window *window = windows.active; pnoutrefresh( @@ -755,6 +761,7 @@ static void keyStyle(wchar_t ch) { } void uiRead(void) { + if (hidden) return; int ret; wint_t ch; static bool style; -- cgit 1.4.0 From 3a5ce4d10f1d0b4e2bcf1d958d42665d5cb0db96 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 15:02:34 -0500 Subject: Remove unnecessary uiDraw --- ui.c | 1 - 1 file changed, 1 deletion(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 66a9c59..b3b98dd 100644 --- a/ui.c +++ b/ui.c @@ -163,7 +163,6 @@ void uiShow(void) { putp(EnterPasteMode); fflush(stdout); hidden = false; - uiDraw(); } void uiHide(void) { -- cgit 1.4.0 From 7470a705b3b0c577f7531bbbfa28ab2156678c94 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 18:16:01 -0500 Subject: Add M-l --- catgirl.1 | 7 +++++++ ui.c | 45 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) (limited to 'ui.c') diff --git a/catgirl.1 b/catgirl.1 index ac558a9..5e333a8 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -260,6 +260,13 @@ Switch to previous window. Switch to previously selected window. .It Ic M-a Cycle through unread windows. +.It Ic M-l +List the contents of the window +without word-wrapping. +Press +.Ic Enter +to return to +.Nm . .It Ic M-m Insert a blank line in the window. .It Ic M- Ns Ar n diff --git a/ui.c b/ui.c index b3b98dd..66c695f 100644 --- a/ui.c +++ b/ui.c @@ -157,6 +157,7 @@ static const char *EnterPasteMode = "\33[?2004h"; static const char *ExitPasteMode = "\33[?2004l"; static bool hidden; +static bool waiting; void uiShow(void) { putp(EnterFocusMode); @@ -204,6 +205,7 @@ static void errExit(void) { X(KeyMetaB, "\33b") \ X(KeyMetaD, "\33d") \ X(KeyMetaF, "\33f") \ + X(KeyMetaL, "\33l") \ X(KeyMetaM, "\33m") \ X(KeyMetaU, "\33u") \ X(KeyMetaSlash, "\33/") \ @@ -528,6 +530,37 @@ static void resize(void) { statusUpdate(); } +static void bufferList(struct Buffer *buffer) { + uiHide(); + waiting = true; + for (size_t i = 0; i < BufferCap; ++i) { + time_t time = buffer->times[(buffer->len + i) % BufferCap]; + const char *line = buffer->lines[(buffer->len + i) % BufferCap]; + if (!line) continue; + + struct tm *tm = localtime(&time); + if (!tm) continue; + char buf[sizeof("[00:00:00]")]; + strftime(buf, sizeof(buf), "[%T]", tm); + vid_attr(colorAttr(mapColor(Gray)), colorPair(mapColor(Gray), -1), NULL); + printf("%s\t", buf); + + size_t len; + struct Style style = Reset; + while (*line) { + styleParse(&style, &line, &len); + vid_attr( + style.attr | colorAttr(mapColor(style.fg)), + colorPair(mapColor(style.fg), mapColor(style.bg)), + NULL + ); + if (len) printf("%.*s", (int)len, line); + line += len; + } + printf("\n"); + } +} + static void inputAdd(struct Style *style, const char *str) { size_t len; while (*str) { @@ -702,6 +735,7 @@ static void keyCode(int code) { break; case KeyMetaB: edit(id, EditPrevWord, 0); break; case KeyMetaD: edit(id, EditDeleteNextWord, 0); break; case KeyMetaF: edit(id, EditNextWord, 0); + break; case KeyMetaL: bufferList(&window->buffer); break; case KeyMetaM: waddch(window->pad, '\n'); break; case KeyMetaU: windowScrollUnread(window); @@ -760,7 +794,16 @@ static void keyStyle(wchar_t ch) { } void uiRead(void) { - if (hidden) return; + if (hidden) { + if (waiting) { + uiShow(); + flushinp(); + waiting = false; + } else { + return; + } + } + int ret; wint_t ch; static bool style; -- cgit 1.4.0 From f3fa88ef920c5fdfd5b4fd6cc0b00a38d9d25c53 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 01:09:03 -0500 Subject: Fix M-a so it properly cycles back to where it started --- ui.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 66c695f..d94351a 100644 --- a/ui.c +++ b/ui.c @@ -697,12 +697,10 @@ void uiCloseNum(size_t num) { } static void showAuto(void) { - static bool origin; - if (!origin) { - windows.other = windows.active; - origin = true; + static struct Window *other; + if (windows.other != other) { + other = windows.active; } - struct Window *other = windows.other; for (struct Window *window = windows.head; window; window = window->next) { if (window->heat < Hot) continue; windowShow(window); @@ -716,7 +714,6 @@ static void showAuto(void) { return; } windowShow(windows.other); - origin = false; } static void keyCode(int code) { -- cgit 1.4.0 From 05fc01b2483dfa203a4f905f294b04325e4111eb Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 01:59:08 -0500 Subject: Simplify mark, heat, unread tracking --- ui.c | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index d94351a..c331a7c 100644 --- a/ui.c +++ b/ui.c @@ -71,11 +71,11 @@ struct Window { size_t id; struct Buffer buffer; WINDOW *pad; + int scroll; bool mark; enum Heat heat; int unreadCount; int unreadLines; - int scroll; struct Window *prev; struct Window *next; }; @@ -114,7 +114,7 @@ static struct Window *windowFor(size_t id) { window->pad = newpad(WindowLines, COLS); if (!window->pad) err(EX_OSERR, "newpad"); scrollok(window->pad, true); - wmove(window->pad, BufferCap - 1, 0); + wmove(window->pad, WindowLines - 1, 0); window->mark = true; windowAdd(window); @@ -356,7 +356,7 @@ static void statusUpdate(void) { int num; const struct Window *window; for (num = 0, window = windows.head; window; ++num, window = window->next) { - if (!window->unreadCount && window != windows.active) continue; + if (!window->heat && window != windows.active) continue; int unread; char buf[256]; snprintf( @@ -367,7 +367,7 @@ static void statusUpdate(void) { window->unreadCount, idColors[window->id] ); - if (!window->unreadCount) buf[unread] = '\0'; + if (!window->mark || !window->unreadCount) buf[unread] = '\0'; statusAdd(buf); } wclrtoeol(status); @@ -379,30 +379,38 @@ static void statusUpdate(void) { self.network, idNames[windows.active->id], &unread, windows.active->unreadCount ); - if (!windows.active->unreadCount) buf[unread] = '\0'; + if (!windows.active->mark || !windows.active->unreadCount) { + buf[unread] = '\0'; + } putp(to_status_line); putp(buf); putp(from_status_line); fflush(stdout); } +static void mark(struct Window *window) { + if (window->scroll) return; + window->mark = true; + window->unreadCount = 0; + window->unreadLines = 0; +} + static void unmark(struct Window *window) { if (!window->scroll) { - window->heat = Cold; - window->unreadCount = 0; window->mark = false; + window->heat = Cold; } statusUpdate(); } static void windowScroll(struct Window *window, int n) { - if (!window->scroll) window->mark = true; + mark(window); window->scroll += n; if (window->scroll > WindowLines - PAGE_LINES) { window->scroll = WindowLines - PAGE_LINES; } if (window->scroll < 0) window->scroll = 0; - if (!window->scroll) unmark(window); + unmark(window); } static void windowScrollUnread(struct Window *window) { @@ -480,7 +488,6 @@ void uiWrite(size_t id, enum Heat heat, const time_t *src, const char *str) { int lines = 1; waddch(window->pad, '\n'); - if (window->mark && !window->unreadCount) window->unreadLines = 0; if (window->mark && heat > Cold) { if (!window->unreadCount++) { lines++; @@ -646,9 +653,9 @@ static void windowShow(struct Window *window) { touchwin(window->pad); windows.other = windows.active; windows.active = window; - windows.other->mark = true; - inputUpdate(); + mark(windows.other); unmark(windows.active); + inputUpdate(); } void uiShowID(size_t id) { @@ -722,7 +729,7 @@ static void keyCode(int code) { switch (code) { break; case KEY_RESIZE: resize(); break; case KeyFocusIn: unmark(window); - break; case KeyFocusOut: window->mark = true; + break; case KeyFocusOut: mark(window); break; case KeyPasteOn:; // TODO break; case KeyPasteOff:; // TODO -- cgit 1.4.0 From b6061a70d78cfce7318b503891fd0290ad0cccaf Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 02:50:32 -0500 Subject: Update line count for words longer than lines --- ui.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index c331a7c..7c933f6 100644 --- a/ui.c +++ b/ui.c @@ -454,7 +454,7 @@ static int wordWrap(WINDOW *win, const char *str) { getyx(win, y, x); const char *word = &str[strspn(str, " ")]; if (width - x - 1 <= wordWidth(word)) { - lines++; + lines += 1 + (align + wordWidth(word)) / width; waddch(win, '\n'); getyx(win, y, x); wmove(win, y, align); -- cgit 1.4.0 From 3c898576524a8384a81a21aa58d0ba1b3d354322 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 02:55:21 -0500 Subject: Move scroll marker on resize --- ui.c | 1 + 1 file changed, 1 insertion(+) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 7c933f6..d35c02d 100644 --- a/ui.c +++ b/ui.c @@ -527,6 +527,7 @@ static void reflow(struct Window *window) { } static void resize(void) { + mvwin(marker, LINES - 2, 0); int height, width; getmaxyx(windows.active->pad, height, width); if (width == COLS) return; -- cgit 1.4.0 From 7a8024ae3dbe0c2c52b6536fb73dd33071e00637 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 03:10:08 -0500 Subject: Always increase unreadLines So that if you switch to a window and some new activity happens before you press M-u, it'll still jump to the right place. --- ui.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index d35c02d..1d1e46c 100644 --- a/ui.c +++ b/ui.c @@ -497,7 +497,7 @@ void uiWrite(size_t id, enum Heat heat, const time_t *src, const char *str) { statusUpdate(); } lines += wordWrap(window->pad, str); - if (window->mark) window->unreadLines += lines; + window->unreadLines += lines; if (window->scroll) windowScroll(window, lines); if (heat > Warm) beep(); } -- cgit 1.4.0 From 7957ca0ecd5c3e512c836a1b19dbd75628c439fb Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 03:29:38 -0500 Subject: Only make windows hotter A warm message shouldn't clear a window's hotness. --- ui.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 1d1e46c..7f13e5b 100644 --- a/ui.c +++ b/ui.c @@ -493,7 +493,7 @@ void uiWrite(size_t id, enum Heat heat, const time_t *src, const char *str) { lines++; waddch(window->pad, '\n'); } - window->heat = heat; + if (window->heat < heat) window->heat = heat; statusUpdate(); } lines += wordWrap(window->pad, str); -- cgit 1.4.0 From 2c9ff1717b00b9373240dad8200bb8a766ac05cb Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 03:37:17 -0500 Subject: Recalculate unreadLines on reflow --- ui.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 7f13e5b..666a93d 100644 --- a/ui.c +++ b/ui.c @@ -415,7 +415,7 @@ static void windowScroll(struct Window *window, int n) { static void windowScrollUnread(struct Window *window) { window->scroll = 0; - windowScroll(window, window->unreadLines - PAGE_LINES); + windowScroll(window, window->unreadLines - PAGE_LINES + 1); } static int wordWidth(const char *str) { @@ -516,13 +516,18 @@ void uiFormat( static void reflow(struct Window *window) { werase(window->pad); - wmove(window->pad, BufferCap - 1, 0); + wmove(window->pad, WindowLines - 1, 0); + window->unreadLines = 0; struct Buffer *buffer = &window->buffer; for (size_t i = 0; i < BufferCap; ++i) { char *line = buffer->lines[(buffer->len + i) % BufferCap]; if (!line) continue; waddch(window->pad, '\n'); - wordWrap(window->pad, line); + if (i >= (size_t)(BufferCap - window->unreadCount)) { + window->unreadLines += 1 + wordWrap(window->pad, line); + } else { + wordWrap(window->pad, line); + } } } -- cgit 1.4.0 From 218bfbac3257f8484e62d2f047b46417be7aff94 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 04:17:07 -0500 Subject: Support all 99 IRC colors Corresponding ANSI colors from the table on ircdocs. --- ui.c | 73 ++++++++++++++++++++++++++++++++++++-------------------------------- 1 file changed, 39 insertions(+), 34 deletions(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 666a93d..e36585a 100644 --- a/ui.c +++ b/ui.c @@ -133,11 +133,11 @@ static void colorInit(void) { } static attr_t colorAttr(short fg) { - return (fg >= COLORS ? A_BOLD : A_NORMAL); + return (fg >= COLORS && fg < 16 ? A_BOLD : A_NORMAL); } static short colorPair(short fg, short bg) { - if (bg == -1) return 1 + fg; + if (bg == -1 && fg < 16) return 1 + fg; fg %= COLORS; bg %= COLORS; for (short pair = 17; pair < colorPairs; ++pair) { @@ -286,27 +286,32 @@ struct Style { }; static const struct Style Reset = { A_NORMAL, Default, Default }; -static short mapColor(enum Color color) { - switch (color) { - break; case White: return 8 + COLOR_WHITE; - break; case Black: return 0 + COLOR_BLACK; - break; case Blue: return 0 + COLOR_BLUE; - break; case Green: return 0 + COLOR_GREEN; - break; case Red: return 8 + COLOR_RED; - break; case Brown: return 0 + COLOR_RED; - break; case Magenta: return 0 + COLOR_MAGENTA; - break; case Orange: return 0 + COLOR_YELLOW; - break; case Yellow: return 8 + COLOR_YELLOW; - break; case LightGreen: return 8 + COLOR_GREEN; - break; case Cyan: return 0 + COLOR_CYAN; - break; case LightCyan: return 8 + COLOR_CYAN; - break; case LightBlue: return 8 + COLOR_BLUE; - break; case Pink: return 8 + COLOR_MAGENTA; - break; case Gray: return 8 + COLOR_BLACK; - break; case LightGray: return 0 + COLOR_WHITE; - break; default: return -1; - } -} +static const short Colors[100] = { + [Default] = -1, + [White] = 8 + COLOR_WHITE, + [Black] = 0 + COLOR_BLACK, + [Blue] = 0 + COLOR_BLUE, + [Green] = 0 + COLOR_GREEN, + [Red] = 8 + COLOR_RED, + [Brown] = 0 + COLOR_RED, + [Magenta] = 0 + COLOR_MAGENTA, + [Orange] = 0 + COLOR_YELLOW, + [Yellow] = 8 + COLOR_YELLOW, + [LightGreen] = 8 + COLOR_GREEN, + [Cyan] = 0 + COLOR_CYAN, + [LightCyan] = 8 + COLOR_CYAN, + [LightBlue] = 8 + COLOR_BLUE, + [Pink] = 8 + COLOR_MAGENTA, + [Gray] = 8 + COLOR_BLACK, + [LightGray] = 0 + COLOR_WHITE, + 52, 94, 100, 58, 22, 29, 23, 24, 17, 54, 53, 89, + 88, 130, 142, 64, 28, 35, 30, 25, 18, 91, 90, 125, + 124, 166, 184, 106, 34, 49, 37, 33, 19, 129, 127, 161, + 196, 208, 226, 154, 46, 86, 51, 75, 21, 171, 201, 198, + 203, 215, 227, 191, 83, 122, 87, 111, 63, 177, 207, 205, + 217, 223, 229, 193, 157, 158, 159, 153, 147, 183, 219, 212, + 16, 233, 235, 237, 239, 241, 244, 247, 250, 254, 231, +}; enum { B = '\2', C = '\3', O = '\17', R = '\26', I = '\35', U = '\37' }; @@ -342,8 +347,8 @@ static void statusAdd(const char *str) { styleParse(&style, &str, &len); wattr_set( status, - style.attr | colorAttr(mapColor(style.fg)), - colorPair(mapColor(style.fg), mapColor(style.bg)), + style.attr | colorAttr(Colors[style.fg]), + colorPair(Colors[style.fg], Colors[style.bg]), NULL ); waddnstr(status, str, len); @@ -471,8 +476,8 @@ static int wordWrap(WINDOW *win, const char *str) { wattr_set( win, - style.attr | colorAttr(mapColor(style.fg)), - colorPair(mapColor(style.fg), mapColor(style.bg)), + style.attr | colorAttr(Colors[style.fg]), + colorPair(Colors[style.fg], Colors[style.bg]), NULL ); waddnstr(win, str, len); @@ -555,7 +560,7 @@ static void bufferList(struct Buffer *buffer) { if (!tm) continue; char buf[sizeof("[00:00:00]")]; strftime(buf, sizeof(buf), "[%T]", tm); - vid_attr(colorAttr(mapColor(Gray)), colorPair(mapColor(Gray), -1), NULL); + vid_attr(colorAttr(Colors[Gray]), colorPair(Colors[Gray], -1), NULL); printf("%s\t", buf); size_t len; @@ -563,8 +568,8 @@ static void bufferList(struct Buffer *buffer) { while (*line) { styleParse(&style, &line, &len); vid_attr( - style.attr | colorAttr(mapColor(style.fg)), - colorPair(mapColor(style.fg), mapColor(style.bg)), + style.attr | colorAttr(Colors[style.fg]), + colorPair(Colors[style.fg], Colors[style.bg]), NULL ); if (len) printf("%.*s", (int)len, line); @@ -591,8 +596,8 @@ static void inputAdd(struct Style *style, const char *str) { if (str - code > 1) waddnstr(input, &code[1], str - &code[1]); wattr_set( input, - style->attr | colorAttr(mapColor(style->fg)), - colorPair(mapColor(style->fg), mapColor(style->bg)), + style->attr | colorAttr(Colors[style->fg]), + colorPair(Colors[style->fg], Colors[style->bg]), NULL ); waddnstr(input, str, len); @@ -636,8 +641,8 @@ static void inputUpdate(void) { wmove(input, 0, 0); wattr_set( input, - init.attr | colorAttr(mapColor(init.fg)), - colorPair(mapColor(init.fg), mapColor(init.bg)), + init.attr | colorAttr(Colors[init.fg]), + colorPair(Colors[init.fg], Colors[init.bg]), NULL ); waddstr(input, prefix); -- cgit 1.4.0 From b9a6d35b659e5eba69897d27cee0091821fbe897 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 05:33:31 -0500 Subject: Improve color fudging Prevent fudged colors from ever being pure black. Distribute fudged colors between normal and bold if COLORS is 8. Fudge colors before checking if it's a pre-allocated pair. --- ui.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index e36585a..3f730d2 100644 --- a/ui.c +++ b/ui.c @@ -133,13 +133,15 @@ static void colorInit(void) { } static attr_t colorAttr(short fg) { - return (fg >= COLORS && fg < 16 ? A_BOLD : A_NORMAL); + if (fg != COLOR_BLACK && fg % COLORS == COLOR_BLACK) return A_BOLD; + if (COLORS > 8) return A_NORMAL; + return (fg / COLORS & 1 ? A_BOLD : A_NORMAL); } static short colorPair(short fg, short bg) { - if (bg == -1 && fg < 16) return 1 + fg; fg %= COLORS; bg %= COLORS; + if (bg == -1 && fg < 16) return 1 + fg; for (short pair = 17; pair < colorPairs; ++pair) { short f, b; pair_content(pair, &f, &b); -- cgit 1.4.0 From 65603d5138b54efc53c04e756631ab8eeddee7fb Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 17:54:16 -0500 Subject: Show heat and other unread in title --- ui.c | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 3f730d2..9a070a6 100644 --- a/ui.c +++ b/ui.c @@ -359,38 +359,45 @@ static void statusAdd(const char *str) { } static void statusUpdate(void) { + int otherUnread = 0; + enum Heat otherHeat = Cold; wmove(status, 0, 0); + int num; const struct Window *window; for (num = 0, window = windows.head; window; ++num, window = window->next) { if (!window->heat && window != windows.active) continue; - int unread; + if (window != windows.active) { + otherUnread += window->unreadCount; + if (window->heat > otherHeat) otherHeat = window->heat; + } + int trunc; char buf[256]; snprintf( buf, sizeof(buf), "\3%d%s %d %s %n(\3%02d%d\3%d) ", idColors[window->id], (window == windows.active ? "\26" : ""), num, idNames[window->id], - &unread, (window->heat > Warm ? White : idColors[window->id]), + &trunc, (window->heat > Warm ? White : idColors[window->id]), window->unreadCount, idColors[window->id] ); - if (!window->mark || !window->unreadCount) buf[unread] = '\0'; + if (!window->mark || !window->unreadCount) buf[trunc] = '\0'; statusAdd(buf); } wclrtoeol(status); + if (!to_status_line) return; - int unread; - char buf[256]; - snprintf( - buf, sizeof(buf), "%s %s%n (%d)", - self.network, idNames[windows.active->id], - &unread, windows.active->unreadCount - ); - if (!windows.active->mark || !windows.active->unreadCount) { - buf[unread] = '\0'; - } + window = windows.active; putp(to_status_line); - putp(buf); + printf("%s %s", self.network, idNames[window->id]); + if (window->mark && window->unreadCount) { + printf( + " (%d%s)", window->unreadCount, (window->heat > Warm ? "!" : "") + ); + } + if (otherUnread) { + printf(" (+%d%s)", otherUnread, (otherHeat > Warm ? "!" : "")); + } putp(from_status_line); fflush(stdout); } -- cgit 1.4.0 From b59431bb15ec74f05119a7c710a1f6a21e702bad Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 19:40:13 -0500 Subject: Add -s to save and load buffers --- catgirl.1 | 27 +++++++++- chat.c | 17 +++++- chat.h | 4 ++ config.c | 8 ++- ui.c | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 220 insertions(+), 12 deletions(-) (limited to 'ui.c') diff --git a/catgirl.1 b/catgirl.1 index 15b387b..00f875b 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -20,6 +20,7 @@ .Op Fl n Ar nick .Op Fl p Ar port .Op Fl r Ar real +.Op Fl s Ar save .Op Fl u Ar user .Op Fl w Ar pass .Op Ar config ... @@ -123,6 +124,18 @@ Set realname to .Ar real . The default realname is the same as the nickname. . +.It Fl s Ar name , Cm save = Ar name +Load and save the contents of windows from +.Ar name +in +.Pa $XDG_DATA_DIRS/catgirl , +or an absolute or relative path if +.Ar name +starts with +.Ql / +or +.Ql \&. . +. .It Fl u Ar user , Cm user = Ar user Set username to .Ar user . @@ -324,7 +337,7 @@ The color numbers are as follows: .Sh FILES .Bl -tag -width Ds .It Pa $XDG_CONFIG_DIRS/catgirl -Configuration files are search for first in +Configuration files are searched for first in .Ev $XDG_CONFIG_HOME , usually .Pa ~/.config , @@ -334,6 +347,18 @@ usually .Pa /etc/xdg . .It Pa ~/.config/catgirl The most likely location of configuration files. +. +.It Pa $XDG_DATA_DIRS/catgirl +Save files are searched for first in +.Ev $XDG_DATA_HOME , +usually +.Pa ~/.local/share , +followed by the colon-separated list of paths +.Ev $XDG_DATA_DIRS , +usually +.Pa /usr/local/share:/usr/share . +.It Pa ~/.local/share/catgirl +The most likely location of save files. .El . .Sh EXAMPLES diff --git a/chat.c b/chat.c index c58fdc5..e8713bb 100644 --- a/chat.c +++ b/chat.c @@ -47,6 +47,15 @@ size_t idNext = Network + 1; struct Self self = { .color = Default }; +static const char *save; +static void exitSave(void) { + int error = uiSave(save); + if (error) { + warn("%s", save); + _exit(EX_IOERR); + } +} + uint32_t hashInit; int procPipe[2] = { -1, -1 }; @@ -84,7 +93,7 @@ int main(int argc, char *argv[]) { const char *user = NULL; const char *real = NULL; - const char *Opts = "!C:H:O:a:c:eh:j:k:n:p:r:u:vw:"; + const char *Opts = "!C:H:O:a:c:eh:j:k:n:p:r:s:u:vw:"; const struct option LongOpts[] = { { "insecure", no_argument, NULL, '!' }, { "copy", required_argument, NULL, 'C' }, @@ -99,6 +108,7 @@ int main(int argc, char *argv[]) { { "nick", required_argument, NULL, 'n' }, { "port", required_argument, NULL, 'p' }, { "real", required_argument, NULL, 'r' }, + { "save", required_argument, NULL, 's' }, { "user", required_argument, NULL, 'u' }, { "debug", no_argument, NULL, 'v' }, { "pass", required_argument, NULL, 'w' }, @@ -121,6 +131,7 @@ int main(int argc, char *argv[]) { break; case 'n': nick = optarg; break; case 'p': port = optarg; break; case 'r': real = optarg; + break; case 's': save = optarg; break; case 'u': user = optarg; break; case 'v': self.debug = true; break; case 'w': pass = optarg; @@ -154,6 +165,10 @@ int main(int argc, char *argv[]) { if (privFile) fclose(privFile); uiInit(); + if (save) { + uiLoad(save); + atexit(exitSave); + } uiShowID(Network); uiFormat(Network, Cold, NULL, "Traveling..."); uiDraw(); diff --git a/chat.h b/chat.h index 13319da..47a6163 100644 --- a/chat.h +++ b/chat.h @@ -26,6 +26,8 @@ #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) #define BIT(x) x##Bit, x = 1 << x##Bit, x##Bit_ = x##Bit +#define XDG_SUBDIR "catgirl" + typedef unsigned char byte; int procPipe[2]; @@ -144,6 +146,8 @@ void uiWrite(size_t id, enum Heat heat, const time_t *time, const char *str); void uiFormat( size_t id, enum Heat heat, const time_t *time, const char *format, ... ) __attribute__((format(printf, 4, 5))); +void uiLoad(const char *name); +int uiSave(const char *name); enum Edit { EditHead, diff --git a/config.c b/config.c index b3e42f9..3bf56c0 100644 --- a/config.c +++ b/config.c @@ -24,8 +24,6 @@ #include "chat.h" -#define CONFIG_DIR "catgirl" - FILE *configOpen(const char *path, const char *mode) { if (path[0] == '/' || path[0] == '.') goto local; @@ -35,10 +33,10 @@ FILE *configOpen(const char *path, const char *mode) { char buf[PATH_MAX]; if (configHome) { - snprintf(buf, sizeof(buf), "%s/" CONFIG_DIR "/%s", configHome, path); + snprintf(buf, sizeof(buf), "%s/" XDG_SUBDIR "/%s", configHome, path); } else { if (!home) goto local; - snprintf(buf, sizeof(buf), "%s/.config/" CONFIG_DIR "/%s", home, path); + snprintf(buf, sizeof(buf), "%s/.config/" XDG_SUBDIR "/%s", home, path); } FILE *file = fopen(buf, mode); if (file) return file; @@ -48,7 +46,7 @@ FILE *configOpen(const char *path, const char *mode) { while (*configDirs) { size_t len = strcspn(configDirs, ":"); snprintf( - buf, sizeof(buf), "%.*s/" CONFIG_DIR "/%s", + buf, sizeof(buf), "%.*s/" XDG_SUBDIR "/%s", (int)len, configDirs, path ); file = fopen(buf, mode); diff --git a/ui.c b/ui.c index 9a070a6..57ef322 100644 --- a/ui.c +++ b/ui.c @@ -20,11 +20,14 @@ #include #include #include +#include +#include #include #include #include #include #include +#include #include #include #include @@ -66,6 +69,13 @@ static void bufferPush(struct Buffer *buffer, time_t time, const char *line) { if (!buffer->lines[i]) err(EX_OSERR, "strdup"); } +static time_t bufferTime(const struct Buffer *buffer, size_t i) { + return buffer->times[(buffer->len + i) % BufferCap]; +} +static const char *bufferLine(const struct Buffer *buffer, size_t i) { + return buffer->lines[(buffer->len + i) % BufferCap]; +} + enum { WindowLines = BufferCap }; struct Window { size_t id; @@ -532,9 +542,8 @@ static void reflow(struct Window *window) { werase(window->pad); wmove(window->pad, WindowLines - 1, 0); window->unreadLines = 0; - struct Buffer *buffer = &window->buffer; for (size_t i = 0; i < BufferCap; ++i) { - char *line = buffer->lines[(buffer->len + i) % BufferCap]; + const char *line = bufferLine(&window->buffer, i); if (!line) continue; waddch(window->pad, '\n'); if (i >= (size_t)(BufferCap - window->unreadCount)) { @@ -557,12 +566,12 @@ static void resize(void) { statusUpdate(); } -static void bufferList(struct Buffer *buffer) { +static void bufferList(const struct Buffer *buffer) { uiHide(); waiting = true; for (size_t i = 0; i < BufferCap; ++i) { - time_t time = buffer->times[(buffer->len + i) % BufferCap]; - const char *line = buffer->lines[(buffer->len + i) % BufferCap]; + time_t time = bufferTime(buffer, i); + const char *line = bufferLine(buffer, i); if (!line) continue; struct tm *tm = localtime(&time); @@ -848,3 +857,160 @@ void uiRead(void) { } inputUpdate(); } + +static FILE *dataOpen(const char *path, const char *mode) { + if (path[0] == '/' || path[0] == '.') goto local; + + const char *home = getenv("HOME"); + const char *dataHome = getenv("XDG_DATA_HOME"); + const char *dataDirs = getenv("XDG_DATA_DIRS"); + + char homePath[PATH_MAX]; + if (dataHome) { + snprintf( + homePath, sizeof(homePath), + "%s/" XDG_SUBDIR "/%s", dataHome, path + ); + } else { + if (!home) goto local; + snprintf( + homePath, sizeof(homePath), + "%s/.local/share/" XDG_SUBDIR "/%s", home, path + ); + } + FILE *file = fopen(homePath, mode); + if (file) return file; + if (errno != ENOENT) { + warn("%s", homePath); + return NULL; + } + + char buf[PATH_MAX]; + if (!dataDirs) dataDirs = "/usr/local/share:/usr/share"; + while (*dataDirs) { + size_t len = strcspn(dataDirs, ":"); + snprintf( + buf, sizeof(buf), "%.*s/" XDG_SUBDIR "/%s", + (int)len, dataDirs, path + ); + file = fopen(buf, mode); + if (file) return file; + if (errno != ENOENT) { + warn("%s", buf); + return NULL; + } + dataDirs += len; + if (*dataDirs) dataDirs++; + } + + if (mode[0] != 'r') { + char *base = strrchr(homePath, '/'); + *base = '\0'; + int error = mkdir(homePath, S_IRWXU); + if (error && errno != EEXIST) { + warn("%s", homePath); + return NULL; + } + *base = '/'; + file = fopen(homePath, mode); + if (!file) warn("%s", homePath); + return file; + } + +local: + file = fopen(path, mode); + if (!file) warn("%s", path); + return file; +} + +static const size_t Signatures[] = { + 0x6C72696774616301, +}; + +static size_t signatureVersion(size_t signature) { + for (size_t i = 0; i < ARRAY_LEN(Signatures); ++i) { + if (signature == Signatures[i]) return i; + } + err(EX_DATAERR, "unknown file signature %zX", signature); +} + +static int writeSize(FILE *file, size_t value) { + return (fwrite(&value, sizeof(value), 1, file) ? 0 : -1); +} +static int writeTime(FILE *file, time_t time) { + return (fwrite(&time, sizeof(time), 1, file) ? 0 : -1); +} +static int writeString(FILE *file, const char *str) { + return (fwrite(str, strlen(str) + 1, 1, file) ? 0 : -1); +} + +int uiSave(const char *name) { + FILE *file = dataOpen(name, "w"); + if (!file) return -1; + + if (writeSize(file, Signatures[0])) return -1; + const struct Window *window; + for (window = windows.head; window; window = window->next) { + if (writeString(file, idNames[window->id])) return -1; + for (size_t i = 0; i < BufferCap; ++i) { + time_t time = bufferTime(&window->buffer, i); + const char *line = bufferLine(&window->buffer, i); + if (!line) continue; + if (writeTime(file, time)) return -1; + if (writeString(file, line)) return -1; + } + if (writeTime(file, 0)) return -1; + } + return fclose(file); +} + +static size_t readSize(FILE *file) { + size_t value; + fread(&value, sizeof(value), 1, file); + if (ferror(file)) err(EX_IOERR, "fread"); + if (feof(file)) errx(EX_DATAERR, "unexpected eof"); + return value; +} +static time_t readTime(FILE *file) { + time_t time; + fread(&time, sizeof(time), 1, file); + if (ferror(file)) err(EX_IOERR, "fread"); + if (feof(file)) errx(EX_DATAERR, "unexpected eof"); + return time; +} +static ssize_t readString(FILE *file, char **buf, size_t *cap) { + ssize_t len = getdelim(buf, cap, '\0', file); + if (len < 0 && !feof(file)) err(EX_IOERR, "getdelim"); + return len; +} + +void uiLoad(const char *name) { + FILE *file = dataOpen(name, "r"); + if (!file) { + if (errno != ENOENT) exit(EX_NOINPUT); + file = dataOpen(name, "w"); + if (!file) exit(EX_CANTCREAT); + fclose(file); + return; + } + + size_t signature = readSize(file); + signatureVersion(signature); + + char *buf = NULL; + size_t cap = 0; + while (0 < readString(file, &buf, &cap)) { + struct Window *window = windowFor(idFor(buf)); + for (;;) { + time_t time = readTime(file); + if (!time) break; + readString(file, &buf, &cap); + bufferPush(&window->buffer, time, buf); + } + reflow(window); + // TODO: Place some marker of end of save. + } + + free(buf); + fclose(file); +} -- cgit 1.4.0 From e6c18403e22bde5a785b7172903f640d03cb8a35 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 19:44:35 -0500 Subject: Leave a blank line after loaded buffer --- ui.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 57ef322..ecf7e60 100644 --- a/ui.c +++ b/ui.c @@ -1008,7 +1008,7 @@ void uiLoad(const char *name) { bufferPush(&window->buffer, time, buf); } reflow(window); - // TODO: Place some marker of end of save. + waddch(window->pad, '\n'); } free(buf); -- cgit 1.4.0 From 99480a42e56e70707822934ffeb56f0454afc127 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 19:57:10 -0500 Subject: Factor out XDG base directory code And add warnings to configOpen, since that's the only way to be accurate if a weird error occurs. --- Makefile | 1 + chat.c | 4 +- chat.h | 2 + config.c | 41 +------------------ ui.c | 67 -------------------------------- xdg.c | 134 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 140 insertions(+), 109 deletions(-) create mode 100644 xdg.c (limited to 'ui.c') diff --git a/Makefile b/Makefile index 89af9b3..b1ffede 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,7 @@ OBJS += handle.o OBJS += irc.o OBJS += ui.o OBJS += url.o +OBJS += xdg.o dev: tags all diff --git a/chat.c b/chat.c index e8713bb..1ae7090 100644 --- a/chat.c +++ b/chat.c @@ -154,11 +154,11 @@ int main(int argc, char *argv[]) { FILE *privFile = NULL; if (cert) { certFile = configOpen(cert, "r"); - if (!certFile) err(EX_NOINPUT, "%s", cert); + if (!certFile) return EX_NOINPUT; } if (priv) { privFile = configOpen(priv, "r"); - if (!privFile) err(EX_NOINPUT, "%s", priv); + if (!privFile) return EX_NOINPUT; } ircConfig(insecure, certFile, privFile); if (certFile) fclose(certFile); diff --git a/chat.h b/chat.h index 47a6163..03a0a50 100644 --- a/chat.h +++ b/chat.h @@ -189,6 +189,8 @@ void urlOpenMatch(size_t id, const char *str); void urlCopyMatch(size_t id, const char *str); FILE *configOpen(const char *path, const char *mode); +FILE *dataOpen(const char *path, const char *mode); + int getopt_config( int argc, char *const *argv, const char *optstring, const struct option *longopts, int *longindex diff --git a/config.c b/config.c index 3bf56c0..3a87948 100644 --- a/config.c +++ b/config.c @@ -24,42 +24,6 @@ #include "chat.h" -FILE *configOpen(const char *path, const char *mode) { - if (path[0] == '/' || path[0] == '.') goto local; - - const char *home = getenv("HOME"); - const char *configHome = getenv("XDG_CONFIG_HOME"); - const char *configDirs = getenv("XDG_CONFIG_DIRS"); - - char buf[PATH_MAX]; - if (configHome) { - snprintf(buf, sizeof(buf), "%s/" XDG_SUBDIR "/%s", configHome, path); - } else { - if (!home) goto local; - snprintf(buf, sizeof(buf), "%s/.config/" XDG_SUBDIR "/%s", home, path); - } - FILE *file = fopen(buf, mode); - if (file) return file; - if (errno != ENOENT) return NULL; - - if (!configDirs) configDirs = "/etc/xdg"; - while (*configDirs) { - size_t len = strcspn(configDirs, ":"); - snprintf( - buf, sizeof(buf), "%.*s/" XDG_SUBDIR "/%s", - (int)len, configDirs, path - ); - file = fopen(buf, mode); - if (file) return file; - if (errno != ENOENT) return NULL; - configDirs += len; - if (*configDirs) configDirs++; - } - -local: - return fopen(path, mode); -} - #define WS "\t " static const char *path; @@ -92,10 +56,7 @@ int getopt_config( num = 0; path = argv[optind++]; file = configOpen(path, "r"); - if (!file) { - warn("%s", path); - return clean('?'); - } + if (!file) return clean('?'); } else { return clean(-1); } diff --git a/ui.c b/ui.c index ecf7e60..9601aaa 100644 --- a/ui.c +++ b/ui.c @@ -21,13 +21,11 @@ #include #include #include -#include #include #include #include #include #include -#include #include #include #include @@ -858,71 +856,6 @@ void uiRead(void) { inputUpdate(); } -static FILE *dataOpen(const char *path, const char *mode) { - if (path[0] == '/' || path[0] == '.') goto local; - - const char *home = getenv("HOME"); - const char *dataHome = getenv("XDG_DATA_HOME"); - const char *dataDirs = getenv("XDG_DATA_DIRS"); - - char homePath[PATH_MAX]; - if (dataHome) { - snprintf( - homePath, sizeof(homePath), - "%s/" XDG_SUBDIR "/%s", dataHome, path - ); - } else { - if (!home) goto local; - snprintf( - homePath, sizeof(homePath), - "%s/.local/share/" XDG_SUBDIR "/%s", home, path - ); - } - FILE *file = fopen(homePath, mode); - if (file) return file; - if (errno != ENOENT) { - warn("%s", homePath); - return NULL; - } - - char buf[PATH_MAX]; - if (!dataDirs) dataDirs = "/usr/local/share:/usr/share"; - while (*dataDirs) { - size_t len = strcspn(dataDirs, ":"); - snprintf( - buf, sizeof(buf), "%.*s/" XDG_SUBDIR "/%s", - (int)len, dataDirs, path - ); - file = fopen(buf, mode); - if (file) return file; - if (errno != ENOENT) { - warn("%s", buf); - return NULL; - } - dataDirs += len; - if (*dataDirs) dataDirs++; - } - - if (mode[0] != 'r') { - char *base = strrchr(homePath, '/'); - *base = '\0'; - int error = mkdir(homePath, S_IRWXU); - if (error && errno != EEXIST) { - warn("%s", homePath); - return NULL; - } - *base = '/'; - file = fopen(homePath, mode); - if (!file) warn("%s", homePath); - return file; - } - -local: - file = fopen(path, mode); - if (!file) warn("%s", path); - return file; -} - static const size_t Signatures[] = { 0x6C72696774616301, }; diff --git a/xdg.c b/xdg.c new file mode 100644 index 0000000..6e33210 --- /dev/null +++ b/xdg.c @@ -0,0 +1,134 @@ +/* Copyright (C) 2019, 2020 C. McEnroe + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "chat.h" + +FILE *configOpen(const char *path, const char *mode) { + if (path[0] == '/' || path[0] == '.') goto local; + + const char *home = getenv("HOME"); + const char *configHome = getenv("XDG_CONFIG_HOME"); + const char *configDirs = getenv("XDG_CONFIG_DIRS"); + + char buf[PATH_MAX]; + if (configHome) { + snprintf(buf, sizeof(buf), "%s/" XDG_SUBDIR "/%s", configHome, path); + } else { + if (!home) goto local; + snprintf(buf, sizeof(buf), "%s/.config/" XDG_SUBDIR "/%s", home, path); + } + FILE *file = fopen(buf, mode); + if (file) return file; + if (errno != ENOENT) { + warn("%s", buf); + return NULL; + } + + if (!configDirs) configDirs = "/etc/xdg"; + while (*configDirs) { + size_t len = strcspn(configDirs, ":"); + snprintf( + buf, sizeof(buf), "%.*s/" XDG_SUBDIR "/%s", + (int)len, configDirs, path + ); + file = fopen(buf, mode); + if (file) return file; + if (errno != ENOENT) { + warn("%s", buf); + return NULL; + } + configDirs += len; + if (*configDirs) configDirs++; + } + +local: + file = fopen(path, mode); + if (!file) warn("%s", path); + return file; +} + +FILE *dataOpen(const char *path, const char *mode) { + if (path[0] == '/' || path[0] == '.') goto local; + + const char *home = getenv("HOME"); + const char *dataHome = getenv("XDG_DATA_HOME"); + const char *dataDirs = getenv("XDG_DATA_DIRS"); + + char homePath[PATH_MAX]; + if (dataHome) { + snprintf( + homePath, sizeof(homePath), + "%s/" XDG_SUBDIR "/%s", dataHome, path + ); + } else { + if (!home) goto local; + snprintf( + homePath, sizeof(homePath), + "%s/.local/share/" XDG_SUBDIR "/%s", home, path + ); + } + FILE *file = fopen(homePath, mode); + if (file) return file; + if (errno != ENOENT) { + warn("%s", homePath); + return NULL; + } + + char buf[PATH_MAX]; + if (!dataDirs) dataDirs = "/usr/local/share:/usr/share"; + while (*dataDirs) { + size_t len = strcspn(dataDirs, ":"); + snprintf( + buf, sizeof(buf), "%.*s/" XDG_SUBDIR "/%s", + (int)len, dataDirs, path + ); + file = fopen(buf, mode); + if (file) return file; + if (errno != ENOENT) { + warn("%s", buf); + return NULL; + } + dataDirs += len; + if (*dataDirs) dataDirs++; + } + + if (mode[0] != 'r') { + char *base = strrchr(homePath, '/'); + *base = '\0'; + int error = mkdir(homePath, S_IRWXU); + if (error && errno != EEXIST) { + warn("%s", homePath); + return NULL; + } + *base = '/'; + file = fopen(homePath, mode); + if (!file) warn("%s", homePath); + return file; + } + +local: + file = fopen(path, mode); + if (!file) warn("%s", path); + return file; +} -- cgit 1.4.0 From 3a156540b8d134b05d7c318ac047a0c690cdc950 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 20:29:19 -0500 Subject: Add C-o as alias of M-/ M-/ is from weechat. C-o is like in vim. --- catgirl.1 | 2 ++ ui.c | 1 + 2 files changed, 3 insertions(+) (limited to 'ui.c') diff --git a/catgirl.1 b/catgirl.1 index 00f875b..7c51b08 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -272,6 +272,8 @@ Complete nick, channel or command. Redraw the UI. .It Ic C-n Switch to next window. +.It Ic C-o +Switch to previously selected window. .It Ic C-p Switch to previous window. .It Ic M-/ diff --git a/ui.c b/ui.c index 9601aaa..5f912b7 100644 --- a/ui.c +++ b/ui.c @@ -805,6 +805,7 @@ static void keyCtrl(wchar_t ch) { break; case L'K': edit(id, EditDeleteTail, 0); break; case L'L': clearok(curscr, true); break; case L'N': windowShow(windows.active->next); + break; case L'O': windowShow(windows.other); break; case L'P': windowShow(windows.active->prev); break; case L'U': edit(id, EditDeleteHead, 0); break; case L'W': edit(id, EditDeletePrevWord, 0); -- cgit 1.4.0 From 2c2839e6c18aabbb76fa624767e248ed86d63326 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 20:44:37 -0500 Subject: Replace alignment tabs with spaces in bufferList --- ui.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 5f912b7..1e7eb26 100644 --- a/ui.c +++ b/ui.c @@ -38,6 +38,7 @@ // Annoying stuff from : #undef lines +#undef tab #ifndef A_ITALIC #define A_ITALIC A_NORMAL @@ -577,12 +578,20 @@ static void bufferList(const struct Buffer *buffer) { char buf[sizeof("[00:00:00]")]; strftime(buf, sizeof(buf), "[%T]", tm); vid_attr(colorAttr(Colors[Gray]), colorPair(Colors[Gray], -1), NULL); - printf("%s\t", buf); + printf("%s ", buf); size_t len; + bool align = false; struct Style style = Reset; while (*line) { + if (*line == '\t') { + printf("%c", (align ? '\t' : ' ')); + align = true; + line++; + } styleParse(&style, &line, &len); + size_t tab = strcspn(line, "\t"); + if (tab < len) len = tab; vid_attr( style.attr | colorAttr(Colors[style.fg]), colorPair(Colors[style.fg], Colors[style.bg]), -- cgit 1.4.0 From 47a0bf7fc2b9a462a6b1d6e76f0b9a137cbef791 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 21:09:32 -0500 Subject: Manually raise SIGINT from C-c This allows it to still work, but makes C-z C-c insert the color code rather than exit, and in the future, will allow pasting in text with color codes. --- ui.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 1e7eb26..903c4af 100644 --- a/ui.c +++ b/ui.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -184,12 +185,13 @@ void uiHide(void) { endwin(); } -// Gain use of C-q, C-s, C-z, C-y, C-o. +// Gain use of C-q, C-s, C-c, C-z, C-y, C-o. static void acquireKeys(void) { struct termios term; int error = tcgetattr(STDOUT_FILENO, &term); if (error) err(EX_OSERR, "tcgetattr"); term.c_iflag &= ~IXON; + term.c_cc[VINTR] = _POSIX_VDISABLE; term.c_cc[VSUSP] = _POSIX_VDISABLE; term.c_cc[VDSUSP] = _POSIX_VDISABLE; term.c_cc[VDISCARD] = _POSIX_VDISABLE; @@ -805,6 +807,7 @@ static void keyCtrl(wchar_t ch) { break; case L'?': edit(id, EditDeletePrev, 0); break; case L'A': edit(id, EditHead, 0); break; case L'B': edit(id, EditPrev, 0); + break; case L'C': raise(SIGINT); break; case L'D': edit(id, EditDeleteNext, 0); break; case L'E': edit(id, EditTail, 0); break; case L'F': edit(id, EditNext, 0); -- cgit 1.4.0 From 90eff04eda86e0a75b0e81960e2166193c366be7 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 21:24:30 -0500 Subject: Only write out title on uiDraw --- ui.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 903c4af..297fd7b 100644 --- a/ui.c +++ b/ui.c @@ -268,6 +268,8 @@ void uiInit(void) { uiShow(); } +static char title[256]; + void uiDraw(void) { if (hidden) return; wnoutrefresh(status); @@ -291,6 +293,12 @@ void uiDraw(void) { BOTTOM, RIGHT ); doupdate(); + + if (!to_status_line) return; + putp(to_status_line); + putp(title); + putp(from_status_line); + fflush(stdout); } struct Style { @@ -396,21 +404,21 @@ static void statusUpdate(void) { statusAdd(buf); } wclrtoeol(status); - if (!to_status_line) return; window = windows.active; - putp(to_status_line); - printf("%s %s", self.network, idNames[window->id]); + snprintf(title, sizeof(title), "%s %s", self.network, idNames[window->id]); if (window->mark && window->unreadCount) { - printf( + snprintf( + &title[strlen(title)], sizeof(title) - strlen(title), " (%d%s)", window->unreadCount, (window->heat > Warm ? "!" : "") ); } if (otherUnread) { - printf(" (+%d%s)", otherUnread, (otherHeat > Warm ? "!" : "")); + snprintf( + &title[strlen(title)], sizeof(title) - strlen(title), + " (+%d%s)", otherUnread, (otherHeat > Warm ? "!" : "") + ); } - putp(from_status_line); - fflush(stdout); } static void mark(struct Window *window) { -- cgit 1.4.0 From 66fe89b84b8acc9c34ab03eeb17ca7b7f6eff1e5 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 21:34:23 -0500 Subject: Only write out title if it has changed --- ui.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index 297fd7b..dcfb607 100644 --- a/ui.c +++ b/ui.c @@ -293,8 +293,12 @@ void uiDraw(void) { BOTTOM, RIGHT ); doupdate(); - if (!to_status_line) return; + + static char prevTitle[sizeof(title)]; + if (!strcmp(title, prevTitle)) return; + strcpy(prevTitle, title); + putp(to_status_line); putp(title); putp(from_status_line); -- cgit 1.4.0 From bf86a4749f93de47d45309028d97ea3a0b7f0c7a Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 22:05:02 -0500 Subject: Invalidate title on uiShow --- ui.c | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) (limited to 'ui.c') diff --git a/ui.c b/ui.c index dcfb607..6c9606d 100644 --- a/ui.c +++ b/ui.c @@ -168,23 +168,6 @@ static const char *ExitFocusMode = "\33[?1004l"; static const char *EnterPasteMode = "\33[?2004h"; static const char *ExitPasteMode = "\33[?2004l"; -static bool hidden; -static bool waiting; - -void uiShow(void) { - putp(EnterFocusMode); - putp(EnterPasteMode); - fflush(stdout); - hidden = false; -} - -void uiHide(void) { - hidden = true; - putp(ExitFocusMode); - putp(ExitPasteMode); - endwin(); -} - // Gain use of C-q, C-s, C-c, C-z, C-y, C-o. static void acquireKeys(void) { struct termios term; @@ -268,7 +251,11 @@ void uiInit(void) { uiShow(); } +static bool hidden; +static bool waiting; + static char title[256]; +static char prevTitle[sizeof(title)]; void uiDraw(void) { if (hidden) return; @@ -293,18 +280,31 @@ void uiDraw(void) { BOTTOM, RIGHT ); doupdate(); - if (!to_status_line) return; - static char prevTitle[sizeof(title)]; + if (!to_status_line) return; if (!strcmp(title, prevTitle)) return; strcpy(prevTitle, title); - putp(to_status_line); putp(title); putp(from_status_line); fflush(stdout); } +void uiShow(void) { + prevTitle[0] = '\0'; + putp(EnterFocusMode); + putp(EnterPasteMode); + fflush(stdout); + hidden = false; +} + +void uiHide(void) { + hidden = true; + putp(ExitFocusMode); + putp(ExitPasteMode); + endwin(); +} + struct Style { attr_t attr; enum Color fg, bg; -- cgit 1.4.0