From 397b4ce6bd9064aa36f8b9c264e4bbc226822d6f Mon Sep 17 00:00:00 2001 From: June McEnroe Date: Sat, 12 Feb 2022 13:26:38 -0500 Subject: Prompt for empty server or SASL passwords --- chat.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'chat.h') diff --git a/chat.h b/chat.h index 19d2b18..753d1a3 100644 --- a/chat.h +++ b/chat.h @@ -193,7 +193,8 @@ extern struct Self { bool restricted; size_t pos; enum Cap caps; - char *plain; + char *plainUser; + char *plainPass; char *mode; char *join; char *nick; -- cgit 1.4.1 From 1a2477ef7a34cc24c7bc18d7b6326643ce0995a2 Mon Sep 17 00:00:00 2001 From: June McEnroe Date: Fri, 18 Feb 2022 23:46:06 -0500 Subject: Implement new line editing "library" Losing tab complete and text macros, for now. This new implementation works on an instance of a struct and does not interact with the rest of catgirl, making it possible to copy into another project. Unlike existing line editing libraries, this one is entirely abstract and can be rendered externally. My goal with this library is to be able to implement vi mode. Since it operates on struct instances rather than globals, it might also be possible to give catgirl separate line editing buffers for each window, which would be a nice UX improvement. --- Makefile | 2 +- chat.c | 1 - chat.h | 25 ---- edit.c | 452 +++++++++++++++++++++++++++++++-------------------------------- edit.h | 91 +++++++++++++ ui.c | 88 +++++++------ 6 files changed, 358 insertions(+), 301 deletions(-) create mode 100644 edit.h (limited to 'chat.h') diff --git a/Makefile b/Makefile index 83ead95..9e6392b 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ all: catgirl catgirl: ${OBJS} ${CC} ${LDFLAGS} ${OBJS} ${LDLIBS} -o $@ -${OBJS}: chat.h +${OBJS}: chat.h edit.h tags: *.[ch] ctags -w *.[ch] diff --git a/chat.c b/chat.c index 4898411..454ae31 100644 --- a/chat.c +++ b/chat.c @@ -371,7 +371,6 @@ int main(int argc, char *argv[]) { set(&network.name, host); set(&self.nick, "*"); - editCompleteAdd(); commandCompleteAdd(); ircConfig(insecure, trust, cert, priv); diff --git a/chat.h b/chat.h index 753d1a3..8a9a48f 100644 --- a/chat.h +++ b/chat.h @@ -341,31 +341,6 @@ int bufferReflow( struct Buffer *buffer, int cols, enum Heat thresh, size_t tail ); -enum Edit { - EditHead, - EditTail, - EditPrev, - EditNext, - EditPrevWord, - EditNextWord, - EditDeleteHead, - EditDeleteTail, - EditDeletePrev, - EditDeleteNext, - EditDeletePrevWord, - EditDeleteNextWord, - EditPaste, - EditTranspose, - EditCollapse, - EditInsert, - EditComplete, - EditExpand, - EditEnter, -}; -void edit(uint id, enum Edit op, wchar_t ch); -char *editBuffer(size_t *pos); -void editCompleteAdd(void); - const char *complete(uint id, const char *prefix); const char *completeSubstr(uint id, const char *substr); void completeAccept(void); diff --git a/edit.c b/edit.c index 66b5302..21061d7 100644 --- a/edit.c +++ b/edit.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 C. McEnroe +/* Copyright (C) 2020, 2022 June 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 @@ -13,8 +13,6 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . * - * Additional permission under GNU GPL version 3 section 7: - * * If you modify this Program, or any covered work, by linking or * combining it with OpenSSL (or a modified version of that library), * containing parts covered by the terms of the OpenSSL License and the @@ -25,282 +23,272 @@ * covered work. */ -#include +#include #include #include -#include #include #include #include -#include "chat.h" +#include "edit.h" + +static bool isword(wchar_t ch) { + return !iswspace(ch) && !iswpunct(ch); +} -enum { Cap = 1024 }; -static wchar_t buf[Cap]; -static size_t len; -static size_t pos; +void editFree(struct Edit *e) { + free(e->buf); + free(e->cut.buf); + free(e->mbs.buf); + e->pos = e->len = e->cap = 0; + e->cut.len = 0; + e->mbs.pos = e->mbs.len = 0; +} -char *editBuffer(size_t *mbsPos) { - static char mbs[MB_LEN_MAX * Cap]; +char *editString(struct Edit *e) { + size_t cap = e->len * MB_CUR_MAX + 1; + char *buf = realloc(e->mbs.buf, cap); + if (!buf) return NULL; + e->mbs.buf = buf; - const wchar_t *ptr = buf; - size_t mbsLen = wcsnrtombs(mbs, &ptr, pos, sizeof(mbs) - 1, NULL); - assert(mbsLen != (size_t)-1); - if (mbsPos) *mbsPos = mbsLen; + const wchar_t *ptr = e->buf; + e->mbs.len = wcsnrtombs(e->mbs.buf, &ptr, e->pos, cap-1, NULL); + if (e->mbs.len == (size_t)-1) return NULL; + e->mbs.pos = e->mbs.len; - ptr = &buf[pos]; + ptr = &e->buf[e->pos]; size_t n = wcsnrtombs( - &mbs[mbsLen], &ptr, len - pos, sizeof(mbs) - mbsLen - 1, NULL + &e->mbs.buf[e->mbs.len], &ptr, e->len - e->pos, + cap-1 - e->mbs.len, NULL ); - assert(n != (size_t)-1); - mbsLen += n; - - mbs[mbsLen] = '\0'; - return mbs; -} - -static struct { - wchar_t buf[Cap]; - size_t len; -} cut; + if (n == (size_t)-1) return NULL; + e->mbs.len += n; -static bool reserve(size_t index, size_t count) { - if (len + count > Cap) return false; - wmemmove(&buf[index + count], &buf[index], len - index); - len += count; - return true; + e->mbs.buf[e->mbs.len] = '\0'; + return e->mbs.buf; } -static void delete(bool copy, size_t index, size_t count) { - if (index + count > len) return; - if (copy) { - wmemcpy(cut.buf, &buf[index], count); - cut.len = count; +int editReserve(struct Edit *e, size_t index, size_t count) { + if (index > e->len) { + errno = EINVAL; + return -1; } - wmemmove(&buf[index], &buf[index + count], len - index - count); - len -= count; -} - -static const struct { - const wchar_t *name; - const wchar_t *string; -} Macros[] = { - { L"\\banhammer", L"▬▬▬▬▬▬▬▋ Ò╭╮Ó" }, - { L"\\bear", L"ʕっ•ᴥ•ʔっ" }, - { L"\\blush", L"(˶′◡‵˶)" }, - { L"\\com", L"\0038,4\2 ☭ " }, - { L"\\cool", L"(⌐■_■)" }, - { L"\\flip", L"(╯°□°)╯︵ ┻━┻" }, - { L"\\gary", L"ᕕ( ᐛ )ᕗ" }, - { L"\\hug", L"(っ・∀・)っ" }, - { L"\\lenny", L"( ͡° ͜ʖ ͡°)" }, - { L"\\look", L"ಠ_ಠ" }, - { L"\\shrug", L"¯\\_(ツ)_/¯" }, - { L"\\unflip", L"┬─┬ノ(º_ºノ)" }, - { L"\\wave", L"ヾ(^∇^)" }, -}; - -void editCompleteAdd(void) { - char mbs[256]; - for (size_t i = 0; i < ARRAY_LEN(Macros); ++i) { - size_t n = wcstombs(mbs, Macros[i].name, sizeof(mbs)); - assert(n != (size_t)-1); - completeAdd(None, mbs, Default); + if (e->len + count > e->cap) { + size_t cap = (e->cap ? e->cap * 2 : 256); + wchar_t *buf = realloc(e->buf, sizeof(*buf) * cap); + if (!buf) return -1; + e->buf = buf; + e->cap = cap; } + wmemmove(&e->buf[index + count], &e->buf[index], e->len - index); + e->len += count; + return 0; } -static void macroExpand(void) { - size_t macro = pos; - while (macro && buf[macro] != L'\\') macro--; - if (macro == pos) return; - for (size_t i = 0; i < ARRAY_LEN(Macros); ++i) { - if (wcsncmp(Macros[i].name, &buf[macro], pos - macro)) continue; - if (wcstombs(NULL, Macros[i].string, 0) == (size_t)-1) continue; - delete(false, macro, pos - macro); - pos = macro; - size_t expand = wcslen(Macros[i].string); - if (reserve(macro, expand)) { - wcsncpy(&buf[macro], Macros[i].string, expand); - pos += expand; - } +int editCopy(struct Edit *e, size_t index, size_t count) { + if (index + count > e->len) { + errno = EINVAL; + return -1; } + wchar_t *buf = realloc(e->cut.buf, sizeof(*buf) * count); + if (!buf) return -1; + e->cut.buf = buf; + wmemcpy(e->cut.buf, &e->buf[index], count); + e->cut.len = count; + return 0; } -static struct { - size_t pos; - size_t pre; - size_t len; - bool suffix; -} tab; - -static void tabComplete(uint id) { - if (!tab.len) { - tab.pos = pos; - while (tab.pos && !iswspace(buf[tab.pos - 1])) tab.pos--; - if (tab.pos == pos) return; - tab.pre = pos - tab.pos; - tab.len = tab.pre; - tab.suffix = true; - } - - char mbs[MB_LEN_MAX * Cap]; - const wchar_t *ptr = &buf[tab.pos]; - size_t n = wcsnrtombs(mbs, &ptr, tab.pre, sizeof(mbs) - 1, NULL); - assert(n != (size_t)-1); - mbs[n] = '\0'; - - const char *comp = complete(id, mbs); - if (!comp) { - comp = complete(id, mbs); - tab.suffix ^= true; - } - if (!comp) { - tab.len = 0; - return; - } - - wchar_t wcs[Cap]; - n = mbstowcs(wcs, comp, Cap); - assert(n != (size_t)-1); - if (tab.pos + n + 2 > Cap) { - completeReject(); - tab.len = 0; - return; - } - - bool colon = (tab.len >= 2 && buf[tab.pos + tab.len - 2] == L':'); - - delete(false, tab.pos, tab.len); - tab.len = n; - if (wcs[0] == L'\\' || wcschr(wcs, L' ')) { - reserve(tab.pos, tab.len); - } else if (wcs[0] != L'/' && tab.suffix && (!tab.pos || colon)) { - tab.len += 2; - reserve(tab.pos, tab.len); - buf[tab.pos + n + 0] = L':'; - buf[tab.pos + n + 1] = L' '; - } else if (tab.suffix && tab.pos >= 2 && buf[tab.pos - 2] == L':') { - tab.len += 2; - reserve(tab.pos, tab.len); - buf[tab.pos - 2] = L','; - buf[tab.pos + n + 0] = L':'; - buf[tab.pos + n + 1] = L' '; - } else { - tab.len++; - reserve(tab.pos, tab.len); - if (!tab.suffix && tab.pos >= 2 && buf[tab.pos - 2] == L',') { - buf[tab.pos - 2] = L':'; - } - buf[tab.pos + n] = L' '; +int editDelete(struct Edit *e, bool cut, size_t index, size_t count) { + if (index + count > e->len) { + errno = EINVAL; + return -1; } - wmemcpy(&buf[tab.pos], wcs, n); - pos = tab.pos + tab.len; -} - -static void tabAccept(void) { - completeAccept(); - tab.len = 0; -} - -static void tabReject(void) { - completeReject(); - tab.len = 0; -} - -static bool isword(wchar_t ch) { - return !iswspace(ch) && !iswpunct(ch); + if (cut && editCopy(e, index, count) < 0) return -1; + wmemmove(&e->buf[index], &e->buf[index + count], e->len - index - count); + e->len -= count; + if (e->pos > e->len) e->pos = e->len; + return 0; } -void edit(uint id, enum Edit op, wchar_t ch) { - size_t init = pos; - switch (op) { - break; case EditHead: pos = 0; - break; case EditTail: pos = len; - break; case EditPrev: if (pos) pos--; - break; case EditNext: if (pos < len) pos++; +int editFn(struct Edit *e, enum EditFn fn) { + int ret = 0; + switch (fn) { + break; case EditHead: e->pos = 0; + break; case EditTail: e->pos = e->len; + break; case EditPrev: if (e->pos) e->pos--; + break; case EditNext: if (e->pos < e->len) e->pos++; break; case EditPrevWord: { - while (pos && !isword(buf[pos - 1])) pos--; - while (pos && isword(buf[pos - 1])) pos--; + while (e->pos && !isword(e->buf[e->pos-1])) e->pos--; + while (e->pos && isword(e->buf[e->pos-1])) e->pos--; } break; case EditNextWord: { - while (pos < len && isword(buf[pos])) pos++; - while (pos < len && !isword(buf[pos])) pos++; + while (e->pos < e->len && isword(e->buf[e->pos])) e->pos++; + while (e->pos < e->len && !isword(e->buf[e->pos])) e->pos++; } - break; case EditDeleteHead: delete(true, 0, pos); pos = 0; - break; case EditDeleteTail: delete(true, pos, len - pos); - break; case EditDeletePrev: if (pos) delete(false, --pos, 1); - break; case EditDeleteNext: delete(false, pos, 1); + break; case EditDeleteHead: { + ret = editDelete(e, true, 0, e->pos); + e->pos = 0; + } + break; case EditDeleteTail: { + ret = editDelete(e, true, e->pos, e->len - e->pos); + } + break; case EditDeletePrev: { + if (e->pos) editDelete(e, false, --e->pos, 1); + } + break; case EditDeleteNext: { + editDelete(e, false, e->pos, 1); + } break; case EditDeletePrevWord: { - if (!pos) break; - size_t word = pos; - while (word && !isword(buf[word - 1])) word--; - while (word && isword(buf[word - 1])) word--; - delete(true, word, pos - word); - pos = word; + if (!e->pos) break; + size_t word = e->pos; + while (word && !isword(e->buf[word-1])) word--; + while (word && isword(e->buf[word-1])) word--; + ret = editDelete(e, true, word, e->pos - word); + e->pos = word; } break; case EditDeleteNextWord: { - if (pos == len) break; - size_t word = pos; - while (word < len && !isword(buf[word])) word++; - while (word < len && isword(buf[word])) word++; - delete(true, pos, word - pos); + if (e->pos == e->len) break; + size_t word = e->pos; + while (word < e->len && !isword(e->buf[word])) word++; + while (word < e->len && isword(e->buf[word])) word++; + ret = editDelete(e, true, e->pos, word - e->pos); } + break; case EditPaste: { - if (reserve(pos, cut.len)) { - wmemcpy(&buf[pos], cut.buf, cut.len); - pos += cut.len; + ret = editReserve(e, e->pos, e->cut.len); + if (ret == 0) { + wmemcpy(&e->buf[e->pos], e->cut.buf, e->cut.len); + e->pos += e->cut.len; } } - break; case EditTranspose: { - if (!pos || len < 2) break; - if (pos == len) pos--; - wchar_t t = buf[pos - 1]; - buf[pos - 1] = buf[pos]; - buf[pos++] = t; + if (e->len < 2) break; + if (!e->pos) e->pos++; + if (e->pos == e->len) e->pos--; + wchar_t x = e->buf[e->pos-1]; + e->buf[e->pos-1] = e->buf[e->pos]; + e->buf[e->pos++] = x; } break; case EditCollapse: { size_t ws; - for (pos = 0; pos < len;) { - for (; pos < len && !iswspace(buf[pos]); ++pos); - for (ws = pos; ws < len && iswspace(buf[ws]); ++ws); - if (pos && ws < len) { - delete(false, pos, ws - pos - 1); - buf[pos++] = L' '; + for (e->pos = 0; e->pos < e->len;) { + for (; e->pos < e->len && !iswspace(e->buf[e->pos]); ++e->pos); + for (ws = e->pos; ws < e->len && iswspace(e->buf[ws]); ++ws); + if (e->pos && ws < e->len) { + editDelete(e, false, e->pos, ws - e->pos - 1); + e->buf[e->pos++] = L' '; } else { - delete(false, pos, ws - pos); + editDelete(e, false, e->pos, ws - e->pos); } } } - break; case EditInsert: { - char mb[MB_LEN_MAX]; - if (wctomb(mb, ch) < 0) return; - if (reserve(pos, 1)) { - buf[pos++] = ch; - } - } - break; case EditComplete: { - tabComplete(id); - return; - } - break; case EditExpand: { - macroExpand(); - tabAccept(); - return; - } - break; case EditEnter: { - tabAccept(); - command(id, editBuffer(NULL)); - len = pos = 0; - return; - } + break; case EditClear: e->len = e->pos = 0; } + return ret; +} + +int editInsert(struct Edit *e, wchar_t ch) { + char mb[MB_LEN_MAX]; + if (wctomb(mb, ch) < 0) return -1; + if (editReserve(e, e->pos, 1) < 0) return -1; + e->buf[e->pos++] = ch; + return 0; +} + +#ifdef TEST +#undef NDEBUG +#include +#include - if (pos < init) { - tabReject(); - } else { - tabAccept(); +static void fix(struct Edit *e, const char *str) { + editFn(e, EditClear); + for (const char *ch = str; *ch; ++ch) { + editInsert(e, (wchar_t)*ch); } } + +static bool eq(struct Edit *e, const char *str1) { + const char *str2 = &str1[strlen(str1) + 1]; + const char *buf = editString(e); + return e->mbs.pos == strlen(str1) + && !strncmp(buf, str1, e->mbs.pos) + && !strcmp(&buf[e->mbs.pos], str2); +} + +int main(void) { + struct Edit e = { .mode = EditEmacs }; + + fix(&e, "foo bar"); + editFn(&e, EditHead); + assert(eq(&e, "\0foo bar")); + editFn(&e, EditTail); + assert(eq(&e, "foo bar\0")); + editFn(&e, EditPrev); + assert(eq(&e, "foo ba\0r")); + editFn(&e, EditNext); + assert(eq(&e, "foo bar\0")); + + fix(&e, "foo, bar"); + editFn(&e, EditPrevWord); + assert(eq(&e, "foo, \0bar")); + editFn(&e, EditPrevWord); + assert(eq(&e, "\0foo, bar")); + editFn(&e, EditNextWord); + assert(eq(&e, "foo, \0bar")); + editFn(&e, EditNextWord); + assert(eq(&e, "foo, bar\0")); + + fix(&e, "foo bar"); + editFn(&e, EditPrevWord); + editFn(&e, EditDeleteHead); + assert(eq(&e, "\0bar")); + + fix(&e, "foo bar"); + editFn(&e, EditPrevWord); + editFn(&e, EditDeleteTail); + assert(eq(&e, "foo \0")); + + fix(&e, "foo bar"); + editFn(&e, EditDeletePrev); + assert(eq(&e, "foo ba\0")); + editFn(&e, EditHead); + editFn(&e, EditDeleteNext); + assert(eq(&e, "\0oo ba")); + + fix(&e, "foo, bar"); + editFn(&e, EditDeletePrevWord); + assert(eq(&e, "foo, \0")); + editFn(&e, EditDeletePrevWord); + assert(eq(&e, "\0")); + + fix(&e, "foo, bar"); + editFn(&e, EditHead); + editFn(&e, EditDeleteNextWord); + assert(eq(&e, "\0, bar")); + editFn(&e, EditDeleteNextWord); + assert(eq(&e, "\0")); + + fix(&e, "foo bar"); + editFn(&e, EditDeletePrevWord); + editFn(&e, EditPaste); + assert(eq(&e, "foo bar\0")); + editFn(&e, EditPaste); + assert(eq(&e, "foo barbar\0")); + + fix(&e, "bar"); + editFn(&e, EditTranspose); + assert(eq(&e, "bra\0")); + editFn(&e, EditHead); + editFn(&e, EditTranspose); + assert(eq(&e, "rb\0a")); + editFn(&e, EditTranspose); + assert(eq(&e, "rab\0")); + + fix(&e, " foo bar "); + editFn(&e, EditCollapse); + assert(eq(&e, "foo bar\0")); +} + +#endif /* TEST */ diff --git a/edit.h b/edit.h new file mode 100644 index 0000000..57edfc1 --- /dev/null +++ b/edit.h @@ -0,0 +1,91 @@ +/* Copyright (C) 2022 June 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 . + * + * If you modify this Program, or any covered work, by linking or + * combining it with OpenSSL (or a modified version of that library), + * containing parts covered by the terms of the OpenSSL License and the + * original SSLeay license, the licensors of this Program grant you + * additional permission to convey the resulting work. Corresponding + * Source for a non-source form of such a combination shall include the + * source code for the parts of OpenSSL used as well as that of the + * covered work. + */ + +#include +#include + +enum EditMode { + EditEmacs, +}; + +struct Edit { + enum EditMode mode; + wchar_t *buf; + size_t pos; + size_t len; + size_t cap; + struct { + wchar_t *buf; + size_t len; + } cut; + struct { + char *buf; + size_t pos; + size_t len; + } mbs; +}; + +enum EditFn { + EditHead, + EditTail, + EditPrev, + EditNext, + EditPrevWord, + EditNextWord, + EditDeleteHead, + EditDeleteTail, + EditDeletePrev, + EditDeleteNext, + EditDeletePrevWord, + EditDeleteNextWord, + EditPaste, + EditTranspose, + EditCollapse, + EditClear, +}; + +// Perform an editing function. +int editFn(struct Edit *e, enum EditFn fn); + +// Perform a vi-mode editing function. +int editVi(struct Edit *e, wchar_t ch); + +// Insert a character at the cursor. +int editInsert(struct Edit *e, wchar_t ch); + +// Convert the buffer to a multi-byte string stored in e->mbs. +char *editString(struct Edit *e); + +// Free all buffers. +void editFree(struct Edit *e); + +// Reserve a range in the buffer. +int editReserve(struct Edit *e, size_t index, size_t count); + +// Copy a range of the buffer into e->cut. +int editCopy(struct Edit *e, size_t index, size_t count); + +// Delete a range from the buffer. If cut is true, copy the deleted portion. +int editDelete(struct Edit *e, bool cut, size_t index, size_t count); diff --git a/ui.c b/ui.c index 3df0cd6..212d205 100644 --- a/ui.c +++ b/ui.c @@ -54,6 +54,7 @@ #endif #include "chat.h" +#include "edit.h" // Annoying stuff from : #undef lines @@ -752,9 +753,10 @@ static char *inputStop( return stop; } +static struct Edit edit; + static void inputUpdate(void) { - size_t pos; - char *buf = editBuffer(&pos); + char *buf = editString(&edit); struct Window *window = windows.ptrs[windows.show]; const char *prefix = ""; @@ -786,7 +788,7 @@ static void inputUpdate(void) { } else { prompt = ""; } - if (skip > &buf[pos]) { + if (skip > &buf[edit.mbs.pos]) { prefix = prompt = suffix = ""; skip = buf; } @@ -803,10 +805,10 @@ static void inputUpdate(void) { waddstr(input, suffix); getyx(input, y, x); - int posx; + int pos; struct Style style = styleInput; - inputStop(styleInput, &style, skip, &buf[pos]); - getyx(input, y, posx); + inputStop(styleInput, &style, skip, &buf[edit.mbs.pos]); + getyx(input, y, pos); wmove(input, y, x); style = styleInput; @@ -818,7 +820,7 @@ static void inputUpdate(void) { } inputAdd(styleInput, &style, ptr); wclrtoeol(input); - wmove(input, y, posx); + wmove(input, y, pos); } void uiWindows(void) { @@ -965,6 +967,11 @@ static void showAuto(void) { } } +static void inputEnter(uint id) { + command(id, editString(&edit)); + editFn(&edit, EditClear); +} + static void keyCode(int code) { struct Window *window = windows.ptrs[windows.show]; uint id = window->id; @@ -973,7 +980,7 @@ static void keyCode(int code) { break; case KeyFocusIn: unmark(window); break; case KeyFocusOut: mark(window); - break; case KeyMetaEnter: edit(id, EditInsert, L'\n'); + break; case KeyMetaEnter: editInsert(&edit, L'\n'); break; case KeyMetaEqual: window->mute ^= true; statusUpdate(); break; case KeyMetaMinus: incThresh(window, -1); break; case KeyMetaPlus: incThresh(window, +1); @@ -984,32 +991,32 @@ static void keyCode(int code) { break; case KeyMeta0 ... KeyMeta9: uiShowNum(code - KeyMeta0); 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); + break; case KeyMetaB: editFn(&edit, EditPrevWord); + break; case KeyMetaD: editFn(&edit, EditDeleteNextWord); + break; case KeyMetaF: editFn(&edit, EditNextWord); break; case KeyMetaL: windowList(window); break; case KeyMetaM: uiWrite(id, Warm, NULL, ""); break; case KeyMetaN: scrollHot(window, +1); break; case KeyMetaP: scrollHot(window, -1); - break; case KeyMetaQ: edit(id, EditCollapse, 0); + break; case KeyMetaQ: editFn(&edit, EditCollapse); break; case KeyMetaS: spoilerReveal ^= true; mainUpdate(); break; case KeyMetaT: toggleTime(window); break; case KeyMetaU: scrollTo(window, window->unreadHard); break; case KeyMetaV: scrollPage(window, +1); - break; case KeyCtrlLeft: edit(id, EditPrevWord, 0); - break; case KeyCtrlRight: edit(id, EditNextWord, 0); + break; case KeyCtrlLeft: editFn(&edit, EditPrevWord); + break; case KeyCtrlRight: editFn(&edit, EditNextWord); - break; case KEY_BACKSPACE: edit(id, EditDeletePrev, 0); - break; case KEY_DC: edit(id, EditDeleteNext, 0); + break; case KEY_BACKSPACE: editFn(&edit, EditDeletePrev); + break; case KEY_DC: editFn(&edit, EditDeleteNext); 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_END: editFn(&edit, EditTail); + break; case KEY_ENTER: inputEnter(id); + break; case KEY_HOME: editFn(&edit, EditHead); + break; case KEY_LEFT: editFn(&edit, EditPrev); break; case KEY_NPAGE: scrollPage(window, -1); break; case KEY_PPAGE: scrollPage(window, +1); - break; case KEY_RIGHT: edit(id, EditNext, 0); + break; case KEY_RIGHT: editFn(&edit, EditNext); break; case KEY_SEND: scrollTo(window, 0); break; case KEY_SHOME: scrollTo(window, BufferCap); break; case KEY_UP: windowScroll(window, +1); @@ -1020,33 +1027,30 @@ static void keyCtrl(wchar_t ch) { struct Window *window = windows.ptrs[windows.show]; uint id = window->id; switch (ch ^ L'@') { - 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'?': editFn(&edit, EditDeletePrev); + break; case L'A': editFn(&edit, EditHead); + break; case L'B': editFn(&edit, EditPrev); 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); - 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, EditDeleteTail, 0); + break; case L'D': editFn(&edit, EditDeleteNext); + break; case L'E': editFn(&edit, EditTail); + break; case L'F': editFn(&edit, EditNext); + break; case L'H': editFn(&edit, EditDeletePrev); + break; case L'J': inputEnter(id); + break; case L'K': editFn(&edit, EditDeleteTail); break; case L'L': clearok(curscr, true); break; case L'N': uiShowNum(windows.show + 1); break; case L'P': uiShowNum(windows.show - 1); - break; case L'R': scrollSearch(window, editBuffer(NULL), -1); - break; case L'S': scrollSearch(window, editBuffer(NULL), +1); - break; case L'T': edit(id, EditTranspose, 0); - break; case L'U': edit(id, EditDeleteHead, 0); + break; case L'R': scrollSearch(window, editString(&edit), -1); + break; case L'S': scrollSearch(window, editString(&edit), +1); + break; case L'T': editFn(&edit, EditTranspose); + break; case L'U': editFn(&edit, EditDeleteHead); break; case L'V': scrollPage(window, -1); - break; case L'W': edit(id, EditDeletePrevWord, 0); - break; case L'X': edit(id, EditExpand, 0); - break; case L'Y': edit(id, EditPaste, 0); + break; case L'W': editFn(&edit, EditDeletePrevWord); + break; case L'Y': editFn(&edit, EditPaste); } } static void keyStyle(wchar_t ch) { - uint id = windows.ptrs[windows.show]->id; if (iswcntrl(ch)) ch = towlower(ch ^ L'@'); char buf[8] = {0}; enum Color color = Default; @@ -1077,7 +1081,7 @@ static void keyStyle(wchar_t ch) { snprintf(buf, sizeof(buf), "%c%02d", C, color); } for (char *ch = buf; *ch; ++ch) { - edit(id, EditInsert, *ch); + editInsert(&edit, *ch); } } @@ -1103,7 +1107,7 @@ void uiRead(void) { } else if (ret == KEY_CODE_YES && ch == KeyPasteManual) { paste ^= true; } else if (paste || literal) { - edit(windows.ptrs[windows.show]->id, EditInsert, ch); + editInsert(&edit, ch); } else if (ret == KEY_CODE_YES) { keyCode(ch); } else if (ch == (L'Z' ^ L'@')) { @@ -1117,7 +1121,7 @@ void uiRead(void) { } else if (iswcntrl(ch)) { keyCtrl(ch); } else { - edit(windows.ptrs[windows.show]->id, EditInsert, ch); + editInsert(&edit, ch); } style = false; literal = false; -- cgit 1.4.1 From 3359a5d69b0fe3c08812f7db83e27958ffec820f Mon Sep 17 00:00:00 2001 From: June McEnroe Date: Sat, 19 Feb 2022 18:28:45 -0500 Subject: Factor out window management to window.c --- Makefile | 1 + README.7 | 4 +- chat.c | 8 +- chat.h | 74 +++++-- command.c | 22 +- handle.c | 2 +- ui.c | 735 ++++++++------------------------------------------------------ window.c | 656 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 828 insertions(+), 674 deletions(-) create mode 100644 window.c (limited to 'chat.h') diff --git a/Makefile b/Makefile index fd06260..dffe4e8 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,7 @@ OBJS += irc.o OBJS += log.o OBJS += ui.o OBJS += url.o +OBJS += window.o OBJS += xdg.o TESTS += edit.t diff --git a/README.7 b/README.7 index 8f13f7c..b98b589 100644 --- a/README.7 +++ b/README.7 @@ -1,5 +1,5 @@ .\" To view this file, run: man ./README.7 -.Dd February 12, 2022 +.Dd February 19, 2022 .Dt README 7 .Os "Causal Agency" . @@ -181,6 +181,8 @@ startup and event loop IRC connection and parsing .It Pa ui.c curses interface +.It Pa window.c +window management .It Pa handle.c IRC message handling .It Pa command.c diff --git a/chat.c b/chat.c index 454ae31..57cae6d 100644 --- a/chat.c +++ b/chat.c @@ -293,8 +293,8 @@ int main(int argc, char *argv[]) { break; case 'R': self.restricted = true; break; case 'S': bind = optarg; break; case 'T': { - uiTime.enable = true; - if (optarg) uiTime.format = optarg; + windowTime.enable = true; + if (optarg) windowTime.format = optarg; } break; case 'a': sasl = true; parsePlain(optarg); break; case 'c': cert = optarg; @@ -309,7 +309,7 @@ int main(int argc, char *argv[]) { break; case 'n': nick = optarg; break; case 'o': printCert = true; break; case 'p': port = optarg; - break; case 'q': uiThreshold = Warm; + break; case 'q': windowThreshold = Warm; break; case 'r': real = optarg; break; case 's': save = optarg; break; case 't': trust = optarg; @@ -381,7 +381,7 @@ int main(int argc, char *argv[]) { uiLoad(save); atexit(exitSave); } - uiShowID(Network); + windowShow(windowFor(Network)); uiFormat( Network, Cold, NULL, "\3%dcatgirl\3\tis GPLv3 fwee softwawe ^w^ " diff --git a/chat.h b/chat.h index 8a9a48f..e9a2926 100644 --- a/chat.h +++ b/chat.h @@ -293,26 +293,34 @@ const char *commandIsAction(uint id, const char *input); size_t commandWillSplit(uint id, const char *input); void commandCompleteAdd(void); -enum Heat { Ice, Cold, Warm, Hot }; -enum { TimeCap = 64 }; -extern enum Heat uiThreshold; -extern struct Time { - bool enable; - const char *format; - int width; -} uiTime; +enum Heat { + Ice, + Cold, + Warm, + Hot, +}; + +enum { + TitleCap = 256, + StatusLines = 1, + MarkerLines = 1, + SplitLines = 5, + InputLines = 1, + InputCols = 1024, +}; +extern char uiTitle[TitleCap]; +extern struct _win_st *uiStatus; +extern struct _win_st *uiMain; extern struct Util uiNotifyUtil; void uiInitEarly(void); void uiInitLate(void); +uint uiAttr(struct Style style); +short uiPair(struct Style style); +void uiUpdate(void); void uiShow(void); void uiHide(void); +void uiWait(void); void uiDraw(void); -void uiWindows(void); -void uiShowID(uint id); -void uiShowNum(uint num); -void uiMoveID(uint id, uint num); -void uiCloseID(uint id); -void uiCloseNum(uint id); void uiRead(void); void uiWrite(uint id, enum Heat heat, const time_t *time, const char *str); void uiFormat( @@ -321,6 +329,44 @@ void uiFormat( void uiLoad(const char *name); int uiSave(void); +enum Scroll { + ScrollOne, + ScrollPage, + ScrollAll, + ScrollUnread, + ScrollHot, +}; +extern struct Time { + bool enable; + const char *format; + int width; +} windowTime; +extern enum Heat windowThreshold; +void windowInit(void); +void windowUpdate(void); +void windowResize(void); +bool windowWrite(uint id, enum Heat heat, const time_t *time, const char *str); +void windowBare(void); +uint windowID(void); +uint windowNum(void); +uint windowFor(uint id); +void windowShow(uint num); +void windowAuto(void); +void windowSwap(void); +void windowMove(uint from, uint to); +void windowClose(uint num); +void windowList(void); +void windowMark(void); +void windowUnmark(void); +void windowToggleMute(void); +void windowToggleTime(void); +void windowToggleThresh(int n); +bool windowTimeEnable(void); +void windowScroll(enum Scroll by, int n); +void windowSearch(const char *str, int dir); +int windowSave(FILE *file); +void windowLoad(FILE *file, size_t version); + enum { BufferCap = 1024 }; struct Buffer; struct Line { diff --git a/command.c b/command.c index 335c396..a127af3 100644 --- a/command.c +++ b/command.c @@ -144,7 +144,7 @@ static void commandMsg(uint id, char *params) { if (params) { splitMessage("PRIVMSG", msg, params); } else { - uiShowID(msg); + windowShow(windowFor(msg)); } } @@ -376,25 +376,25 @@ static void commandQuery(uint id, char *params) { if (idColors[query] == Default) { idColors[query] = completeColor(id, params); } - uiShowID(query); + windowShow(windowFor(query)); } static void commandWindow(uint id, char *params) { if (!params) { - uiWindows(); + windowList(); } else if (isdigit(params[0])) { - uiShowNum(strtoul(params, NULL, 10)); + windowShow(strtoul(params, NULL, 10)); } else { id = idFind(params); if (id) { - uiShowID(id); + windowShow(windowFor(id)); return; } for (const char *match; (match = completeSubstr(None, params));) { id = idFind(match); if (!id) continue; completeAccept(); - uiShowID(id); + windowShow(windowFor(id)); break; } } @@ -405,20 +405,20 @@ static void commandMove(uint id, char *params) { char *name = strsep(¶ms, " "); if (params) { id = idFind(name); - if (id) uiMoveID(id, strtoul(params, NULL, 10)); + if (id) windowMove(windowFor(id), strtoul(params, NULL, 10)); } else { - uiMoveID(id, strtoul(name, NULL, 10)); + windowMove(windowFor(id), strtoul(name, NULL, 10)); } } static void commandClose(uint id, char *params) { if (!params) { - uiCloseID(id); + windowClose(windowFor(id)); } else if (isdigit(params[0])) { - uiCloseNum(strtoul(params, NULL, 10)); + windowClose(strtoul(params, NULL, 10)); } else { id = idFind(params); - if (id) uiCloseID(id); + if (id) windowClose(windowFor(id)); } } diff --git a/handle.c b/handle.c index d663ca1..f4cf68e 100644 --- a/handle.c +++ b/handle.c @@ -343,7 +343,7 @@ static void handleJoin(struct Message *msg) { idColors[id] = hash(msg->params[0]); completeTouch(None, msg->params[0], idColors[id]); if (replies[ReplyJoin]) { - uiShowID(id); + windowShow(windowFor(id)); replies[ReplyJoin]--; } } diff --git a/ui.c b/ui.c index 212d205..85b5a72 100644 --- a/ui.c +++ b/ui.c @@ -33,15 +33,15 @@ #include #include #include -#include +#include #include #include #include #include #include #include -#include #include +#include #include #include #include @@ -60,102 +60,14 @@ #undef lines #undef tab -enum { - StatusLines = 1, - MarkerLines = 1, - SplitLines = 5, - InputLines = 1, - InputCols = 1024, -}; - #define BOTTOM (LINES - 1) #define RIGHT (COLS - 1) #define MAIN_LINES (LINES - StatusLines - InputLines) -static WINDOW *status; -static WINDOW *main; +WINDOW *uiStatus; +WINDOW *uiMain; static WINDOW *input; -struct Window { - uint id; - int scroll; - bool mark; - bool mute; - bool time; - enum Heat thresh; - enum Heat heat; - uint unreadSoft; - uint unreadHard; - uint unreadWarm; - struct Buffer *buffer; -}; - -static struct { - struct Window *ptrs[IDCap]; - uint len; - uint show; - uint swap; - uint user; -} windows; - -static uint windowPush(struct Window *window) { - assert(windows.len < IDCap); - windows.ptrs[windows.len] = window; - return windows.len++; -} - -static uint windowInsert(uint num, struct Window *window) { - assert(windows.len < IDCap); - assert(num <= windows.len); - memmove( - &windows.ptrs[num + 1], - &windows.ptrs[num], - sizeof(*windows.ptrs) * (windows.len - num) - ); - windows.ptrs[num] = window; - windows.len++; - return num; -} - -static struct Window *windowRemove(uint num) { - assert(num < windows.len); - struct Window *window = windows.ptrs[num]; - windows.len--; - memmove( - &windows.ptrs[num], - &windows.ptrs[num + 1], - sizeof(*windows.ptrs) * (windows.len - num) - ); - return window; -} - -enum Heat uiThreshold = Cold; - -static uint windowFor(uint id) { - for (uint num = 0; num < windows.len; ++num) { - if (windows.ptrs[num]->id == id) return num; - } - struct Window *window = calloc(1, sizeof(*window)); - if (!window) err(EX_OSERR, "malloc"); - window->id = id; - window->mark = true; - window->time = uiTime.enable; - if (id == Network || id == Debug) { - window->thresh = Cold; - } else { - window->thresh = uiThreshold; - } - window->buffer = bufferAlloc(); - completeAdd(None, idNames[id], idColors[id]); - return windowPush(window); -} - -static void windowFree(struct Window *window) { - completeRemove(None, idNames[window->id]); - bufferFree(window->buffer); - free(window); -} - static short colorPairs; static void colorInit(void) { @@ -240,8 +152,6 @@ enum { static const char *FocusMode[2] = { "\33[?1004l", "\33[?1004h" }; static const char *PasteMode[2] = { "\33[?2004l", "\33[?2004h" }; -struct Time uiTime = { .format = "%X" }; - static void errExit(void) { putp(FocusMode[false]); putp(PasteMode[false]); @@ -271,30 +181,18 @@ void uiInitEarly(void) { ENUM_KEY #undef X - status = newwin(StatusLines, COLS, 0, 0); - if (!status) err(EX_OSERR, "newwin"); - - main = newwin(MAIN_LINES, COLS, StatusLines, 0); - if (!main) err(EX_OSERR, "newwin"); - - int y; - char fmt[TimeCap]; - char buf[TimeCap]; - styleStrip(fmt, sizeof(fmt), uiTime.format); - struct tm *time = localtime(&(time_t) { -22100400 }); - size_t len = strftime(buf, sizeof(buf), fmt, time); - if (!len) errx(EX_CONFIG, "invalid timestamp format: %s", fmt); - waddstr(main, buf); - waddch(main, ' '); - getyx(main, y, uiTime.width); - (void)y; + uiStatus = newwin(StatusLines, COLS, 0, 0); + if (!uiStatus) err(EX_OSERR, "newwin"); + + uiMain = newwin(MAIN_LINES, COLS, StatusLines, 0); + if (!uiMain) err(EX_OSERR, "newwin"); input = newpad(InputLines, InputCols); if (!input) err(EX_OSERR, "newpad"); keypad(input, true); nodelay(input, true); - windowFor(Network); + windowInit(); uiShow(); } @@ -323,13 +221,13 @@ void uiInitLate(void) { static bool hidden = true; static bool waiting; -static char title[256]; -static char prevTitle[sizeof(title)]; +char uiTitle[TitleCap]; +static char prevTitle[TitleCap]; void uiDraw(void) { if (hidden) return; - wnoutrefresh(status); - wnoutrefresh(main); + wnoutrefresh(uiStatus); + wnoutrefresh(uiMain); int y, x; getyx(input, y, x); pnoutrefresh( @@ -342,10 +240,10 @@ void uiDraw(void) { doupdate(); if (!to_status_line) return; - if (!strcmp(title, prevTitle)) return; - strcpy(prevTitle, title); + if (!strcmp(uiTitle, prevTitle)) return; + strcpy(prevTitle, uiTitle); putp(to_status_line); - putp(title); + putp(uiTitle); putp(from_status_line); fflush(stdout); } @@ -377,7 +275,7 @@ static const short Colors[ColorCap] = { 16, 233, 235, 237, 239, 241, 244, 247, 250, 254, 231, }; -static attr_t styleAttr(struct Style style) { +uint uiAttr(struct Style style) { attr_t attr = A_NORMAL; if (style.attr & Bold) attr |= A_BOLD; if (style.attr & Reverse) attr |= A_REVERSE; @@ -388,96 +286,13 @@ static attr_t styleAttr(struct Style style) { static bool spoilerReveal; -static short stylePair(struct Style style) { +short uiPair(struct Style style) { if (spoilerReveal && style.fg == style.bg) { return colorPair(Colors[Default], Colors[style.bg]); } return colorPair(Colors[style.fg], Colors[style.bg]); } -static int styleAdd(WINDOW *win, struct Style init, const char *str) { - struct Style style = init; - while (*str) { - size_t len = styleParse(&style, &str); - wattr_set(win, styleAttr(style), stylePair(style), NULL); - if (waddnstr(win, str, len) == ERR) - return -1; - str += len; - } - return 0; -} - -static void statusUpdate(void) { - struct { - uint unread; - enum Heat heat; - } others = { 0, Cold }; - - wmove(status, 0, 0); - for (uint num = 0; num < windows.len; ++num) { - const struct Window *window = windows.ptrs[num]; - if (num != windows.show && !window->scroll) { - if (window->heat < Warm) continue; - if (window->mute && window->heat < Hot) continue; - } - if (num != windows.show) { - others.unread += window->unreadWarm; - if (window->heat > others.heat) others.heat = window->heat; - } - char buf[256], *end = &buf[sizeof(buf)]; - char *ptr = seprintf( - buf, end, "\3%d%s %u%s%s %s ", - idColors[window->id], (num == windows.show ? "\26" : ""), - num, window->thresh[(const char *[]) { "-", "", "+", "++" }], - &"="[!window->mute], idNames[window->id] - ); - if (window->mark && window->unreadWarm) { - ptr = seprintf( - ptr, end, "\3%d+%d\3%d%s", - (window->heat > Warm ? White : idColors[window->id]), - window->unreadWarm, idColors[window->id], - (window->scroll ? "" : " ") - ); - } - if (window->scroll) { - ptr = seprintf(ptr, end, "~%d ", window->scroll); - } - if (styleAdd(status, StyleDefault, buf) < 0) break; - } - wclrtoeol(status); - - const struct Window *window = windows.ptrs[windows.show]; - char *end = &title[sizeof(title)]; - char *ptr = seprintf( - title, end, "%s %s", network.name, idNames[window->id] - ); - if (window->mark && window->unreadWarm) { - ptr = seprintf( - ptr, end, " +%d%s", window->unreadWarm, &"!"[window->heat < Hot] - ); - } - if (others.unread) { - ptr = seprintf( - ptr, end, " (+%d%s)", others.unread, &"!"[others.heat < Hot] - ); - } -} - -static void mark(struct Window *window) { - if (window->scroll) return; - window->mark = true; - window->unreadSoft = 0; - window->unreadWarm = 0; -} - -static void unmark(struct Window *window) { - if (!window->scroll) { - window->mark = false; - window->heat = Cold; - } - statusUpdate(); -} - void uiShow(void) { if (!hidden) return; prevTitle[0] = '\0'; @@ -485,86 +300,20 @@ void uiShow(void) { putp(PasteMode[true]); fflush(stdout); hidden = false; - unmark(windows.ptrs[windows.show]); + windowUnmark(); } void uiHide(void) { if (hidden) return; - mark(windows.ptrs[windows.show]); + windowMark(); hidden = true; putp(FocusMode[false]); putp(PasteMode[false]); endwin(); } -static size_t windowTop(const struct Window *window) { - size_t top = BufferCap - MAIN_LINES - window->scroll; - if (window->scroll) top += MarkerLines; - return top; -} - -static size_t windowBottom(const struct Window *window) { - size_t bottom = BufferCap - (window->scroll ?: 1); - if (window->scroll) bottom -= SplitLines + MarkerLines; - return bottom; -} - -static int windowCols(const struct Window *window) { - return COLS - (window->time ? uiTime.width : 0); -} - -static void mainAdd(int y, bool time, const struct Line *line) { - int ny, nx; - wmove(main, y, 0); - if (!line || !line->str[0]) { - wclrtoeol(main); - return; - } - if (time && line->time) { - char buf[TimeCap]; - strftime(buf, sizeof(buf), uiTime.format, localtime(&line->time)); - struct Style init = { .fg = Gray, .bg = Default }; - styleAdd(main, init, buf); - waddch(main, ' '); - } else if (time) { - whline(main, ' ', uiTime.width); - wmove(main, y, uiTime.width); - } - styleAdd(main, StyleDefault, line->str); - getyx(main, ny, nx); - if (ny != y) return; - wclrtoeol(main); - (void)nx; -} - -static void mainUpdate(void) { - struct Window *window = windows.ptrs[windows.show]; - - int y = 0; - int marker = MAIN_LINES - SplitLines - MarkerLines; - for (size_t i = windowTop(window); i < BufferCap; ++i) { - mainAdd(y++, window->time, bufferHard(window->buffer, i)); - if (window->scroll && y == marker) break; - } - if (!window->scroll) return; - - y = MAIN_LINES - SplitLines; - for (size_t i = BufferCap - SplitLines; i < BufferCap; ++i) { - mainAdd(y++, window->time, bufferHard(window->buffer, i)); - } - wattr_set(main, A_NORMAL, 0, NULL); - mvwhline(main, marker, 0, ACS_BULLET, COLS); -} - -static void windowScroll(struct Window *window, int n) { - mark(window); - window->scroll += n; - if (window->scroll > BufferCap - MAIN_LINES) { - window->scroll = BufferCap - MAIN_LINES; - } - if (window->scroll < 0) window->scroll = 0; - unmark(window); - if (window == windows.ptrs[windows.show]) mainUpdate(); +void uiWait(void) { + waiting = true; } struct Util uiNotifyUtil; @@ -593,36 +342,8 @@ static void notify(uint id, const char *str) { } void uiWrite(uint id, enum Heat heat, const time_t *src, const char *str) { - struct Window *window = windows.ptrs[windowFor(id)]; - time_t ts = (src ? *src : time(NULL)); - - if (heat >= window->thresh) { - if (!window->unreadSoft++) window->unreadHard = 0; - } - if (window->mark && heat > Cold) { - if (!window->unreadWarm++) { - int lines = bufferPush( - window->buffer, windowCols(window), - window->thresh, Warm, ts, "" - ); - if (window->scroll) windowScroll(window, lines); - if (window->unreadSoft > 1) { - window->unreadSoft++; - window->unreadHard += lines; - } - } - if (heat > window->heat) window->heat = heat; - statusUpdate(); - } - int lines = bufferPush( - window->buffer, windowCols(window), - window->thresh, heat, ts, str - ); - window->unreadHard += lines; - if (window->scroll) windowScroll(window, lines); - if (window == windows.ptrs[windows.show]) mainUpdate(); - - if (window->mark && heat > Warm) { + bool note = windowWrite(id, heat, src, str); + if (note) { beep(); notify(id, str); } @@ -640,78 +361,10 @@ void uiFormat( uiWrite(id, heat, time, buf); } -static void scrollTo(struct Window *window, int top) { - window->scroll = 0; - windowScroll(window, top - MAIN_LINES + MarkerLines); -} - -static void windowReflow(struct Window *window) { - uint num = 0; - const struct Line *line = bufferHard(window->buffer, windowTop(window)); - if (line) num = line->num; - window->unreadHard = bufferReflow( - window->buffer, windowCols(window), - window->thresh, window->unreadSoft - ); - if (!window->scroll || !num) return; - for (size_t i = 0; i < BufferCap; ++i) { - line = bufferHard(window->buffer, i); - if (!line || line->num != num) continue; - scrollTo(window, BufferCap - i); - break; - } -} - static void resize(void) { - wclear(main); - wresize(main, MAIN_LINES, COLS); - for (uint num = 0; num < windows.len; ++num) { - windowReflow(windows.ptrs[num]); - } - statusUpdate(); - mainUpdate(); -} - -static void windowList(const struct Window *window) { - uiHide(); - waiting = true; - - uint num = 0; - const struct Line *line = bufferHard(window->buffer, windowBottom(window)); - if (line) num = line->num; - for (size_t i = 0; i < BufferCap; ++i) { - line = bufferSoft(window->buffer, i); - if (!line) continue; - if (line->num > num) break; - if (!line->str[0]) { - printf("\n"); - continue; - } - - char buf[TimeCap]; - strftime(buf, sizeof(buf), uiTime.format, localtime(&line->time)); - vid_attr(colorAttr(Colors[Gray]), colorPair(Colors[Gray], -1), NULL); - printf("%s ", buf); - - bool align = false; - struct Style style = StyleDefault; - for (const char *str = line->str; *str;) { - if (*str == '\t') { - printf("%c", (align ? '\t' : ' ')); - align = true; - str++; - } - - size_t len = styleParse(&style, &str); - size_t tab = strcspn(str, "\t"); - if (tab < len) len = tab; - - vid_attr(styleAttr(style), stylePair(style), NULL); - printf("%.*s", (int)len, str); - str += len; - } - printf("\n"); - } + wclear(uiMain); + wresize(uiMain, MAIN_LINES, COLS); + windowResize(); } static void inputAdd(struct Style reset, struct Style *style, const char *str) { @@ -736,7 +389,7 @@ static void inputAdd(struct Style reset, struct Style *style, const char *str) { } size_t nl = strcspn(str, "\n"); if (nl < len) len = nl; - wattr_set(input, styleAttr(*style), stylePair(*style), NULL); + wattr_set(input, uiAttr(*style), uiPair(*style), NULL); waddnstr(input, str, len); str += len; } @@ -755,9 +408,9 @@ static char *inputStop( static struct Edit edit; -static void inputUpdate(void) { +void uiUpdate(void) { char *buf = editString(&edit); - struct Window *window = windows.ptrs[windows.show]; + uint id = windowID(); const char *prefix = ""; const char *prompt = self.nick; @@ -766,10 +419,10 @@ static void inputUpdate(void) { struct Style stylePrompt = { .fg = self.color, .bg = Default }; struct Style styleInput = StyleDefault; - size_t split = commandWillSplit(window->id, buf); - const char *privmsg = commandIsPrivmsg(window->id, buf); - const char *notice = commandIsNotice(window->id, buf); - const char *action = commandIsAction(window->id, buf); + size_t split = commandWillSplit(id, buf); + const char *privmsg = commandIsPrivmsg(id, buf); + const char *notice = commandIsNotice(id, buf); + const char *action = commandIsAction(id, buf); if (privmsg) { prefix = "<"; suffix = "> "; skip = privmsg; @@ -782,7 +435,7 @@ static void inputUpdate(void) { stylePrompt.attr |= Italic; styleInput.attr |= Italic; skip = action; - } else if (window->id == Debug && buf[0] != '/') { + } else if (id == Debug && buf[0] != '/') { prompt = "<< "; stylePrompt.fg = Gray; } else { @@ -795,11 +448,11 @@ static void inputUpdate(void) { int y, x; wmove(input, 0, 0); - if (window->time && window->id != Network) { - whline(input, ' ', uiTime.width); - wmove(input, 0, uiTime.width); + if (windowTimeEnable() && id != Network) { + whline(input, ' ', windowTime.width); + wmove(input, 0, windowTime.width); } - wattr_set(input, styleAttr(stylePrompt), stylePair(stylePrompt), NULL); + wattr_set(input, uiAttr(stylePrompt), uiPair(stylePrompt), NULL); waddstr(input, prefix); waddstr(input, prompt); waddstr(input, suffix); @@ -823,209 +476,61 @@ static void inputUpdate(void) { wmove(input, y, pos); } -void uiWindows(void) { - for (uint num = 0; num < windows.len; ++num) { - const struct Window *window = windows.ptrs[num]; - uiFormat( - Network, Warm, NULL, "\3%02d%u %s", - idColors[window->id], num, idNames[window->id] - ); - } -} - -static void windowShow(uint num) { - if (num != windows.show) { - windows.swap = windows.show; - mark(windows.ptrs[windows.swap]); - } - windows.show = num; - windows.user = num; - unmark(windows.ptrs[windows.show]); - mainUpdate(); - inputUpdate(); -} - -void uiShowID(uint id) { - windowShow(windowFor(id)); -} - -void uiShowNum(uint num) { - if (num < windows.len) windowShow(num); -} - -void uiMoveID(uint id, uint num) { - struct Window *window = windowRemove(windowFor(id)); - if (num < windows.len) { - windowShow(windowInsert(num, window)); - } else { - windowShow(windowPush(window)); - } -} - -static void windowClose(uint num) { - if (windows.ptrs[num]->id == Network) return; - struct Window *window = windowRemove(num); - completeClear(window->id); - windowFree(window); - if (windows.swap >= num) windows.swap--; - if (windows.show == num) { - windowShow(windows.swap); - windows.swap = windows.show; - } else if (windows.show > num) { - windows.show--; - mainUpdate(); - } - statusUpdate(); -} - -void uiCloseID(uint id) { - windowClose(windowFor(id)); -} - -void uiCloseNum(uint num) { - if (num < windows.len) windowClose(num); -} - -static void scrollPage(struct Window *window, int n) { - windowScroll(window, n * (MAIN_LINES - SplitLines - MarkerLines - 1)); -} - -static void scrollTop(struct Window *window) { - for (size_t i = 0; i < BufferCap; ++i) { - if (!bufferHard(window->buffer, i)) continue; - scrollTo(window, BufferCap - i); - break; - } -} - -static void scrollHot(struct Window *window, int dir) { - for (size_t i = windowTop(window) + dir; i < BufferCap; i += dir) { - const struct Line *line = bufferHard(window->buffer, i); - const struct Line *prev = bufferHard(window->buffer, i - 1); - if (!line || line->heat < Hot) continue; - if (prev && prev->heat > Warm) continue; - scrollTo(window, BufferCap - i); - break; - } -} - -static void scrollSearch(struct Window *window, const char *str, int dir) { - for (size_t i = windowTop(window) + dir; i < BufferCap; i += dir) { - const struct Line *line = bufferHard(window->buffer, i); - if (!line || !strcasestr(line->str, str)) continue; - scrollTo(window, BufferCap - i); - break; - } -} - -static void toggleTime(struct Window *window) { - window->time ^= true; - windowReflow(window); - statusUpdate(); - mainUpdate(); - inputUpdate(); -} - -static void incThresh(struct Window *window, int n) { - if (n > 0 && window->thresh == Hot) return; - if (n < 0 && window->thresh == Ice) { - window->thresh = Cold; - } else { - window->thresh += n; - } - windowReflow(window); - statusUpdate(); - mainUpdate(); - statusUpdate(); -} - -static void showAuto(void) { - uint minHot = UINT_MAX, numHot = 0; - uint minWarm = UINT_MAX, numWarm = 0; - for (uint num = 0; num < windows.len; ++num) { - struct Window *window = windows.ptrs[num]; - if (window->heat >= Hot) { - if (window->unreadWarm >= minHot) continue; - minHot = window->unreadWarm; - numHot = num; - } - if (window->heat >= Warm && !window->mute) { - if (window->unreadWarm >= minWarm) continue; - minWarm = window->unreadWarm; - numWarm = num; - } - } - uint user = windows.user; - if (minHot < UINT_MAX) { - windowShow(numHot); - windows.user = user; - } else if (minWarm < UINT_MAX) { - windowShow(numWarm); - windows.user = user; - } else if (user != windows.show) { - windowShow(user); - } -} - -static void inputEnter(uint id) { - command(id, editString(&edit)); +static void inputEnter(void) { + command(windowID(), editString(&edit)); editFn(&edit, EditClear); } static void keyCode(int code) { - struct Window *window = windows.ptrs[windows.show]; - uint id = window->id; switch (code) { break; case KEY_RESIZE: resize(); - break; case KeyFocusIn: unmark(window); - break; case KeyFocusOut: mark(window); + break; case KeyFocusIn: windowUnmark(); + break; case KeyFocusOut: windowMark(); break; case KeyMetaEnter: editInsert(&edit, L'\n'); - break; case KeyMetaEqual: window->mute ^= true; statusUpdate(); - break; case KeyMetaMinus: incThresh(window, -1); - break; case KeyMetaPlus: incThresh(window, +1); - break; case KeyMetaSlash: windowShow(windows.swap); + break; case KeyMetaEqual: windowToggleMute(); + break; case KeyMetaMinus: windowToggleThresh(-1); + break; case KeyMetaPlus: windowToggleThresh(+1); + break; case KeyMetaSlash: windowSwap(); - break; case KeyMetaGt: scrollTo(window, 0); - break; case KeyMetaLt: scrollTop(window); + break; case KeyMetaGt: windowScroll(ScrollAll, -1); + break; case KeyMetaLt: windowScroll(ScrollAll, +1); - break; case KeyMeta0 ... KeyMeta9: uiShowNum(code - KeyMeta0); - break; case KeyMetaA: showAuto(); + break; case KeyMeta0 ... KeyMeta9: windowShow(code - KeyMeta0); + break; case KeyMetaA: windowAuto(); break; case KeyMetaB: editFn(&edit, EditPrevWord); break; case KeyMetaD: editFn(&edit, EditDeleteNextWord); break; case KeyMetaF: editFn(&edit, EditNextWord); - break; case KeyMetaL: windowList(window); - break; case KeyMetaM: uiWrite(id, Warm, NULL, ""); - break; case KeyMetaN: scrollHot(window, +1); - break; case KeyMetaP: scrollHot(window, -1); + break; case KeyMetaL: windowBare(); + break; case KeyMetaM: uiWrite(windowID(), Warm, NULL, ""); + break; case KeyMetaN: windowScroll(ScrollHot, +1); + break; case KeyMetaP: windowScroll(ScrollHot, -1); break; case KeyMetaQ: editFn(&edit, EditCollapse); - break; case KeyMetaS: spoilerReveal ^= true; mainUpdate(); - break; case KeyMetaT: toggleTime(window); - break; case KeyMetaU: scrollTo(window, window->unreadHard); - break; case KeyMetaV: scrollPage(window, +1); + break; case KeyMetaS: spoilerReveal ^= true; windowUpdate(); + break; case KeyMetaT: windowToggleTime(); + break; case KeyMetaU: windowScroll(ScrollUnread, 0); + break; case KeyMetaV: windowScroll(ScrollPage, +1); break; case KeyCtrlLeft: editFn(&edit, EditPrevWord); break; case KeyCtrlRight: editFn(&edit, EditNextWord); break; case KEY_BACKSPACE: editFn(&edit, EditDeletePrev); break; case KEY_DC: editFn(&edit, EditDeleteNext); - break; case KEY_DOWN: windowScroll(window, -1); + break; case KEY_DOWN: windowScroll(ScrollOne, -1); break; case KEY_END: editFn(&edit, EditTail); - break; case KEY_ENTER: inputEnter(id); + break; case KEY_ENTER: inputEnter(); break; case KEY_HOME: editFn(&edit, EditHead); break; case KEY_LEFT: editFn(&edit, EditPrev); - break; case KEY_NPAGE: scrollPage(window, -1); - break; case KEY_PPAGE: scrollPage(window, +1); + break; case KEY_NPAGE: windowScroll(ScrollPage, -1); + break; case KEY_PPAGE: windowScroll(ScrollPage, +1); break; case KEY_RIGHT: editFn(&edit, EditNext); - break; case KEY_SEND: scrollTo(window, 0); - break; case KEY_SHOME: scrollTo(window, BufferCap); - break; case KEY_UP: windowScroll(window, +1); + break; case KEY_SEND: windowScroll(ScrollAll, -1); + break; case KEY_SHOME: windowScroll(ScrollAll, +1); + break; case KEY_UP: windowScroll(ScrollOne, +1); } } static void keyCtrl(wchar_t ch) { - struct Window *window = windows.ptrs[windows.show]; - uint id = window->id; switch (ch ^ L'@') { break; case L'?': editFn(&edit, EditDeletePrev); break; case L'A': editFn(&edit, EditHead); @@ -1035,16 +540,16 @@ static void keyCtrl(wchar_t ch) { break; case L'E': editFn(&edit, EditTail); break; case L'F': editFn(&edit, EditNext); break; case L'H': editFn(&edit, EditDeletePrev); - break; case L'J': inputEnter(id); + break; case L'J': inputEnter(); break; case L'K': editFn(&edit, EditDeleteTail); break; case L'L': clearok(curscr, true); - break; case L'N': uiShowNum(windows.show + 1); - break; case L'P': uiShowNum(windows.show - 1); - break; case L'R': scrollSearch(window, editString(&edit), -1); - break; case L'S': scrollSearch(window, editString(&edit), +1); + break; case L'N': windowShow(windowNum() + 1); + break; case L'P': windowShow(windowNum() - 1); + break; case L'R': windowSearch(editString(&edit), -1); + break; case L'S': windowSearch(editString(&edit), +1); break; case L'T': editFn(&edit, EditTranspose); break; case L'U': editFn(&edit, EditDeleteHead); - break; case L'V': scrollPage(window, -1); + break; case L'V': windowScroll(ScrollPage, -1); break; case L'W': editFn(&edit, EditDeletePrevWord); break; case L'Y': editFn(&edit, EditPaste); } @@ -1127,13 +632,15 @@ void uiRead(void) { literal = false; if (spr) { spoilerReveal = false; - mainUpdate(); + windowUpdate(); } } - inputUpdate(); + uiUpdate(); } -static const time_t Signatures[] = { +static FILE *saveFile; + +static const uint64_t Signatures[] = { 0x6C72696774616301, // no heat, unread, unreadWarm 0x6C72696774616302, // no self.pos 0x6C72696774616303, // no buffer line heat @@ -1144,68 +651,33 @@ static const time_t Signatures[] = { 0x6C72696774616308, }; -static size_t signatureVersion(time_t signature) { +static size_t signatureVersion(uint64_t signature) { for (size_t i = 0; i < ARRAY_LEN(Signatures); ++i) { if (signature == Signatures[i]) return i; } - errx(EX_DATAERR, "unknown file signature %jX", (uintmax_t)signature); + errx(EX_DATAERR, "unknown file signature %" PRIX64, signature); } -static int writeTime(FILE *file, time_t time) { - return (fwrite(&time, sizeof(time), 1, file) ? 0 : -1); +static int writeUint64(FILE *file, uint64_t u) { + return (fwrite(&u, sizeof(u), 1, file) ? 0 : -1); } -static int writeString(FILE *file, const char *str) { - return (fwrite(str, strlen(str) + 1, 1, file) ? 0 : -1); -} - -static FILE *saveFile; int uiSave(void) { - int error = 0 - || ftruncate(fileno(saveFile), 0) - || writeTime(saveFile, Signatures[7]) - || writeTime(saveFile, self.pos); - if (error) return error; - for (uint num = 0; num < windows.len; ++num) { - const struct Window *window = windows.ptrs[num]; - error = 0 - || writeString(saveFile, idNames[window->id]) - || writeTime(saveFile, window->mute) - || writeTime(saveFile, window->time) - || writeTime(saveFile, window->thresh) - || writeTime(saveFile, window->heat) - || writeTime(saveFile, window->unreadSoft) - || writeTime(saveFile, window->unreadWarm); - if (error) return error; - for (size_t i = 0; i < BufferCap; ++i) { - const struct Line *line = bufferSoft(window->buffer, i); - if (!line) continue; - error = 0 - || writeTime(saveFile, line->time) - || writeTime(saveFile, line->heat) - || writeString(saveFile, line->str); - if (error) return error; - } - error = writeTime(saveFile, 0); - if (error) return error; - } return 0 - || writeString(saveFile, "") + || ftruncate(fileno(saveFile), 0) + || writeUint64(saveFile, Signatures[7]) + || writeUint64(saveFile, self.pos) + || windowSave(saveFile) || urlSave(saveFile) || fclose(saveFile); } -static time_t readTime(FILE *file) { - time_t time; - fread(&time, sizeof(time), 1, file); +static uint64_t readUint64(FILE *file) { + uint64_t u; + fread(&u, sizeof(u), 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; + return u; } void uiLoad(const char *name) { @@ -1235,31 +707,8 @@ void uiLoad(const char *name) { size_t version = signatureVersion(signature); if (version > 1) { - self.pos = readTime(saveFile); - } - - char *buf = NULL; - size_t cap = 0; - while (0 < readString(saveFile, &buf, &cap) && buf[0]) { - struct Window *window = windows.ptrs[windowFor(idFor(buf))]; - if (version > 3) window->mute = readTime(saveFile); - if (version > 6) window->time = readTime(saveFile); - if (version > 5) window->thresh = readTime(saveFile); - if (version > 0) { - window->heat = readTime(saveFile); - window->unreadSoft = readTime(saveFile); - window->unreadWarm = readTime(saveFile); - } - for (;;) { - time_t time = readTime(saveFile); - if (!time) break; - enum Heat heat = (version > 2 ? readTime(saveFile) : Cold); - readString(saveFile, &buf, &cap); - bufferPush(window->buffer, COLS, window->thresh, heat, time, buf); - } - windowReflow(window); + self.pos = readUint64(saveFile); } + windowLoad(saveFile, version); urlLoad(saveFile, version); - - free(buf); } diff --git a/window.c b/window.c new file mode 100644 index 0000000..ce3ec4d --- /dev/null +++ b/window.c @@ -0,0 +1,656 @@ +/* Copyright (C) 2020 June 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 . + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this Program, or any covered work, by linking or + * combining it with OpenSSL (or a modified version of that library), + * containing parts covered by the terms of the OpenSSL License and the + * original SSLeay license, the licensors of this Program grant you + * additional permission to convey the resulting work. Corresponding + * Source for a non-source form of such a combination shall include the + * source code for the parts of OpenSSL used as well as that of the + * covered work. + */ + +#define _XOPEN_SOURCE_EXTENDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "chat.h" + +#define MAIN_LINES (LINES - StatusLines - InputLines) + +static struct Window { + uint id; + int scroll; + bool mark; + bool mute; + bool time; + enum Heat thresh; + enum Heat heat; + uint unreadSoft; + uint unreadHard; + uint unreadWarm; + struct Buffer *buffer; +} *windows[IDCap]; + +static uint count; +static uint show; +static uint swap; +static uint user; + +static uint windowPush(struct Window *window) { + assert(count < IDCap); + windows[count] = window; + return count++; +} + +static uint windowInsert(uint num, struct Window *window) { + assert(count < IDCap); + assert(num <= count); + memmove( + &windows[num + 1], + &windows[num], + sizeof(*windows) * (count - num) + ); + windows[num] = window; + count++; + return num; +} + +static struct Window *windowRemove(uint num) { + assert(num < count); + struct Window *window = windows[num]; + count--; + memmove( + &windows[num], + &windows[num + 1], + sizeof(*windows) * (count - num) + ); + return window; +} + +static void windowFree(struct Window *window) { + completeRemove(None, idNames[window->id]); + bufferFree(window->buffer); + free(window); +} + +enum Heat windowThreshold = Cold; +struct Time windowTime = { .format = "%X" }; + +uint windowFor(uint id) { + for (uint num = 0; num < count; ++num) { + if (windows[num]->id == id) return num; + } + + struct Window *window = calloc(1, sizeof(*window)); + if (!window) err(EX_OSERR, "malloc"); + + window->id = id; + window->mark = true; + window->time = windowTime.enable; + if (id == Network || id == Debug) { + window->thresh = Cold; + } else { + window->thresh = windowThreshold; + } + window->buffer = bufferAlloc(); + completeAdd(None, idNames[id], idColors[id]); + + return windowPush(window); +} + +enum { TimeCap = 64 }; + +void windowInit(void) { + char fmt[TimeCap]; + char buf[TimeCap]; + styleStrip(fmt, sizeof(fmt), windowTime.format); + + struct tm *time = localtime(&(time_t) { -22100400 }); + size_t len = strftime(buf, sizeof(buf), fmt, time); + if (!len) errx(EX_CONFIG, "invalid timestamp format: %s", fmt); + + int y; + waddstr(uiMain, buf); + waddch(uiMain, ' '); + getyx(uiMain, y, windowTime.width); + (void)y; + + windowFor(Network); +} + +static int styleAdd(WINDOW *win, struct Style init, const char *str) { + struct Style style = init; + while (*str) { + size_t len = styleParse(&style, &str); + wattr_set(win, uiAttr(style), uiPair(style), NULL); + if (waddnstr(win, str, len) == ERR) + return -1; + str += len; + } + return 0; +} + +static void statusUpdate(void) { + struct { + uint unread; + enum Heat heat; + } others = { 0, Cold }; + + wmove(uiStatus, 0, 0); + for (uint num = 0; num < count; ++num) { + const struct Window *window = windows[num]; + if (num != show && !window->scroll) { + if (window->heat < Warm) continue; + if (window->mute && window->heat < Hot) continue; + } + if (num != show) { + others.unread += window->unreadWarm; + if (window->heat > others.heat) others.heat = window->heat; + } + char buf[256], *end = &buf[sizeof(buf)]; + char *ptr = seprintf( + buf, end, "\3%d%s %u%s%s %s ", + idColors[window->id], (num == show ? "\26" : ""), + num, window->thresh[(const char *[]) { "-", "", "+", "++" }], + &"="[!window->mute], idNames[window->id] + ); + if (window->mark && window->unreadWarm) { + ptr = seprintf( + ptr, end, "\3%d+%d\3%d%s", + (window->heat > Warm ? White : idColors[window->id]), + window->unreadWarm, idColors[window->id], + (window->scroll ? "" : " ") + ); + } + if (window->scroll) { + ptr = seprintf(ptr, end, "~%d ", window->scroll); + } + if (styleAdd(uiStatus, StyleDefault, buf) < 0) break; + } + wclrtoeol(uiStatus); + + const struct Window *window = windows[show]; + char *end = &uiTitle[sizeof(uiTitle)]; + char *ptr = seprintf( + uiTitle, end, "%s %s", network.name, idNames[window->id] + ); + if (window->mark && window->unreadWarm) { + ptr = seprintf( + ptr, end, " +%d%s", window->unreadWarm, &"!"[window->heat < Hot] + ); + } + if (others.unread) { + ptr = seprintf( + ptr, end, " (+%d%s)", others.unread, &"!"[others.heat < Hot] + ); + } +} + +static size_t windowTop(const struct Window *window) { + size_t top = BufferCap - MAIN_LINES - window->scroll; + if (window->scroll) top += MarkerLines; + return top; +} + +static size_t windowBottom(const struct Window *window) { + size_t bottom = BufferCap - (window->scroll ?: 1); + if (window->scroll) bottom -= SplitLines + MarkerLines; + return bottom; +} + +static void mainAdd(int y, bool time, const struct Line *line) { + int ny, nx; + wmove(uiMain, y, 0); + if (!line || !line->str[0]) { + wclrtoeol(uiMain); + return; + } + if (time && line->time) { + char buf[TimeCap]; + strftime(buf, sizeof(buf), windowTime.format, localtime(&line->time)); + struct Style init = { .fg = Gray, .bg = Default }; + styleAdd(uiMain, init, buf); + waddch(uiMain, ' '); + } else if (time) { + whline(uiMain, ' ', windowTime.width); + wmove(uiMain, y, windowTime.width); + } + styleAdd(uiMain, StyleDefault, line->str); + getyx(uiMain, ny, nx); + if (ny != y) return; + wclrtoeol(uiMain); + (void)nx; +} + +static void mainUpdate(void) { + const struct Window *window = windows[show]; + + int y = 0; + int marker = MAIN_LINES - SplitLines - MarkerLines; + for (size_t i = windowTop(window); i < BufferCap; ++i) { + mainAdd(y++, window->time, bufferHard(window->buffer, i)); + if (window->scroll && y == marker) break; + } + if (!window->scroll) return; + + y = MAIN_LINES - SplitLines; + for (size_t i = BufferCap - SplitLines; i < BufferCap; ++i) { + mainAdd(y++, window->time, bufferHard(window->buffer, i)); + } + wattr_set(uiMain, A_NORMAL, 0, NULL); + mvwhline(uiMain, marker, 0, ACS_BULLET, COLS); +} + +void windowUpdate(void) { + statusUpdate(); + mainUpdate(); +} + +void windowBare(void) { + uiHide(); + uiWait(); + + const struct Window *window = windows[show]; + const struct Line *line = bufferHard(window->buffer, windowBottom(window)); + + uint num = 0; + if (line) num = line->num; + for (size_t i = 0; i < BufferCap; ++i) { + line = bufferSoft(window->buffer, i); + if (!line) continue; + if (line->num > num) break; + if (!line->str[0]) { + printf("\n"); + continue; + } + + char buf[TimeCap]; + struct Style style = { .fg = Gray, .bg = Default }; + strftime(buf, sizeof(buf), windowTime.format, localtime(&line->time)); + vid_attr(uiAttr(style), uiPair(style), NULL); + printf("%s ", buf); + + bool align = false; + style = StyleDefault; + for (const char *str = line->str; *str;) { + if (*str == '\t') { + printf("%c", (align ? '\t' : ' ')); + align = true; + str++; + } + + size_t len = styleParse(&style, &str); + size_t tab = strcspn(str, "\t"); + if (tab < len) len = tab; + + vid_attr(uiAttr(style), uiPair(style), NULL); + printf("%.*s", (int)len, str); + str += len; + } + printf("\n"); + } +} + +static void mark(struct Window *window) { + if (window->scroll) return; + window->mark = true; + window->unreadSoft = 0; + window->unreadWarm = 0; +} + +static void unmark(struct Window *window) { + if (!window->scroll) { + window->mark = false; + window->heat = Cold; + } + statusUpdate(); +} + +static void scrollN(struct Window *window, int n) { + mark(window); + window->scroll += n; + if (window->scroll > BufferCap - MAIN_LINES) { + window->scroll = BufferCap - MAIN_LINES; + } + if (window->scroll < 0) window->scroll = 0; + unmark(window); + if (window == windows[show]) mainUpdate(); +} + +static void scrollTo(struct Window *window, int top) { + window->scroll = 0; + scrollN(window, top - MAIN_LINES + MarkerLines); +} + +static int windowCols(const struct Window *window) { + return COLS - (window->time ? windowTime.width : 0); +} + +bool windowWrite(uint id, enum Heat heat, const time_t *src, const char *str) { + struct Window *window = windows[windowFor(id)]; + time_t ts = (src ? *src : time(NULL)); + + if (heat >= window->thresh) { + if (!window->unreadSoft++) window->unreadHard = 0; + } + if (window->mark && heat > Cold) { + if (!window->unreadWarm++) { + int lines = bufferPush( + window->buffer, windowCols(window), + window->thresh, Warm, ts, "" + ); + if (window->scroll) scrollN(window, lines); + if (window->unreadSoft > 1) { + window->unreadSoft++; + window->unreadHard += lines; + } + } + if (heat > window->heat) window->heat = heat; + statusUpdate(); + } + int lines = bufferPush( + window->buffer, windowCols(window), + window->thresh, heat, ts, str + ); + window->unreadHard += lines; + if (window->scroll) scrollN(window, lines); + if (window == windows[show]) mainUpdate(); + + return window->mark && heat > Warm; +} + +static void reflow(struct Window *window) { + uint num = 0; + const struct Line *line = bufferHard(window->buffer, windowTop(window)); + if (line) num = line->num; + window->unreadHard = bufferReflow( + window->buffer, windowCols(window), + window->thresh, window->unreadSoft + ); + if (!window->scroll || !num) return; + for (size_t i = 0; i < BufferCap; ++i) { + line = bufferHard(window->buffer, i); + if (!line || line->num != num) continue; + scrollTo(window, BufferCap - i); + break; + } +} + +void windowResize(void) { + for (uint num = 0; num < count; ++num) { + reflow(windows[num]); + } + windowUpdate(); +} + +uint windowID(void) { + return windows[show]->id; +} + +uint windowNum(void) { + return show; +} + +void windowShow(uint num) { + if (num >= count) return; + if (num != show) { + swap = show; + mark(windows[swap]); + } + show = num; + user = num; + unmark(windows[show]); + mainUpdate(); + uiUpdate(); +} + +void windowAuto(void) { + uint minHot = UINT_MAX, numHot = 0; + uint minWarm = UINT_MAX, numWarm = 0; + for (uint num = 0; num < count; ++num) { + struct Window *window = windows[num]; + if (window->heat >= Hot) { + if (window->unreadWarm >= minHot) continue; + minHot = window->unreadWarm; + numHot = num; + } + if (window->heat >= Warm && !window->mute) { + if (window->unreadWarm >= minWarm) continue; + minWarm = window->unreadWarm; + numWarm = num; + } + } + uint oldUser = user; + if (minHot < UINT_MAX) { + windowShow(numHot); + user = oldUser; + } else if (minWarm < UINT_MAX) { + windowShow(numWarm); + user = oldUser; + } else if (user != show) { + windowShow(user); + } +} + +void windowSwap(void) { + windowShow(swap); +} + +void windowMove(uint from, uint to) { + if (from >= count) return; + struct Window *window = windowRemove(from); + if (to < count) { + windowShow(windowInsert(to, window)); + } else { + windowShow(windowPush(window)); + } +} + +void windowClose(uint num) { + if (num >= count) return; + if (windows[num]->id == Network) return; + struct Window *window = windowRemove(num); + completeClear(window->id); + windowFree(window); + if (swap >= num) swap--; + if (show == num) { + windowShow(swap); + swap = show; + } else if (show > num) { + show--; + mainUpdate(); + } + statusUpdate(); +} + +void windowList(void) { + for (uint num = 0; num < count; ++num) { + const struct Window *window = windows[num]; + uiFormat( + Network, Warm, NULL, "\3%02d%u %s", + idColors[window->id], num, idNames[window->id] + ); + } +} + +void windowMark(void) { + mark(windows[show]); +} + +void windowUnmark(void) { + unmark(windows[show]); +} + +void windowToggleMute(void) { + windows[show]->mute ^= true; + statusUpdate(); +} + +void windowToggleTime(void) { + windows[show]->time ^= true; + reflow(windows[show]); + windowUpdate(); + uiUpdate(); +} + +void windowToggleThresh(int n) { + struct Window *window = windows[show]; + if (n > 0 && window->thresh == Hot) return; + if (n < 0 && window->thresh == Ice) { + window->thresh = Cold; + } else { + window->thresh += n; + } + reflow(window); + windowUpdate(); +} + +bool windowTimeEnable(void) { + return windows[show]->time; +} + +void windowScroll(enum Scroll by, int n) { + struct Window *window = windows[show]; + switch (by) { + break; case ScrollOne: { + scrollN(window, n); + } + break; case ScrollPage: { + scrollN(window, n * (MAIN_LINES - SplitLines - MarkerLines - 1)); + } + break; case ScrollAll: { + if (n < 0) { + scrollTo(window, 0); + break; + } + for (size_t i = 0; i < BufferCap; ++i) { + if (!bufferHard(window->buffer, i)) continue; + scrollTo(window, BufferCap - i); + break; + } + } + break; case ScrollUnread: { + scrollTo(window, window->unreadHard); + } + break; case ScrollHot: { + for (size_t i = windowTop(window) + n; i < BufferCap; i += n) { + const struct Line *line = bufferHard(window->buffer, i); + const struct Line *prev = bufferHard(window->buffer, i - 1); + if (!line || line->heat < Hot) continue; + if (prev && prev->heat > Warm) continue; + scrollTo(window, BufferCap - i); + break; + } + } + } +} + +void windowSearch(const char *str, int dir) { + struct Window *window = windows[show]; + for (size_t i = windowTop(window) + dir; i < BufferCap; i += dir) { + const struct Line *line = bufferHard(window->buffer, i); + if (!line || !strcasestr(line->str, str)) continue; + scrollTo(window, BufferCap - i); + break; + } +} + +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 windowSave(FILE *file) { + int error; + for (uint num = 0; num < count; ++num) { + const struct Window *window = windows[num]; + error = 0 + || writeString(file, idNames[window->id]) + || writeTime(file, window->mute) + || writeTime(file, window->time) + || writeTime(file, window->thresh) + || writeTime(file, window->heat) + || writeTime(file, window->unreadSoft) + || writeTime(file, window->unreadWarm); + if (error) return error; + for (size_t i = 0; i < BufferCap; ++i) { + const struct Line *line = bufferSoft(window->buffer, i); + if (!line) continue; + error = 0 + || writeTime(file, line->time) + || writeTime(file, line->heat) + || writeString(file, line->str); + if (error) return error; + } + error = writeTime(file, 0); + if (error) return error; + } + return writeString(file, ""); +} + +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 windowLoad(FILE *file, size_t version) { + size_t cap = 0; + char *buf = NULL; + while (0 < readString(file, &buf, &cap) && buf[0]) { + struct Window *window = windows[windowFor(idFor(buf))]; + if (version > 3) window->mute = readTime(file); + if (version > 6) window->time = readTime(file); + if (version > 5) window->thresh = readTime(file); + if (version > 0) { + window->heat = readTime(file); + window->unreadSoft = readTime(file); + window->unreadWarm = readTime(file); + } + for (;;) { + time_t time = readTime(file); + if (!time) break; + enum Heat heat = (version > 2 ? readTime(file) : Cold); + readString(file, &buf, &cap); + bufferPush(window->buffer, COLS, window->thresh, heat, time, buf); + } + reflow(window); + } + free(buf); +} -- cgit 1.4.1 From 073cebec7a5a07ab2b829e40ce47ec3b1d774bd9 Mon Sep 17 00:00:00 2001 From: June McEnroe Date: Sat, 19 Feb 2022 20:20:19 -0500 Subject: Factor out input handling to input.c --- Makefile | 5 +- README.7 | 4 +- chat.c | 9 +- chat.h | 14 ++- handle.c | 2 +- input.c | 390 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ui.c | 369 ++--------------------------------------------------------- window.c | 6 +- 8 files changed, 424 insertions(+), 375 deletions(-) create mode 100644 input.c (limited to 'chat.h') diff --git a/Makefile b/Makefile index dffe4e8..3abba03 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,7 @@ OBJS += config.o OBJS += edit.o OBJS += filter.o OBJS += handle.o +OBJS += input.o OBJS += irc.o OBJS += log.o OBJS += ui.o @@ -36,7 +37,9 @@ all: catgirl catgirl: ${OBJS} ${CC} ${LDFLAGS} ${OBJS} ${LDLIBS} -o $@ -${OBJS} ${TESTS}: chat.h edit.h +${OBJS}: chat.h + +edit.o edit.t input.o: edit.h check: ${TESTS} diff --git a/README.7 b/README.7 index b98b589..dd0020d 100644 --- a/README.7 +++ b/README.7 @@ -183,10 +183,12 @@ IRC connection and parsing curses interface .It Pa window.c window management +.It Pa input.c +input handling .It Pa handle.c IRC message handling .It Pa command.c -input command handling +command handling .It Pa buffer.c line wrapping .It Pa edit.c diff --git a/chat.c b/chat.c index 57cae6d..c4b256c 100644 --- a/chat.c +++ b/chat.c @@ -375,7 +375,7 @@ int main(int argc, char *argv[]) { ircConfig(insecure, trust, cert, priv); - uiInitEarly(); + uiInit(); sig_t cursesWinch = signal(SIGWINCH, signalHandler); if (save) { uiLoad(save); @@ -407,7 +407,8 @@ int main(int argc, char *argv[]) { ircFormat("NICK :%s\r\n", nick); ircFormat("USER %s 0 * :%s\r\n", user, real); - uiInitLate(); + // Avoid disabling VINTR until main loop. + inputInit(); signal(SIGHUP, signalHandler); signal(SIGINT, signalHandler); signal(SIGALRM, signalHandler); @@ -436,7 +437,7 @@ int main(int argc, char *argv[]) { int nfds = poll(fds, (pipes ? ARRAY_LEN(fds) : 2), -1); if (nfds < 0 && errno != EINTR) err(EX_IOERR, "poll"); if (nfds > 0) { - if (fds[0].revents) uiRead(); + if (fds[0].revents) inputRead(); if (fds[1].revents) ircRecv(); if (fds[2].revents) utilRead(); if (fds[3].revents) execRead(); @@ -488,7 +489,7 @@ int main(int argc, char *argv[]) { cursesWinch(SIGWINCH); // doupdate(3) needs to be called for KEY_RESIZE to be picked up. uiDraw(); - uiRead(); + inputRead(); } uiDraw(); diff --git a/chat.h b/chat.h index e9a2926..1bf1edd 100644 --- a/chat.h +++ b/chat.h @@ -311,17 +311,16 @@ enum { extern char uiTitle[TitleCap]; extern struct _win_st *uiStatus; extern struct _win_st *uiMain; +extern struct _win_st *uiInput; +extern bool uiSpoilerReveal; extern struct Util uiNotifyUtil; -void uiInitEarly(void); -void uiInitLate(void); +void uiInit(void); uint uiAttr(struct Style style); short uiPair(struct Style style); -void uiUpdate(void); void uiShow(void); void uiHide(void); -void uiWait(void); void uiDraw(void); -void uiRead(void); +void uiResize(void); void uiWrite(uint id, enum Heat heat, const time_t *time, const char *str); void uiFormat( uint id, enum Heat heat, const time_t *time, const char *format, ... @@ -329,6 +328,11 @@ void uiFormat( void uiLoad(const char *name); int uiSave(void); +void inputInit(void); +void inputWait(void); +void inputUpdate(void); +void inputRead(void); + enum Scroll { ScrollOne, ScrollPage, diff --git a/handle.c b/handle.c index f4cf68e..9f051c7 100644 --- a/handle.c +++ b/handle.c @@ -425,7 +425,7 @@ static void handleNick(struct Message *msg) { require(msg, true, 1); if (!strcmp(msg->nick, self.nick)) { set(&self.nick, msg->params[0]); - uiRead(); // Update prompt. + inputUpdate(); } for (uint id; (id = completeID(msg->nick));) { if (!strcmp(idNames[id], msg->nick)) { diff --git a/input.c b/input.c new file mode 100644 index 0000000..963bd4e --- /dev/null +++ b/input.c @@ -0,0 +1,390 @@ +/* Copyright (C) 2020 June 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 . + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this Program, or any covered work, by linking or + * combining it with OpenSSL (or a modified version of that library), + * containing parts covered by the terms of the OpenSSL License and the + * original SSLeay license, the licensors of this Program grant you + * additional permission to convey the resulting work. Corresponding + * Source for a non-source form of such a combination shall include the + * source code for the parts of OpenSSL used as well as that of the + * covered work. + */ + +#define _XOPEN_SOURCE_EXTENDED + +#include +#include +#include +#include +#include +#include +#include + +#include "chat.h" +#include "edit.h" + +#define ENUM_KEY \ + X(KeyCtrlLeft, "\33[1;5D", NULL) \ + X(KeyCtrlRight, "\33[1;5C", NULL) \ + X(KeyMeta0, "\0330", "\33)") \ + X(KeyMeta1, "\0331", "\33!") \ + X(KeyMeta2, "\0332", "\33@") \ + X(KeyMeta3, "\0333", "\33#") \ + X(KeyMeta4, "\0334", "\33$") \ + X(KeyMeta5, "\0335", "\33%") \ + X(KeyMeta6, "\0336", "\33^") \ + X(KeyMeta7, "\0337", "\33&") \ + X(KeyMeta8, "\0338", "\33*") \ + X(KeyMeta9, "\0339", "\33(") \ + X(KeyMetaA, "\33a", NULL) \ + X(KeyMetaB, "\33b", NULL) \ + X(KeyMetaD, "\33d", NULL) \ + X(KeyMetaF, "\33f", NULL) \ + X(KeyMetaL, "\33l", NULL) \ + X(KeyMetaM, "\33m", NULL) \ + X(KeyMetaN, "\33n", NULL) \ + X(KeyMetaP, "\33p", NULL) \ + X(KeyMetaQ, "\33q", NULL) \ + X(KeyMetaS, "\33s", NULL) \ + X(KeyMetaT, "\33t", NULL) \ + X(KeyMetaU, "\33u", NULL) \ + X(KeyMetaV, "\33v", NULL) \ + X(KeyMetaEnter, "\33\r", "\33\n") \ + X(KeyMetaGt, "\33>", "\33.") \ + X(KeyMetaLt, "\33<", "\33,") \ + X(KeyMetaEqual, "\33=", NULL) \ + X(KeyMetaMinus, "\33-", "\33_") \ + X(KeyMetaPlus, "\33+", NULL) \ + X(KeyMetaSlash, "\33/", "\33?") \ + X(KeyFocusIn, "\33[I", NULL) \ + X(KeyFocusOut, "\33[O", NULL) \ + X(KeyPasteOn, "\33[200~", NULL) \ + X(KeyPasteOff, "\33[201~", NULL) \ + X(KeyPasteManual, "\32p", "\32\20") + +enum { + KeyMax = KEY_MAX, +#define X(id, seq, alt) id, + ENUM_KEY +#undef X +}; + +static struct Edit edit; + +void inputInit(void) { + struct termios term; + int error = tcgetattr(STDOUT_FILENO, &term); + if (error) err(EX_OSERR, "tcgetattr"); + + // Gain use of C-q, C-s, C-c, C-z, C-y, C-v, C-o. + term.c_iflag &= ~IXON; + term.c_cc[VINTR] = _POSIX_VDISABLE; + term.c_cc[VSUSP] = _POSIX_VDISABLE; +#ifdef VDSUSP + term.c_cc[VDSUSP] = _POSIX_VDISABLE; +#endif + term.c_cc[VLNEXT] = _POSIX_VDISABLE; + term.c_cc[VDISCARD] = _POSIX_VDISABLE; + + error = tcsetattr(STDOUT_FILENO, TCSANOW, &term); + if (error) err(EX_OSERR, "tcsetattr"); + + def_prog_mode(); + +#define X(id, seq, alt) define_key(seq, id); if (alt) define_key(alt, id); + ENUM_KEY +#undef X + + keypad(uiInput, true); + nodelay(uiInput, true); +} + +static void inputAdd(struct Style reset, struct Style *style, const char *str) { + while (*str) { + const char *code = str; + size_t len = styleParse(style, &str); + wattr_set(uiInput, A_BOLD | A_REVERSE, 0, NULL); + switch (*code) { + break; case B: waddch(uiInput, 'B'); + break; case C: waddch(uiInput, 'C'); + break; case O: waddch(uiInput, 'O'); + break; case R: waddch(uiInput, 'R'); + break; case I: waddch(uiInput, 'I'); + break; case U: waddch(uiInput, 'U'); + break; case '\n': waddch(uiInput, 'N'); + } + if (str - code > 1) waddnstr(uiInput, &code[1], str - &code[1]); + if (str[0] == '\n') { + *style = reset; + str++; + len--; + } + size_t nl = strcspn(str, "\n"); + if (nl < len) len = nl; + wattr_set(uiInput, uiAttr(*style), uiPair(*style), NULL); + waddnstr(uiInput, str, len); + str += len; + } +} + +static char *inputStop( + struct Style reset, struct Style *style, + const char *str, char *stop +) { + char ch = *stop; + *stop = '\0'; + inputAdd(reset, style, str); + *stop = ch; + return stop; +} + +void inputUpdate(void) { + uint id = windowID(); + char *buf = editString(&edit); + + const char *prefix = ""; + const char *prompt = self.nick; + const char *suffix = ""; + const char *skip = buf; + struct Style stylePrompt = { .fg = self.color, .bg = Default }; + struct Style styleInput = StyleDefault; + + size_t split = commandWillSplit(id, buf); + const char *privmsg = commandIsPrivmsg(id, buf); + const char *notice = commandIsNotice(id, buf); + const char *action = commandIsAction(id, buf); + if (privmsg) { + prefix = "<"; suffix = "> "; + skip = privmsg; + } else if (notice) { + prefix = "-"; suffix = "- "; + styleInput.fg = LightGray; + skip = notice; + } else if (action) { + prefix = "* "; suffix = " "; + stylePrompt.attr |= Italic; + styleInput.attr |= Italic; + skip = action; + } else if (id == Debug && buf[0] != '/') { + prompt = "<< "; + stylePrompt.fg = Gray; + } else { + prompt = ""; + } + if (skip > &buf[edit.mbs.pos]) { + prefix = prompt = suffix = ""; + skip = buf; + } + + int y, x; + wmove(uiInput, 0, 0); + if (windowTimeEnable() && id != Network) { + whline(uiInput, ' ', windowTime.width); + wmove(uiInput, 0, windowTime.width); + } + wattr_set(uiInput, uiAttr(stylePrompt), uiPair(stylePrompt), NULL); + waddstr(uiInput, prefix); + waddstr(uiInput, prompt); + waddstr(uiInput, suffix); + getyx(uiInput, y, x); + + int pos; + struct Style style = styleInput; + inputStop(styleInput, &style, skip, &buf[edit.mbs.pos]); + getyx(uiInput, y, pos); + wmove(uiInput, y, x); + + style = styleInput; + const char *ptr = skip; + if (split) { + ptr = inputStop(styleInput, &style, ptr, &buf[split]); + style = styleInput; + style.bg = Red; + } + inputAdd(styleInput, &style, ptr); + wclrtoeol(uiInput); + wmove(uiInput, y, pos); +} + +static void inputEnter(void) { + command(windowID(), editString(&edit)); + editFn(&edit, EditClear); +} + +static void keyCode(int code) { + switch (code) { + break; case KEY_RESIZE: uiResize(); + break; case KeyFocusIn: windowUnmark(); + break; case KeyFocusOut: windowMark(); + + break; case KeyMetaEnter: editInsert(&edit, L'\n'); + break; case KeyMetaEqual: windowToggleMute(); + break; case KeyMetaMinus: windowToggleThresh(-1); + break; case KeyMetaPlus: windowToggleThresh(+1); + break; case KeyMetaSlash: windowSwap(); + + break; case KeyMetaGt: windowScroll(ScrollAll, -1); + break; case KeyMetaLt: windowScroll(ScrollAll, +1); + + break; case KeyMeta0 ... KeyMeta9: windowShow(code - KeyMeta0); + break; case KeyMetaA: windowAuto(); + break; case KeyMetaB: editFn(&edit, EditPrevWord); + break; case KeyMetaD: editFn(&edit, EditDeleteNextWord); + break; case KeyMetaF: editFn(&edit, EditNextWord); + break; case KeyMetaL: windowBare(); + break; case KeyMetaM: uiWrite(windowID(), Warm, NULL, ""); + break; case KeyMetaN: windowScroll(ScrollHot, +1); + break; case KeyMetaP: windowScroll(ScrollHot, -1); + break; case KeyMetaQ: editFn(&edit, EditCollapse); + break; case KeyMetaS: uiSpoilerReveal ^= true; windowUpdate(); + break; case KeyMetaT: windowToggleTime(); + break; case KeyMetaU: windowScroll(ScrollUnread, 0); + break; case KeyMetaV: windowScroll(ScrollPage, +1); + + break; case KeyCtrlLeft: editFn(&edit, EditPrevWord); + break; case KeyCtrlRight: editFn(&edit, EditNextWord); + + break; case KEY_BACKSPACE: editFn(&edit, EditDeletePrev); + break; case KEY_DC: editFn(&edit, EditDeleteNext); + break; case KEY_DOWN: windowScroll(ScrollOne, -1); + break; case KEY_END: editFn(&edit, EditTail); + break; case KEY_ENTER: inputEnter(); + break; case KEY_HOME: editFn(&edit, EditHead); + break; case KEY_LEFT: editFn(&edit, EditPrev); + break; case KEY_NPAGE: windowScroll(ScrollPage, -1); + break; case KEY_PPAGE: windowScroll(ScrollPage, +1); + break; case KEY_RIGHT: editFn(&edit, EditNext); + break; case KEY_SEND: windowScroll(ScrollAll, -1); + break; case KEY_SHOME: windowScroll(ScrollAll, +1); + break; case KEY_UP: windowScroll(ScrollOne, +1); + } +} + +static void keyCtrl(wchar_t ch) { + switch (ch ^ L'@') { + break; case L'?': editFn(&edit, EditDeletePrev); + break; case L'A': editFn(&edit, EditHead); + break; case L'B': editFn(&edit, EditPrev); + break; case L'C': raise(SIGINT); + break; case L'D': editFn(&edit, EditDeleteNext); + break; case L'E': editFn(&edit, EditTail); + break; case L'F': editFn(&edit, EditNext); + break; case L'H': editFn(&edit, EditDeletePrev); + break; case L'J': inputEnter(); + break; case L'K': editFn(&edit, EditDeleteTail); + break; case L'L': clearok(curscr, true); + break; case L'N': windowShow(windowNum() + 1); + break; case L'P': windowShow(windowNum() - 1); + break; case L'R': windowSearch(editString(&edit), -1); + break; case L'S': windowSearch(editString(&edit), +1); + break; case L'T': editFn(&edit, EditTranspose); + break; case L'U': editFn(&edit, EditDeleteHead); + break; case L'V': windowScroll(ScrollPage, -1); + break; case L'W': editFn(&edit, EditDeletePrevWord); + break; case L'Y': editFn(&edit, EditPaste); + } +} + +static void keyStyle(wchar_t ch) { + if (iswcntrl(ch)) ch = towlower(ch ^ L'@'); + char buf[8] = {0}; + enum Color color = Default; + switch (ch) { + break; case L'A': color = Gray; + break; case L'B': color = Blue; + break; case L'C': color = Cyan; + break; case L'G': color = Green; + break; case L'K': color = Black; + break; case L'M': color = Magenta; + break; case L'N': color = Brown; + break; case L'O': color = Orange; + break; case L'P': color = Pink; + break; case L'R': color = Red; + break; case L'W': color = White; + break; case L'Y': color = Yellow; + break; case L'b': buf[0] = B; + break; case L'c': buf[0] = C; + break; case L'i': buf[0] = I; + break; case L'o': buf[0] = O; + break; case L'r': buf[0] = R; + break; case L's': { + snprintf(buf, sizeof(buf), "%c%02d,%02d", C, Black, Black); + } + break; case L'u': buf[0] = U; + } + if (color != Default) { + snprintf(buf, sizeof(buf), "%c%02d", C, color); + } + for (char *ch = buf; *ch; ++ch) { + editInsert(&edit, *ch); + } +} + +static bool waiting; + +void inputWait(void) { + waiting = true; +} + +void inputRead(void) { + if (isendwin()) { + if (waiting) { + uiShow(); + flushinp(); + waiting = false; + } else { + return; + } + } + + wint_t ch; + static bool paste, style, literal; + for (int ret; ERR != (ret = wget_wch(uiInput, &ch));) { + bool spr = uiSpoilerReveal; + if (ret == KEY_CODE_YES && ch == KeyPasteOn) { + paste = true; + } else if (ret == KEY_CODE_YES && ch == KeyPasteOff) { + paste = false; + } else if (ret == KEY_CODE_YES && ch == KeyPasteManual) { + paste ^= true; + } else if (paste || literal) { + editInsert(&edit, ch); + } else if (ret == KEY_CODE_YES) { + keyCode(ch); + } else if (ch == (L'Z' ^ L'@')) { + style = true; + continue; + } else if (style && ch == (L'V' ^ L'@')) { + literal = true; + continue; + } else if (style) { + keyStyle(ch); + } else if (iswcntrl(ch)) { + keyCtrl(ch); + } else { + editInsert(&edit, ch); + } + style = false; + literal = false; + if (spr) { + uiSpoilerReveal = false; + windowUpdate(); + } + } + inputUpdate(); +} diff --git a/ui.c b/ui.c index 85b5a72..d24f7a7 100644 --- a/ui.c +++ b/ui.c @@ -54,7 +54,6 @@ #endif #include "chat.h" -#include "edit.h" // Annoying stuff from : #undef lines @@ -66,7 +65,7 @@ WINDOW *uiStatus; WINDOW *uiMain; -static WINDOW *input; +WINDOW *uiInput; static short colorPairs; @@ -101,52 +100,6 @@ static short colorPair(short fg, short bg) { return colorPairs++; } -#define ENUM_KEY \ - X(KeyCtrlLeft, "\33[1;5D", NULL) \ - X(KeyCtrlRight, "\33[1;5C", NULL) \ - X(KeyMeta0, "\0330", "\33)") \ - X(KeyMeta1, "\0331", "\33!") \ - X(KeyMeta2, "\0332", "\33@") \ - X(KeyMeta3, "\0333", "\33#") \ - X(KeyMeta4, "\0334", "\33$") \ - X(KeyMeta5, "\0335", "\33%") \ - X(KeyMeta6, "\0336", "\33^") \ - X(KeyMeta7, "\0337", "\33&") \ - X(KeyMeta8, "\0338", "\33*") \ - X(KeyMeta9, "\0339", "\33(") \ - X(KeyMetaA, "\33a", NULL) \ - X(KeyMetaB, "\33b", NULL) \ - X(KeyMetaD, "\33d", NULL) \ - X(KeyMetaF, "\33f", NULL) \ - X(KeyMetaL, "\33l", NULL) \ - X(KeyMetaM, "\33m", NULL) \ - X(KeyMetaN, "\33n", NULL) \ - X(KeyMetaP, "\33p", NULL) \ - X(KeyMetaQ, "\33q", NULL) \ - X(KeyMetaS, "\33s", NULL) \ - X(KeyMetaT, "\33t", NULL) \ - X(KeyMetaU, "\33u", NULL) \ - X(KeyMetaV, "\33v", NULL) \ - X(KeyMetaEnter, "\33\r", "\33\n") \ - X(KeyMetaGt, "\33>", "\33.") \ - X(KeyMetaLt, "\33<", "\33,") \ - X(KeyMetaEqual, "\33=", NULL) \ - X(KeyMetaMinus, "\33-", "\33_") \ - X(KeyMetaPlus, "\33+", NULL) \ - X(KeyMetaSlash, "\33/", "\33?") \ - X(KeyFocusIn, "\33[I", NULL) \ - X(KeyFocusOut, "\33[O", NULL) \ - X(KeyPasteOn, "\33[200~", NULL) \ - X(KeyPasteOff, "\33[201~", NULL) \ - X(KeyPasteManual, "\32p", "\32\20") - -enum { - KeyMax = KEY_MAX, -#define X(id, seq, alt) id, - ENUM_KEY -#undef X -}; - // XXX: Assuming terminals will be fine with these even if they're unsupported, // since they're "private" modes. static const char *FocusMode[2] = { "\33[?1004l", "\33[?1004h" }; @@ -158,7 +111,7 @@ static void errExit(void) { reset_shell_mode(); } -void uiInitEarly(void) { +void uiInit(void) { initscr(); cbreak(); noecho(); @@ -177,49 +130,20 @@ void uiInitEarly(void) { from_status_line = "\7"; } -#define X(id, seq, alt) define_key(seq, id); if (alt) define_key(alt, id); - ENUM_KEY -#undef X - uiStatus = newwin(StatusLines, COLS, 0, 0); if (!uiStatus) err(EX_OSERR, "newwin"); uiMain = newwin(MAIN_LINES, COLS, StatusLines, 0); if (!uiMain) err(EX_OSERR, "newwin"); - input = newpad(InputLines, InputCols); - if (!input) err(EX_OSERR, "newpad"); - keypad(input, true); - nodelay(input, true); + uiInput = newpad(InputLines, InputCols); + if (!uiInput) err(EX_OSERR, "newpad"); windowInit(); uiShow(); } -// Avoid disabling VINTR until main loop. -void uiInitLate(void) { - struct termios term; - int error = tcgetattr(STDOUT_FILENO, &term); - if (error) err(EX_OSERR, "tcgetattr"); - - // Gain use of C-q, C-s, C-c, C-z, C-y, C-v, C-o. - term.c_iflag &= ~IXON; - term.c_cc[VINTR] = _POSIX_VDISABLE; - term.c_cc[VSUSP] = _POSIX_VDISABLE; -#ifdef VDSUSP - term.c_cc[VDSUSP] = _POSIX_VDISABLE; -#endif - term.c_cc[VLNEXT] = _POSIX_VDISABLE; - term.c_cc[VDISCARD] = _POSIX_VDISABLE; - - error = tcsetattr(STDOUT_FILENO, TCSANOW, &term); - if (error) err(EX_OSERR, "tcsetattr"); - - def_prog_mode(); -} - static bool hidden = true; -static bool waiting; char uiTitle[TitleCap]; static char prevTitle[TitleCap]; @@ -229,9 +153,9 @@ void uiDraw(void) { wnoutrefresh(uiStatus); wnoutrefresh(uiMain); int y, x; - getyx(input, y, x); + getyx(uiInput, y, x); pnoutrefresh( - input, + uiInput, 0, (x + 1 > RIGHT ? x + 1 - RIGHT : 0), LINES - InputLines, 0, BOTTOM, RIGHT @@ -284,10 +208,10 @@ uint uiAttr(struct Style style) { return attr | colorAttr(Colors[style.fg]); } -static bool spoilerReveal; +bool uiSpoilerReveal; short uiPair(struct Style style) { - if (spoilerReveal && style.fg == style.bg) { + if (uiSpoilerReveal && style.fg == style.bg) { return colorPair(Colors[Default], Colors[style.bg]); } return colorPair(Colors[style.fg], Colors[style.bg]); @@ -312,10 +236,6 @@ void uiHide(void) { endwin(); } -void uiWait(void) { - waiting = true; -} - struct Util uiNotifyUtil; static void notify(uint id, const char *str) { if (self.restricted) return; @@ -361,283 +281,12 @@ void uiFormat( uiWrite(id, heat, time, buf); } -static void resize(void) { +void uiResize(void) { wclear(uiMain); wresize(uiMain, MAIN_LINES, COLS); windowResize(); } -static void inputAdd(struct Style reset, struct Style *style, const char *str) { - while (*str) { - const char *code = str; - size_t len = styleParse(style, &str); - wattr_set(input, A_BOLD | A_REVERSE, 0, NULL); - switch (*code) { - 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'); - break; case '\n': waddch(input, 'N'); - } - if (str - code > 1) waddnstr(input, &code[1], str - &code[1]); - if (str[0] == '\n') { - *style = reset; - str++; - len--; - } - size_t nl = strcspn(str, "\n"); - if (nl < len) len = nl; - wattr_set(input, uiAttr(*style), uiPair(*style), NULL); - waddnstr(input, str, len); - str += len; - } -} - -static char *inputStop( - struct Style reset, struct Style *style, - const char *str, char *stop -) { - char ch = *stop; - *stop = '\0'; - inputAdd(reset, style, str); - *stop = ch; - return stop; -} - -static struct Edit edit; - -void uiUpdate(void) { - char *buf = editString(&edit); - uint id = windowID(); - - const char *prefix = ""; - const char *prompt = self.nick; - const char *suffix = ""; - const char *skip = buf; - struct Style stylePrompt = { .fg = self.color, .bg = Default }; - struct Style styleInput = StyleDefault; - - size_t split = commandWillSplit(id, buf); - const char *privmsg = commandIsPrivmsg(id, buf); - const char *notice = commandIsNotice(id, buf); - const char *action = commandIsAction(id, buf); - if (privmsg) { - prefix = "<"; suffix = "> "; - skip = privmsg; - } else if (notice) { - prefix = "-"; suffix = "- "; - styleInput.fg = LightGray; - skip = notice; - } else if (action) { - prefix = "* "; suffix = " "; - stylePrompt.attr |= Italic; - styleInput.attr |= Italic; - skip = action; - } else if (id == Debug && buf[0] != '/') { - prompt = "<< "; - stylePrompt.fg = Gray; - } else { - prompt = ""; - } - if (skip > &buf[edit.mbs.pos]) { - prefix = prompt = suffix = ""; - skip = buf; - } - - int y, x; - wmove(input, 0, 0); - if (windowTimeEnable() && id != Network) { - whline(input, ' ', windowTime.width); - wmove(input, 0, windowTime.width); - } - wattr_set(input, uiAttr(stylePrompt), uiPair(stylePrompt), NULL); - waddstr(input, prefix); - waddstr(input, prompt); - waddstr(input, suffix); - getyx(input, y, x); - - int pos; - struct Style style = styleInput; - inputStop(styleInput, &style, skip, &buf[edit.mbs.pos]); - getyx(input, y, pos); - wmove(input, y, x); - - style = styleInput; - const char *ptr = skip; - if (split) { - ptr = inputStop(styleInput, &style, ptr, &buf[split]); - style = styleInput; - style.bg = Red; - } - inputAdd(styleInput, &style, ptr); - wclrtoeol(input); - wmove(input, y, pos); -} - -static void inputEnter(void) { - command(windowID(), editString(&edit)); - editFn(&edit, EditClear); -} - -static void keyCode(int code) { - switch (code) { - break; case KEY_RESIZE: resize(); - break; case KeyFocusIn: windowUnmark(); - break; case KeyFocusOut: windowMark(); - - break; case KeyMetaEnter: editInsert(&edit, L'\n'); - break; case KeyMetaEqual: windowToggleMute(); - break; case KeyMetaMinus: windowToggleThresh(-1); - break; case KeyMetaPlus: windowToggleThresh(+1); - break; case KeyMetaSlash: windowSwap(); - - break; case KeyMetaGt: windowScroll(ScrollAll, -1); - break; case KeyMetaLt: windowScroll(ScrollAll, +1); - - break; case KeyMeta0 ... KeyMeta9: windowShow(code - KeyMeta0); - break; case KeyMetaA: windowAuto(); - break; case KeyMetaB: editFn(&edit, EditPrevWord); - break; case KeyMetaD: editFn(&edit, EditDeleteNextWord); - break; case KeyMetaF: editFn(&edit, EditNextWord); - break; case KeyMetaL: windowBare(); - break; case KeyMetaM: uiWrite(windowID(), Warm, NULL, ""); - break; case KeyMetaN: windowScroll(ScrollHot, +1); - break; case KeyMetaP: windowScroll(ScrollHot, -1); - break; case KeyMetaQ: editFn(&edit, EditCollapse); - break; case KeyMetaS: spoilerReveal ^= true; windowUpdate(); - break; case KeyMetaT: windowToggleTime(); - break; case KeyMetaU: windowScroll(ScrollUnread, 0); - break; case KeyMetaV: windowScroll(ScrollPage, +1); - - break; case KeyCtrlLeft: editFn(&edit, EditPrevWord); - break; case KeyCtrlRight: editFn(&edit, EditNextWord); - - break; case KEY_BACKSPACE: editFn(&edit, EditDeletePrev); - break; case KEY_DC: editFn(&edit, EditDeleteNext); - break; case KEY_DOWN: windowScroll(ScrollOne, -1); - break; case KEY_END: editFn(&edit, EditTail); - break; case KEY_ENTER: inputEnter(); - break; case KEY_HOME: editFn(&edit, EditHead); - break; case KEY_LEFT: editFn(&edit, EditPrev); - break; case KEY_NPAGE: windowScroll(ScrollPage, -1); - break; case KEY_PPAGE: windowScroll(ScrollPage, +1); - break; case KEY_RIGHT: editFn(&edit, EditNext); - break; case KEY_SEND: windowScroll(ScrollAll, -1); - break; case KEY_SHOME: windowScroll(ScrollAll, +1); - break; case KEY_UP: windowScroll(ScrollOne, +1); - } -} - -static void keyCtrl(wchar_t ch) { - switch (ch ^ L'@') { - break; case L'?': editFn(&edit, EditDeletePrev); - break; case L'A': editFn(&edit, EditHead); - break; case L'B': editFn(&edit, EditPrev); - break; case L'C': raise(SIGINT); - break; case L'D': editFn(&edit, EditDeleteNext); - break; case L'E': editFn(&edit, EditTail); - break; case L'F': editFn(&edit, EditNext); - break; case L'H': editFn(&edit, EditDeletePrev); - break; case L'J': inputEnter(); - break; case L'K': editFn(&edit, EditDeleteTail); - break; case L'L': clearok(curscr, true); - break; case L'N': windowShow(windowNum() + 1); - break; case L'P': windowShow(windowNum() - 1); - break; case L'R': windowSearch(editString(&edit), -1); - break; case L'S': windowSearch(editString(&edit), +1); - break; case L'T': editFn(&edit, EditTranspose); - break; case L'U': editFn(&edit, EditDeleteHead); - break; case L'V': windowScroll(ScrollPage, -1); - break; case L'W': editFn(&edit, EditDeletePrevWord); - break; case L'Y': editFn(&edit, EditPaste); - } -} - -static void keyStyle(wchar_t ch) { - if (iswcntrl(ch)) ch = towlower(ch ^ L'@'); - char buf[8] = {0}; - enum Color color = Default; - switch (ch) { - break; case L'A': color = Gray; - break; case L'B': color = Blue; - break; case L'C': color = Cyan; - break; case L'G': color = Green; - break; case L'K': color = Black; - break; case L'M': color = Magenta; - break; case L'N': color = Brown; - break; case L'O': color = Orange; - break; case L'P': color = Pink; - break; case L'R': color = Red; - break; case L'W': color = White; - break; case L'Y': color = Yellow; - break; case L'b': buf[0] = B; - break; case L'c': buf[0] = C; - break; case L'i': buf[0] = I; - break; case L'o': buf[0] = O; - break; case L'r': buf[0] = R; - break; case L's': { - snprintf(buf, sizeof(buf), "%c%02d,%02d", C, Black, Black); - } - break; case L'u': buf[0] = U; - } - if (color != Default) { - snprintf(buf, sizeof(buf), "%c%02d", C, color); - } - for (char *ch = buf; *ch; ++ch) { - editInsert(&edit, *ch); - } -} - -void uiRead(void) { - if (hidden) { - if (waiting) { - uiShow(); - flushinp(); - waiting = false; - } else { - return; - } - } - - wint_t ch; - static bool paste, style, literal; - for (int ret; ERR != (ret = wget_wch(input, &ch));) { - bool spr = spoilerReveal; - if (ret == KEY_CODE_YES && ch == KeyPasteOn) { - paste = true; - } else if (ret == KEY_CODE_YES && ch == KeyPasteOff) { - paste = false; - } else if (ret == KEY_CODE_YES && ch == KeyPasteManual) { - paste ^= true; - } else if (paste || literal) { - editInsert(&edit, ch); - } else if (ret == KEY_CODE_YES) { - keyCode(ch); - } else if (ch == (L'Z' ^ L'@')) { - style = true; - continue; - } else if (style && ch == (L'V' ^ L'@')) { - literal = true; - continue; - } else if (style) { - keyStyle(ch); - } else if (iswcntrl(ch)) { - keyCtrl(ch); - } else { - editInsert(&edit, ch); - } - style = false; - literal = false; - if (spr) { - spoilerReveal = false; - windowUpdate(); - } - } - uiUpdate(); -} - static FILE *saveFile; static const uint64_t Signatures[] = { diff --git a/window.c b/window.c index ce3ec4d..0395542 100644 --- a/window.c +++ b/window.c @@ -273,7 +273,7 @@ void windowUpdate(void) { void windowBare(void) { uiHide(); - uiWait(); + inputWait(); const struct Window *window = windows[show]; const struct Line *line = bufferHard(window->buffer, windowBottom(window)); @@ -426,7 +426,7 @@ void windowShow(uint num) { user = num; unmark(windows[show]); mainUpdate(); - uiUpdate(); + inputUpdate(); } void windowAuto(void) { @@ -515,7 +515,7 @@ void windowToggleTime(void) { windows[show]->time ^= true; reflow(windows[show]); windowUpdate(); - uiUpdate(); + inputUpdate(); } void windowToggleThresh(int n) { -- cgit 1.4.1 From 143fa1b50965645dc0ebafecdd5db192f4454695 Mon Sep 17 00:00:00 2001 From: June McEnroe Date: Sat, 19 Feb 2022 20:37:48 -0500 Subject: Reimplement text macros --- README.7 | 2 +- chat.c | 1 + chat.h | 1 + input.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 1 deletion(-) (limited to 'chat.h') diff --git a/README.7 b/README.7 index dd0020d..32ee50f 100644 --- a/README.7 +++ b/README.7 @@ -132,7 +132,7 @@ $ make all .Pp Packagers are encouraged to patch in their own text macros in -.Pa edit.c . +.Pa input.c . . .Pp If installing diff --git a/chat.c b/chat.c index c4b256c..b085165 100644 --- a/chat.c +++ b/chat.c @@ -372,6 +372,7 @@ int main(int argc, char *argv[]) { set(&self.nick, "*"); commandCompleteAdd(); + inputCompleteAdd(); ircConfig(insecure, trust, cert, priv); diff --git a/chat.h b/chat.h index 1bf1edd..0de930d 100644 --- a/chat.h +++ b/chat.h @@ -332,6 +332,7 @@ void inputInit(void); void inputWait(void); void inputUpdate(void); void inputRead(void); +void inputCompleteAdd(void); enum Scroll { ScrollOne, diff --git a/input.c b/input.c index 963bd4e..45913cc 100644 --- a/input.c +++ b/input.c @@ -27,6 +27,7 @@ #define _XOPEN_SOURCE_EXTENDED +#include #include #include #include @@ -34,6 +35,7 @@ #include #include #include +#include #include "chat.h" #include "edit.h" @@ -221,6 +223,54 @@ void inputUpdate(void) { wmove(uiInput, y, pos); } +static const struct { + const wchar_t *name; + const wchar_t *string; +} Macros[] = { + { L"\\banhammer", L"▬▬▬▬▬▬▬▋ Ò╭╮Ó" }, + { L"\\bear", L"ʕっ•ᴥ•ʔっ" }, + { L"\\blush", L"(˶′◡‵˶)" }, + { L"\\com", L"\0038,4\2 ☭ " }, + { L"\\cool", L"(⌐■_■)" }, + { L"\\flip", L"(╯°□°)╯︵ ┻━┻" }, + { L"\\gary", L"ᕕ( ᐛ )ᕗ" }, + { L"\\hug", L"(っ・∀・)っ" }, + { L"\\lenny", L"( ͡° ͜ʖ ͡°)" }, + { L"\\look", L"ಠ_ಠ" }, + { L"\\shrug", L"¯\\_(ツ)_/¯" }, + { L"\\unflip", L"┬─┬ノ(º_ºノ)" }, + { L"\\wave", L"ヾ(^∇^)" }, +}; + +void inputCompleteAdd(void) { + char mbs[256]; + for (size_t i = 0; i < ARRAY_LEN(Macros); ++i) { + size_t n = wcstombs(mbs, Macros[i].name, sizeof(mbs)); + assert(n != (size_t)-1); + completeAdd(None, mbs, Default); + } +} + +static int macroExpand(struct Edit *e) { + size_t macro = e->pos; + while (macro && e->buf[macro] != L'\\') macro--; + if (macro == e->pos) return 0; + for (size_t i = 0; i < ARRAY_LEN(Macros); ++i) { + if (wcslen(Macros[i].name) != e->pos - macro) continue; + if (wcsncmp(Macros[i].name, &e->buf[macro], e->pos - macro)) continue; + if (wcstombs(NULL, Macros[i].string, 0) == (size_t)-1) continue; + size_t expand = wcslen(Macros[i].string); + int error = 0 + || editDelete(e, false, macro, e->pos - macro) + || editReserve(e, macro, expand); + if (error) return error; + wcsncpy(&e->buf[macro], Macros[i].string, expand); + e->pos = macro + expand; + break; + } + return 0; +} + static void inputEnter(void) { command(windowID(), editString(&edit)); editFn(&edit, EditClear); @@ -296,6 +346,7 @@ static void keyCtrl(wchar_t ch) { break; case L'U': editFn(&edit, EditDeleteHead); break; case L'V': windowScroll(ScrollPage, -1); break; case L'W': editFn(&edit, EditDeletePrevWord); + break; case L'X': macroExpand(&edit); break; case L'Y': editFn(&edit, EditPaste); } } -- cgit 1.4.1 From a281bdc5e1700e25022536a5482b1fb41ece4219 Mon Sep 17 00:00:00 2001 From: June McEnroe Date: Sun, 20 Feb 2022 14:49:17 -0500 Subject: Show indicator in status when window has pending input --- chat.h | 1 + input.c | 4 ++++ window.c | 10 ++++++---- 3 files changed, 11 insertions(+), 4 deletions(-) (limited to 'chat.h') diff --git a/chat.h b/chat.h index 0de930d..17334fa 100644 --- a/chat.h +++ b/chat.h @@ -331,6 +331,7 @@ int uiSave(void); void inputInit(void); void inputWait(void); void inputUpdate(void); +bool inputPending(uint id); void inputRead(void); void inputCompleteAdd(void); diff --git a/input.c b/input.c index b578d17..ad6d533 100644 --- a/input.c +++ b/input.c @@ -227,6 +227,10 @@ void inputUpdate(void) { wmove(uiInput, y, pos); } +bool inputPending(uint id) { + return edits[id].len; +} + static const struct { const wchar_t *name; const wchar_t *string; diff --git a/window.c b/window.c index 0395542..ee0911f 100644 --- a/window.c +++ b/window.c @@ -164,7 +164,7 @@ static void statusUpdate(void) { wmove(uiStatus, 0, 0); for (uint num = 0; num < count; ++num) { const struct Window *window = windows[num]; - if (num != show && !window->scroll) { + if (num != show && !window->scroll && !inputPending(window->id)) { if (window->heat < Warm) continue; if (window->mute && window->heat < Hot) continue; } @@ -181,15 +181,17 @@ static void statusUpdate(void) { ); if (window->mark && window->unreadWarm) { ptr = seprintf( - ptr, end, "\3%d+%d\3%d%s", + ptr, end, "\3%d+%d\3%d ", (window->heat > Warm ? White : idColors[window->id]), - window->unreadWarm, idColors[window->id], - (window->scroll ? "" : " ") + window->unreadWarm, idColors[window->id] ); } if (window->scroll) { ptr = seprintf(ptr, end, "~%d ", window->scroll); } + if (num != show && inputPending(window->id)) { + ptr = seprintf(ptr, end, "@ "); + } if (styleAdd(uiStatus, StyleDefault, buf) < 0) break; } wclrtoeol(uiStatus); -- cgit 1.4.1 From 78ff548b934bb265437e3afeea78942c601691e6 Mon Sep 17 00:00:00 2001 From: June McEnroe Date: Sun, 20 Feb 2022 16:57:11 -0500 Subject: Save input buffer contents --- chat.h | 2 ++ input.c | 39 +++++++++++++++++++++++++++++++++++++++ ui.c | 7 +++++-- 3 files changed, 46 insertions(+), 2 deletions(-) (limited to 'chat.h') diff --git a/chat.h b/chat.h index 17334fa..1c46f00 100644 --- a/chat.h +++ b/chat.h @@ -334,6 +334,8 @@ void inputUpdate(void); bool inputPending(uint id); void inputRead(void); void inputCompleteAdd(void); +int inputSave(FILE *file); +void inputLoad(FILE *file, size_t version); enum Scroll { ScrollOne, diff --git a/input.c b/input.c index b6c51a2..de8cecf 100644 --- a/input.c +++ b/input.c @@ -586,3 +586,42 @@ void inputRead(void) { } inputUpdate(); } + +static int writeString(FILE *file, const char *str) { + return (fwrite(str, strlen(str) + 1, 1, file) ? 0 : -1); +} + +int inputSave(FILE *file) { + int error; + for (uint id = 0; id < IDCap; ++id) { + if (!edits[id].len) continue; + char *ptr = editString(&edits[id], &buf, &cap, NULL); + if (!ptr) return -1; + error = 0 + || writeString(file, idNames[id]) + || writeString(file, ptr); + if (error) return error; + } + return writeString(file, ""); +} + +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 inputLoad(FILE *file, size_t version) { + if (version < 8) return; + while (0 < readString(file, &buf, &cap) && buf[0]) { + uint id = idFor(buf); + readString(file, &buf, &cap); + size_t max = strlen(buf); + int error = editReserve(&edits[id], 0, max); + if (error) err(EX_OSERR, "editReserve"); + size_t len = mbstowcs(edits[id].buf, buf, max); + assert(len != (size_t)-1); + edits[id].len = len; + edits[id].pos = len; + } +} diff --git a/ui.c b/ui.c index 67c6349..64bd6ce 100644 --- a/ui.c +++ b/ui.c @@ -288,7 +288,8 @@ static const uint64_t Signatures[] = { 0x6C72696774616305, // no URLs 0x6C72696774616306, // no thresh 0x6C72696774616307, // no window time - 0x6C72696774616308, + 0x6C72696774616308, // no input + 0x6C72696774616309, }; static size_t signatureVersion(uint64_t signature) { @@ -305,9 +306,10 @@ static int writeUint64(FILE *file, uint64_t u) { int uiSave(void) { return 0 || ftruncate(fileno(saveFile), 0) - || writeUint64(saveFile, Signatures[7]) + || writeUint64(saveFile, Signatures[8]) || writeUint64(saveFile, self.pos) || windowSave(saveFile) + || inputSave(saveFile) || urlSave(saveFile) || fclose(saveFile); } @@ -350,5 +352,6 @@ void uiLoad(const char *name) { self.pos = readUint64(saveFile); } windowLoad(saveFile, version); + inputLoad(saveFile, version); urlLoad(saveFile, version); } -- cgit 1.4.1