diff options
Diffstat (limited to 'input.c')
-rw-r--r-- | input.c | 747 |
1 files changed, 550 insertions, 197 deletions
diff --git a/input.c b/input.c index 8be8eaf..6b33b93 100644 --- a/input.c +++ b/input.c @@ -1,276 +1,629 @@ -/* Copyright (C) 2018 C. McEnroe <june@causal.agency> +/* Copyright (C) 2020 June McEnroe <june@causal.agency> * * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by + * 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 Affero General Public License for more details. + * GNU General Public License for more details. * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * 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. */ -#include <ctype.h> +#define _XOPEN_SOURCE_EXTENDED + +#include <assert.h> +#include <curses.h> #include <err.h> +#include <signal.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sysexits.h> +#include <termios.h> +#include <unistd.h> +#include <wchar.h> +#include <wctype.h> #include "chat.h" +#include "edit.h" -static void privmsg(struct Tag tag, bool action, const char *mesg) { - char *line; - int send; - asprintf( - &line, ":%s!%s %nPRIVMSG %s :%s%s%s", - self.nick, self.user, &send, tag.name, - (action ? "\1ACTION " : ""), mesg, (action ? "\1" : "") - ); - if (!line) err(EX_OSERR, "asprintf"); - ircFmt("%s\r\n", &line[send]); - handle(line); - free(line); -} +#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") -typedef void Handler(struct Tag tag, char *params); +enum { + KeyMax = KEY_MAX, +#define X(id, seq, alt) id, + ENUM_KEY +#undef X +}; -static void inputJoin(struct Tag tag, char *params) { - char *chan = strsep(¶ms, " "); - char *key = strsep(¶ms, " "); - if (key) { - ircFmt("JOIN %s %s\r\n", chan, key); - } else { - ircFmt("JOIN %s\r\n", chan ? chan : tag.name); - } -} +static struct Edit cut; +static struct Edit edits[IDCap]; -static void inputList(struct Tag tag, char *params) { - (void)tag; - char *chan = strsep(¶ms, " "); - if (chan) { - ircFmt("LIST %s\r\n", chan); - } else { - ircFmt("LIST\r\n"); +void inputInit(void) { + for (size_t i = 0; i < ARRAY_LEN(edits); ++i) { + edits[i].cut = &cut; } -} -static void inputMe(struct Tag tag, char *params) { - privmsg(tag, true, params ? params : ""); -} + struct termios term; + int error = tcgetattr(STDOUT_FILENO, &term); + if (error) err(EX_OSERR, "tcgetattr"); -static void inputNick(struct Tag tag, char *params) { - char *nick = strsep(¶ms, " "); - if (!nick) { - uiLog(tag, UIHot, L"/nick requires a name"); - return; - } - ircFmt("NICK %s\r\n", nick); -} + // 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 -static void inputPart(struct Tag tag, char *params) { - ircFmt("PART %s :%s\r\n", tag.name, params ? params : "Goodbye"); + keypad(uiInput, true); + nodelay(uiInput, true); } -static void inputQuery(struct Tag tag, char *params) { - char *nick = strsep(¶ms, " "); - if (!nick) { - uiLog(tag, UIHot, L"/query requires a nick"); - return; +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; } - tabTouch(TagNone, nick); - uiShowTag(tagFor(nick)); - logReplay(tagFor(nick)); } -static void inputQuit(struct Tag tag, char *params) { - (void)tag; - ircQuit(params ? params : "Goodbye"); +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 void inputQuote(struct Tag tag, char *params) { - (void)tag; - if (params) ircFmt("%s\r\n", params); -} +static size_t cap; +static char *buf; + +void inputUpdate(void) { + uint id = windowID(); + + size_t pos = 0; + const char *ptr = editString(&edits[id], &buf, &cap, &pos); + if (!ptr) err(EX_OSERR, "editString"); -static void inputTopic(struct Tag tag, char *params) { - if (params) { - ircFmt("TOPIC %s :%s\r\n", tag.name, params); + 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 { - ircFmt("TOPIC %s\r\n", tag.name); + prompt = ""; + } + if (skip > &buf[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 posx; + struct Style style = styleInput; + inputStop(styleInput, &style, skip, &buf[pos]); + getyx(uiInput, y, posx); + wmove(uiInput, y, x); + + ptr = skip; + style = styleInput; + if (split) { + ptr = inputStop(styleInput, &style, ptr, &buf[split]); + style = styleInput; + style.bg = Red; } + inputAdd(styleInput, &style, ptr); + wclrtoeol(uiInput); + wmove(uiInput, y, posx); } -static void inputWho(struct Tag tag, char *params) { - (void)params; - ircFmt("WHO :%s\r\n", tag.name); +bool inputPending(uint id) { + return edits[id].len; } -static void inputWhois(struct Tag tag, char *params) { - char *nick = strsep(¶ms, " "); - if (!nick) { - uiLog(tag, UIHot, L"/whois requires a nick"); - return; +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 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); } - ircFmt("WHOIS %s\r\n", nick); } -static void inputZNC(struct Tag tag, char *params) { - (void)tag; - ircFmt("ZNC %s\r\n", params ? params : ""); +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 inputClose(struct Tag tag, char *params) { - (void)params; - uiCloseTag(tag); - tabRemove(TagNone, tag.name); +static struct { + uint id; + char *pre; + size_t pos; + size_t len; + bool suffix; + struct Cursor curs; +} tab; + +static void tabAccept(void) { + completeAccept(&tab.curs); + tab.len = 0; } -static void inputMan(struct Tag tag, char *params) { - (void)tag; - (void)params; - eventWait((const char *[]) { "man", "1", "catgirl", NULL }); +static void tabReject(void) { + completeReject(&tab.curs); + tab.len = 0; } -static void inputMove(struct Tag tag, char *params) { - char *num = strsep(¶ms, " "); - if (!num) { - uiLog(tag, UIHot, L"/move requires a number"); - return; +static int tabComplete(struct Edit *e, uint id) { + if (tab.len && id != tab.id) { + tabAccept(); } - uiMoveTag(tag, strtol(num, NULL, 0), num[0] == '+' || num[0] == '-'); -} -static void inputOpen(struct Tag tag, char *params) { - if (params && !isdigit(params[0])) { - urlOpenMatch(tag, params); + if (!tab.len) { + tab.id = id; + tab.pos = e->pos; + while (tab.pos && !iswspace(e->buf[tab.pos-1])) tab.pos--; + tab.len = e->pos - tab.pos; + if (!tab.len) return 0; + + size_t cap = tab.len * MB_CUR_MAX + 1; + char *buf = realloc(tab.pre, cap); + if (!buf) return -1; + tab.pre = buf; + + const wchar_t *ptr = &e->buf[tab.pos]; + size_t n = wcsnrtombs(tab.pre, &ptr, tab.len, cap-1, NULL); + if (n == (size_t)-1) return -1; + tab.pre[n] = '\0'; + tab.suffix = true; + } + + const char *comp = completePrefix(&tab.curs, id, tab.pre); + if (!comp) { + comp = completePrefix(&tab.curs, id, tab.pre); + tab.suffix ^= true; + } + if (!comp) { + tab.len = 0; + return 0; + } + + size_t cap = strlen(comp) + 1; + wchar_t *wcs = malloc(sizeof(*wcs) * cap); + if (!wcs) return -1; + + size_t n = mbstowcs(wcs, comp, cap); + assert(n != (size_t)-1); + + bool colon = (tab.len >= 2 && e->buf[tab.pos + tab.len - 2] == L':'); + + int error = editDelete(e, false, tab.pos, tab.len); + if (error) goto fail; + + tab.len = n; + if (wcs[0] == L'\\' || wcschr(wcs, L' ')) { + error = editReserve(e, tab.pos, tab.len); + if (error) goto fail; + } else if (wcs[0] != L'/' && tab.suffix && (!tab.pos || colon)) { + tab.len += 2; + error = editReserve(e, tab.pos, tab.len); + if (error) goto fail; + e->buf[tab.pos + n + 0] = L':'; + e->buf[tab.pos + n + 1] = L' '; + } else if (tab.suffix && tab.pos >= 2 && e->buf[tab.pos - 2] == L':') { + tab.len += 2; + error = editReserve(e, tab.pos, tab.len); + if (error) goto fail; + e->buf[tab.pos - 2] = L','; + e->buf[tab.pos + n + 0] = L':'; + e->buf[tab.pos + n + 1] = L' '; } else { - size_t at = (params ? strtoul(strsep(¶ms, "-,"), NULL, 0) : 1); - size_t to = (params ? strtoul(params, NULL, 0) : at); - urlOpenRange(tag, at - 1, to); + tab.len++; + error = editReserve(e, tab.pos, tab.len); + if (error) goto fail; + if (!tab.suffix && tab.pos >= 2 && e->buf[tab.pos - 2] == L',') { + e->buf[tab.pos - 2] = L':'; + } + e->buf[tab.pos + n] = L' '; } + wmemcpy(&e->buf[tab.pos], wcs, n); + e->pos = tab.pos + tab.len; + free(wcs); + return 0; + +fail: + free(wcs); + return -1; } -static void inputRaw(struct Tag tag, char *params) { - (void)tag; - (void)params; - self.raw ^= true; - uiFmt( - TagRaw, UIWarm, "\3%d%s\3 %s raw mode!", - colorGen(self.user), self.nick, (self.raw ? "engages" : "disengages") - ); +static void inputEnter(void) { + uint id = windowID(); + char *cmd = editString(&edits[id], &buf, &cap, NULL); + if (!cmd) err(EX_OSERR, "editString"); + + tabAccept(); + editFn(&edits[id], EditClear); + command(id, cmd); } -static void inputURL(struct Tag tag, char *params) { - (void)params; - urlList(tag); +static void keyCode(int code) { + int error = 0; + struct Edit *edit = &edits[windowID()]; + switch (code) { + break; case KEY_RESIZE: uiResize(); + break; case KeyFocusIn: windowUnmark(); + break; case KeyFocusOut: windowMark(); + + break; case KeyMetaEnter: error = 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: error = editFn(edit, EditPrevWord); + break; case KeyMetaD: error = editFn(edit, EditDeleteNextWord); + break; case KeyMetaF: error = 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: error = 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: error = editFn(edit, EditPrevWord); + break; case KeyCtrlRight: error = editFn(edit, EditNextWord); + + break; case KEY_BACKSPACE: error = editFn(edit, EditDeletePrev); + break; case KEY_DC: error = editFn(edit, EditDeleteNext); + break; case KEY_DOWN: windowScroll(ScrollOne, -1); + break; case KEY_END: error = editFn(edit, EditTail); + break; case KEY_ENTER: inputEnter(); + break; case KEY_HOME: error = editFn(edit, EditHead); + break; case KEY_LEFT: error = editFn(edit, EditPrev); + break; case KEY_NPAGE: windowScroll(ScrollPage, -1); + break; case KEY_PPAGE: windowScroll(ScrollPage, +1); + break; case KEY_RIGHT: error = editFn(edit, EditNext); + break; case KEY_SEND: windowScroll(ScrollAll, -1); + break; case KEY_SHOME: windowScroll(ScrollAll, +1); + break; case KEY_UP: windowScroll(ScrollOne, +1); + } + if (error) err(EX_OSERR, "editFn"); } -static void inputWindow(struct Tag tag, char *params) { - char *word = strsep(¶ms, " "); - if (!word) { - uiLog(tag, UIHot, L"/window requires a name or number"); - return; +static void keyCtrl(wchar_t ch) { + int error = 0; + struct Edit *edit = &edits[windowID()]; + switch (ch ^ L'@') { + break; case L'?': error = editFn(edit, EditDeletePrev); + break; case L'A': error = editFn(edit, EditHead); + break; case L'B': error = editFn(edit, EditPrev); + break; case L'C': raise(SIGINT); + break; case L'D': error = editFn(edit, EditDeleteNext); + break; case L'E': error = editFn(edit, EditTail); + break; case L'F': error = editFn(edit, EditNext); + break; case L'H': error = editFn(edit, EditDeletePrev); + break; case L'I': error = tabComplete(edit, windowID()); + break; case L'J': inputEnter(); + break; case L'K': error = editFn(edit, EditDeleteTail); + break; case L'L': clearok(curscr, true); wrefresh(curscr); + break; case L'N': windowShow(windowNum() + 1); + break; case L'P': windowShow(windowNum() - 1); + break; case L'R': windowSearch(editString(edit, &buf, &cap, NULL), -1); + break; case L'S': windowSearch(editString(edit, &buf, &cap, NULL), +1); + break; case L'T': error = editFn(edit, EditTranspose); + break; case L'U': error = editFn(edit, EditDeleteHead); + break; case L'V': windowScroll(ScrollPage, -1); + break; case L'W': error = editFn(edit, EditDeletePrevWord); + break; case L'X': error = macroExpand(edit); tabAccept(); + break; case L'Y': error = editFn(edit, EditPaste); } - bool relative = (word[0] == '+' || word[0] == '-'); - char *trail; - int num = strtol(word, &trail, 0); - if (!trail[0]) { - uiShowNum(num, relative); - } else { - struct Tag name = tagFind(word); - if (name.id != TagNone.id) { - uiShowTag(name); - } else { - uiFmt(tag, UIHot, "No window for %s", word); + if (error) err(EX_OSERR, "editFn"); +} + +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); + } + struct Edit *edit = &edits[windowID()]; + for (char *ch = buf; *ch; ++ch) { + int error = editInsert(edit, *ch); + if (error) err(EX_OSERR, "editInsert"); } } -static const struct { - const char *command; - Handler *handler; - bool limit; -} Commands[] = { - { "/close", .handler = inputClose }, - { "/help", .handler = inputMan }, - { "/join", .handler = inputJoin, .limit = true }, - { "/list", .handler = inputList }, - { "/man", .handler = inputMan }, - { "/me", .handler = inputMe }, - { "/move", .handler = inputMove }, - { "/names", .handler = inputWho }, - { "/nick", .handler = inputNick }, - { "/open", .handler = inputOpen }, - { "/part", .handler = inputPart }, - { "/query", .handler = inputQuery, .limit = true }, - { "/quit", .handler = inputQuit }, - { "/quote", .handler = inputQuote, .limit = true }, - { "/raw", .handler = inputRaw, .limit = true }, - { "/topic", .handler = inputTopic }, - { "/url", .handler = inputURL }, - { "/who", .handler = inputWho }, - { "/whois", .handler = inputWhois }, - { "/window", .handler = inputWindow }, - { "/znc", .handler = inputZNC }, -}; -static const size_t CommandsLen = sizeof(Commands) / sizeof(Commands[0]); +static bool waiting; -void inputTab(void) { - for (size_t i = 0; i < CommandsLen; ++i) { - tabTouch(TagNone, Commands[i].command); - } +void inputWait(void) { + waiting = true; } -void input(struct Tag tag, char *input) { - bool slash = (input[0] == '/'); - if (slash) { - char *space = strchr(&input[1], ' '); - char *extra = strchr(&input[1], '/'); - if (extra && (!space || extra < space)) slash = false; +void inputRead(void) { + if (isendwin()) { + if (waiting) { + uiShow(); + flushinp(); + waiting = false; + } else { + return; + } } - if (!slash) { - if (tag.id == TagRaw.id) { - ircFmt("%s\r\n", input); - } else if (tag.id != TagStatus.id) { - privmsg(tag, false, input); + wint_t ch; + static bool paste, style, literal; + for (int ret; ERR != (ret = wget_wch(uiInput, &ch));) { + bool tabbing = false; + size_t pos = edits[tab.id].pos; + 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) { + int error = editInsert(&edits[windowID()], ch); + if (error) err(EX_OSERR, "editInsert"); + } 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)) { + tabbing = (ch == (L'I' ^ L'@')); + keyCtrl(ch); + } else { + int error = editInsert(&edits[windowID()], ch); + if (error) err(EX_OSERR, "editInsert"); } - return; - } + style = false; + literal = false; - char *word = strsep(&input, " "); - if (input && !input[0]) input = NULL; + if (!tabbing) { + if (edits[tab.id].pos > pos) { + tabAccept(); + } else if (edits[tab.id].pos < pos) { + tabReject(); + } + } - char *trail; - strtol(&word[1], &trail, 0); - if (!trail[0]) { - inputWindow(tag, &word[1]); - return; + if (spr) { + uiSpoilerReveal = false; + windowUpdate(); + } } + inputUpdate(); +} - const char *command = word; - const char *uniq = tabNext(TagNone, command); - if (uniq && tabNext(TagNone, command) == uniq) { - command = uniq; - tabAccept(); - } else { - tabReject(); +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, ""); +} - for (size_t i = 0; i < CommandsLen; ++i) { - if (strcasecmp(command, Commands[i].command)) continue; - if (self.limit && Commands[i].limit) { - uiFmt(tag, UIHot, "%s isn't available in restricted mode", command); - return; - } - Commands[i].handler(tag, input); - return; +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; } - uiFmt(tag, UIHot, "%s isn't a recognized command", command); } |