From 920179b142cfe2b3e8877cae57c6b9d4aad9eca3 Mon Sep 17 00:00:00 2001 From: Alcor Date: Sat, 14 Mar 2026 20:01:20 +0100 Subject: [PATCH] Configurable macro definitions --- catgirl.1 | 9 ++++++ chat.c | 4 +-- chat.h | 2 +- input.c | 90 ++++++++++++++++++++++++++++++++----------------------- 4 files changed, 65 insertions(+), 40 deletions(-) diff --git a/catgirl.1 b/catgirl.1 index 9f8ceeb..704bd2f 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -12,6 +12,7 @@ .Op Fl C Ar copy .Op Fl H Ar hash .Op Fl I Ar highlight +.Op Fl M Ar macros .Op Fl N Ar notify .Op Fl O Ar open .Op Fl S Ar bind @@ -171,6 +172,14 @@ joins your favourite channel: .Pp .Dl highlight crush!*@* join #channel . +.It Fl M Ar filename | Cm macros Ar filename +Read text macro definitions from +.Cm filename , +which should be a two-column +text file mapping macro names to their +respective expansions. A macro name may only +contain alphanumeric characters and underscores. +. .It Fl N Ar util | Cm notify Ar util Send notifications using a utility. Subsequent diff --git a/chat.c b/chat.c index daefcec..a9f8cd9 100644 --- a/chat.c +++ b/chat.c @@ -244,6 +244,7 @@ int main(int argc, char *argv[]) { { .val = 'C', .name = "copy", required_argument }, { .val = 'H', .name = "hash", required_argument }, { .val = 'I', .name = "highlight", required_argument }, + { .val = 'M', .name = "macros", required_argument }, { .val = 'N', .name = "notify", required_argument }, { .val = 'O', .name = "open", required_argument }, { .val = 'R', .name = "restrict", no_argument }, @@ -284,6 +285,7 @@ int main(int argc, char *argv[]) { break; case 'C': utilPush(&urlCopyUtil, optarg); break; case 'H': parseHash(optarg); break; case 'I': filterAdd(Hot, optarg); + break; case 'M': inputCompletion(configOpen(optarg, "r")); break; case 'N': utilPush(&uiNotifyUtil, optarg); break; case 'O': utilPush(&urlOpenUtil, optarg); break; case 'R': self.restricted = true; @@ -365,8 +367,6 @@ int main(int argc, char *argv[]) { set(&network.name, host); set(&self.nick, "*"); - inputCompletion(); - ircConfig(insecure, trust, cert, priv); uiInit(); diff --git a/chat.h b/chat.h index 958f598..b8b9fae 100644 --- a/chat.h +++ b/chat.h @@ -346,7 +346,7 @@ void inputWait(void); void inputUpdate(void); bool inputPending(uint id); void inputRead(void); -void inputCompletion(void); +void inputCompletion(FILE *file); int inputSave(FILE *file); void inputLoad(FILE *file, size_t version); diff --git a/input.c b/input.c index 7e1f9c1..2738fa3 100644 --- a/input.c +++ b/input.c @@ -241,51 +241,67 @@ bool inputPending(uint id) { return edits[id].len; } -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"ヾ(^∇^)" }, -}; +enum { NMACROS = 256 }; +static struct Macro { const wchar_t *name, *string; } Macros[NMACROS]; +struct MacroQuery { const wchar_t *str; size_t n; }; +static size_t MacrosTop; + +static int MacroCompare(const void *p, const void *q) { + const struct Macro *ma = p, *mb = q; + return wcscmp(ma->name, mb->name); +} + +static int MacroQueryCmp(const void *p, const void *q) { + const struct MacroQuery *query = p; + const struct Macro *macro = q; + return wcsncmp(query->str, macro->name, query->n); +} + +static void wrtrim(wchar_t *s) { + wchar_t *end = s + wcslen(s) - 1; + while (end >= s && iswspace(*end)) { + *end = L'\0'; + --end; + } +} -void inputCompletion(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); - completePush(None, mbs, Default); +void inputCompletion(FILE *file) { + if (!file) return; + wchar_t line[512]; + while (MacrosTop < NMACROS && fgetws(line, sizeof(line), file)) { + if (*line == L'\n' || *line == L'\r') continue; + wchar_t wcvalue[256] = { L'\0' }, wckey[257] = { L'\\' }; + if (swscanf(line, L" %255l[a-zA-Z0-9_] %255l[^\n]", &wckey[1], wcvalue) == 2) { + char key[257]; + size_t n = wcstombs(key, wckey, sizeof(key)); + if (n == (size_t)-1 || n >= sizeof(key)) continue; + completePush(None, key, Default); + Macros[MacrosTop].name = wcsdup(wckey); + if (!Macros[MacrosTop].name) err(1, "wcsdup"); + Macros[MacrosTop].string = (wrtrim(wcvalue), wcsdup(wcvalue)); + if (!Macros[MacrosTop].string) err(1, "wcsdup"); + ++MacrosTop; + } } + fclose(file); + qsort(Macros, MacrosTop, sizeof(struct Macro), MacroCompare); } 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; - } + const struct MacroQuery query = { &e->buf[macro], e->pos - macro }; + const struct Macro *match = bsearch(&query, Macros, MacrosTop, sizeof(struct Macro), MacroQueryCmp); + if (!match) return 0; + if (wcstombs(NULL, match->string, 0) == (size_t)-1) return 0; + size_t expand = wcslen(match->string); + int error = 0 + || editDelete(e, false, macro, e->pos - macro) + || editReserve(e, macro, expand); + if (error) return error; + wcsncpy(&e->buf[macro], match->string, expand); + e->pos = macro + expand; return 0; } -- 2.47.3