From eac0f83efa3d6b1db2715c21d60b170a546ebc0a Mon Sep 17 00:00:00 2001 From: Curtis McEnroe Date: Wed, 8 Aug 2018 16:59:26 -0400 Subject: [PATCH] Factor out line editing to edit.c --- Makefile | 2 +- README | 3 +- chat.h | 14 ++++ edit.c | 193 +++++++++++++++++++++++++++++++++++++++++++++++++ pls.c | 2 + tab.c | 6 +- ui.c | 213 ++++++++++--------------------------------------------- 7 files changed, 254 insertions(+), 179 deletions(-) create mode 100644 edit.c diff --git a/Makefile b/Makefile index 38c14bf..de85e55 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ CFLAGS += -Wall -Wextra -Wpedantic CFLAGS += -I/usr/local/include LDFLAGS += -L/usr/local/lib LDLIBS = -lcursesw -ltls -OBJS = chat.o handle.o input.o irc.o pls.o tab.o ui.o +OBJS = chat.o edit.o handle.o input.o irc.o pls.o tab.o ui.o all: tags chat diff --git a/README b/README index 59079c1..db03f11 100644 --- a/README +++ b/README @@ -4,7 +4,8 @@ This software targets FreeBSD and requires LibreSSL. chat.h Shared state and function prototypes chat.c Command line parsing and poll loop - ui.c Curses UI, mIRC formatting, line editing + ui.c Curses UI and mIRC formatting + edit.c Line editing irc.c TLS client connection input.c Input command handling handle.c Incoming command handling diff --git a/chat.h b/chat.h index 09f0d2c..9507f12 100644 --- a/chat.h +++ b/chat.h @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -31,6 +32,15 @@ struct { char *chan; } chat; +enum { + IRC_BOLD = 002, + IRC_COLOR = 003, + IRC_REVERSE = 026, + IRC_RESET = 017, + IRC_ITALIC = 035, + IRC_UNDERLINE = 037, +}; + int ircConnect(const char *host, const char *port, const char *webPass); void ircRead(void); void ircWrite(const char *ptr, size_t len); @@ -58,6 +68,10 @@ void uiFmt(const wchar_t *format, ...); } while(0) #endif +const wchar_t *editHead(void); +const wchar_t *editTail(void); +bool edit(bool meta, bool ctrl, wchar_t ch); + void handle(char *line); void input(char *line); diff --git a/edit.c b/edit.c new file mode 100644 index 0000000..9073f92 --- /dev/null +++ b/edit.c @@ -0,0 +1,193 @@ +/* Copyright (C) 2018 Curtis McEnroe + * + * 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 + * 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. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include "chat.h" + +enum { BUF_LEN = 512 }; +static struct { + wchar_t buf[BUF_LEN]; + wchar_t *ptr; + wchar_t *end; + wchar_t *tab; +} line = { + .ptr = line.buf, + .end = line.buf, +}; + +static wchar_t tail; +const wchar_t *editHead(void) { + tail = *line.ptr; + *line.ptr = L'\0'; + return line.buf; +} +const wchar_t *editTail(void) { + *line.ptr = tail; + *line.end = L'\0'; + return line.ptr; +} + +static void left(void) { + if (line.ptr > line.buf) line.ptr--; +} +static void right(void) { + if (line.ptr < line.end) line.ptr++; +} +static void home(void) { + line.ptr = line.buf; +} +static void end(void) { + line.ptr = line.end; +} +static void kill(void) { + line.end = line.ptr; +} + +static void backspace(void) { + if (line.ptr == line.buf) return; + if (line.ptr != line.end) { + wmemmove(line.ptr - 1, line.ptr, line.end - line.ptr); + } + line.ptr--; + line.end--; +} +static void delete(void) { + if (line.ptr == line.end) return; + right(); + backspace(); +} + +static void insert(wchar_t ch) { + if (line.end == &line.buf[BUF_LEN - 1]) return; + if (line.ptr != line.end) { + wmemmove(line.ptr + 1, line.ptr, line.end - line.ptr); + } + *line.ptr++ = ch; + line.end++; +} + +static void enter(void) { + if (line.end == line.buf) return; + *line.end = L'\0'; + char *str = awcstombs(line.buf); + if (!str) err(EX_DATAERR, "awcstombs"); + input(str); + free(str); + line.ptr = line.buf; + line.end = line.buf; +} + +static char *prefix; +static void complete(void) { + if (!line.tab) { + editHead(); + line.tab = wcsrchr(line.buf, L' '); + line.tab = (line.tab ? &line.tab[1] : line.buf); + prefix = awcstombs(line.tab); + if (!prefix) err(EX_DATAERR, "awcstombs"); + editTail(); + } + + const char *next = tabNext(prefix); + if (!next) return; + + wchar_t *wcs = ambstowcs(next); + if (!wcs) err(EX_DATAERR, "ambstowcs"); + + size_t i = 0; + for (; wcs[i] && line.ptr > &line.tab[i]; ++i) { + line.tab[i] = wcs[i]; + } + while (line.ptr > &line.tab[i]) { + backspace(); + } + for (; wcs[i]; ++i) { + insert(wcs[i]); + } + free(wcs); + + size_t pos = line.tab - line.buf; + if (!pos) { + insert(L':'); + } else if (pos >= 2) { + if (line.buf[pos - 2] == L':' || line.buf[pos - 2] == L',') { + line.buf[pos - 2] = L','; + insert(L':'); + } + } + insert(L' '); +} + +static void accept(void) { + if (!line.tab) return; + line.tab = NULL; + free(prefix); + tabAccept(); +} +static void reject(void) { + if (!line.tab) return; + line.tab = NULL; + free(prefix); + tabReject(); +} + +static bool editMeta(wchar_t ch) { + (void)ch; + return false; +} + +static bool editCtrl(wchar_t ch) { + switch (ch) { + break; case L'B': reject(); left(); + break; case L'F': reject(); right(); + break; case L'A': reject(); home(); + break; case L'E': reject(); end(); + break; case L'D': reject(); delete(); + break; case L'K': reject(); kill(); + + break; case L'C': accept(); insert(IRC_COLOR); + break; case L'N': accept(); insert(IRC_RESET); + break; case L'O': accept(); insert(IRC_BOLD); + break; case L'R': accept(); insert(IRC_COLOR); + break; case L'T': accept(); insert(IRC_ITALIC); + break; case L'V': accept(); insert(IRC_REVERSE); + + break; default: return false; + } + return true; +} + +bool edit(bool meta, bool ctrl, wchar_t ch) { + if (meta) return editMeta(ch); + if (ctrl) return editCtrl(ch); + switch (ch) { + break; case L'\t': complete(); + break; case L'\b': reject(); backspace(); + break; case L'\n': accept(); enter(); + break; default: { + if (!iswprint(ch)) return false; + accept(); + insert(ch); + } + } + return true; +} diff --git a/pls.c b/pls.c index 27ede4c..01df654 100644 --- a/pls.c +++ b/pls.c @@ -20,6 +20,8 @@ #include #include +#include "chat.h" + wchar_t *ambstowcs(const char *src) { size_t len = mbsrtowcs(NULL, &src, 0, NULL); if (len == (size_t)-1) return NULL; diff --git a/tab.c b/tab.c index 45670ad..a9ddfe5 100644 --- a/tab.c +++ b/tab.c @@ -34,7 +34,7 @@ static void prepend(struct Entry *entry) { head = entry; } -static void remove(struct Entry *entry) { +static void unlink(struct Entry *entry) { if (entry->prev) entry->prev->next = entry->next; if (entry->next) entry->next->prev = entry->prev; if (head == entry) head = entry->next; @@ -42,7 +42,7 @@ static void remove(struct Entry *entry) { static void touch(struct Entry *entry) { if (head == entry) return; - remove(entry); + unlink(entry); prepend(entry); } @@ -70,7 +70,7 @@ static struct Entry *match; void tabRemove(const char *word) { for (struct Entry *entry = head; entry; entry = entry->next) { if (strcmp(entry->word, word)) continue; - remove(entry); + unlink(entry); if (match == entry) match = entry->next; free(entry->word); free(entry); diff --git a/ui.c b/ui.c index 937fa63..72996c1 100644 --- a/ui.c +++ b/ui.c @@ -34,7 +34,8 @@ #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) -#define CTRL(c) ((c) & 037) +#define CTRL(c) ((c) & 037) +#define UNCTRL(c) ((c) + '@') #ifndef A_ITALIC #define A_ITALIC A_NORMAL @@ -235,14 +236,6 @@ static void wordWrap(WINDOW *win, const wchar_t *str) { } } -enum { - IRC_BOLD = 0x02, - IRC_COLOR = 0x03, - IRC_REVERSE = 0x16, - IRC_RESET = 0x0F, - IRC_ITALIC = 0x1D, - IRC_UNDERLINE = 0x1F, -}; static const wchar_t IRC_CODES[] = { L' ', IRC_BOLD, @@ -324,199 +317,71 @@ static void logDown(void) { ui.scroll = MIN(ui.scroll + logHeight() / 2, LOG_LINES); } -enum { BUF_LEN = 512 }; -static struct { - wchar_t buf[BUF_LEN]; - wchar_t *ptr; - wchar_t *end; -} line = { .ptr = line.buf, .end = line.buf }; - -static void left(void) { - if (line.ptr > line.buf) line.ptr--; -} -static void right(void) { - if (line.ptr < line.end) line.ptr++; -} -static void home(void) { - line.ptr = line.buf; -} -static void end(void) { - line.ptr = line.end; -} - -static void kill(void) { - line.end = line.ptr; -} - -static void insert(wchar_t ch) { - if (line.end == &line.buf[BUF_LEN - 1]) return; - if (line.ptr != line.end) { - wmemmove(line.ptr + 1, line.ptr, line.end - line.ptr); - } - *line.ptr++ = ch; - line.end++; -} - -static void backspace(void) { - if (line.ptr == line.buf) return; - if (line.ptr != line.end) { - wmemmove(line.ptr - 1, line.ptr, line.end - line.ptr); - } - line.ptr--; - line.end--; -} - -static void delete(void) { - if (line.ptr == line.end) return; - right(); - backspace(); -} - -static void enter(void) { - if (line.end == line.buf) return; - *line.end = L'\0'; - char *str = awcstombs(line.buf); - if (!str) err(EX_DATAERR, "awcstombs"); - input(str); - free(str); - line.ptr = line.buf; - line.end = line.buf; -} - -static struct { - wchar_t *word; - char *prefix; -} tab; - -static void accept(void) { - if (!tab.word) return; - tab.word = NULL; - free(tab.prefix); - tabAccept(); -} - -static void reject(void) { - if (!tab.word) return; - tab.word = NULL; - free(tab.prefix); - tabReject(); -} - -static void complete(void) { - if (!tab.word) { - wchar_t ch = *line.ptr; - *line.ptr = L'\0'; - tab.word = wcsrchr(line.buf, L' '); - tab.word = (tab.word ? &tab.word[1] : line.buf); - tab.prefix = awcstombs(tab.word); - if (!tab.prefix) err(EX_DATAERR, "awcstombs"); - *line.ptr = ch; - } - - const char *complete = tabNext(tab.prefix); - if (!complete) { - reject(); - return; - } - - wchar_t *wcs = ambstowcs(complete); - if (!wcs) err(EX_DATAERR, "ambstowcs"); - - size_t i; - for (i = 0; wcs[i] && line.ptr > &tab.word[i]; ++i) { - tab.word[i] = wcs[i]; - } - while (line.ptr > &tab.word[i]) { - backspace(); - } - for (; wcs[i]; ++i) { - insert(wcs[i]); - } - free(wcs); - - if (tab.word == line.buf) insert(L':'); - insert(L' '); -} - -static void keyChar(wint_t ch) { +static bool keyChar(wint_t ch) { static bool esc, csi; - - if (csi) { - csi = false; - if (ch == L'O') logMark(); - return; - } - csi = (esc && ch == L'['); - esc = (ch == L'\33'); - if (csi) return; - + bool update = false; switch (ch) { break; case CTRL('L'): uiRedraw(); - break; case CTRL('B'): reject(); left(); - break; case CTRL('F'): reject(); right(); - break; case CTRL('A'): reject(); home(); - break; case CTRL('E'): reject(); end(); - break; case CTRL('D'): reject(); delete(); - break; case CTRL('K'): reject(); kill(); - break; case L'\b': reject(); backspace(); - break; case L'\177': reject(); backspace(); - break; case L'\t': complete(); - break; case L'\n': accept(); enter(); - break; case CTRL('C'): accept(); insert(IRC_COLOR); - break; case CTRL('N'): accept(); insert(IRC_RESET); - break; case CTRL('O'): accept(); insert(IRC_BOLD); - break; case CTRL('R'): accept(); insert(IRC_COLOR); - break; case CTRL('T'): accept(); insert(IRC_ITALIC); - break; case CTRL('U'): accept(); insert(IRC_UNDERLINE); - break; case CTRL('V'): accept(); insert(IRC_REVERSE); + break; case CTRL('['): esc = true; return false; + break; case L'\b': update = edit(esc, false, L'\b'); + break; case L'\177': update = edit(esc, false, L'\b'); + break; case L'\t': update = edit(esc, false, L'\t'); + break; case L'\n': update = edit(esc, false, L'\n'); break; default: { - if (iswprint(ch)) { - accept(); - insert(ch); + if (esc && ch == L'[') { + csi = true; + return false; + } else if (csi) { + if (ch == L'O') logMark(); + } else if (iswcntrl(ch)) { + update = edit(esc, true, UNCTRL(ch)); + } else { + update = edit(esc, false, ch); } } } + esc = false; + csi = false; + return update; } -static void keyCode(wint_t ch) { +static bool keyCode(wint_t ch) { switch (ch) { break; case KEY_RESIZE: uiResize(); break; case KEY_PPAGE: logUp(); break; case KEY_NPAGE: logDown(); - break; case KEY_LEFT: left(); - break; case KEY_RIGHT: right(); - break; case KEY_HOME: home(); - break; case KEY_END: end(); - break; case KEY_BACKSPACE: backspace(); - break; case KEY_DC: delete(); - break; case KEY_ENTER: enter(); + break; case KEY_LEFT: return edit(false, true, 'B'); + break; case KEY_RIGHT: return edit(false, true, 'F'); + break; case KEY_HOME: return edit(false, true, 'A'); + break; case KEY_END: return edit(false, true, 'E'); + break; case KEY_DC: return edit(false, true, 'D'); + break; case KEY_BACKSPACE: return edit(false, false, '\b'); + break; case KEY_ENTER: return edit(false, false, '\n'); } + return false; } void uiRead(void) { + bool update = false; int ret; wint_t ch; while (ERR != (ret = wget_wch(ui.input, &ch))) { if (ret == KEY_CODE_YES) { - keyCode(ch); + update |= keyCode(ch); } else { - keyChar(ch); + update |= keyChar(ch); } } + if (!update) return; wmove(ui.input, 1, 0); + addIRC(ui.input, editHead()); - ch = *line.ptr; - *line.ptr = L'\0'; - addIRC(ui.input, line.buf); - *line.ptr = ch; + int y, x; + getyx(ui.input, y, x); - int _, x; - getyx(ui.input, _, x); - - *line.end = L'\0'; - addIRC(ui.input, line.ptr); + addIRC(ui.input, editTail()); wclrtoeol(ui.input); - wmove(ui.input, 1, x); + wmove(ui.input, y, x); }