diff --git a/Makefile b/Makefile index 4f70876..3ca0008 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ LDLIBS = -lcursesw -ltls OBJS += chat.o OBJS += edit.o OBJS += event.o +OBJS += format.o OBJS += handle.o OBJS += input.o OBJS += irc.o diff --git a/README b/README index a50bf31..8737aea 100644 --- a/README +++ b/README @@ -9,7 +9,8 @@ This software requires LibreSSL and targets FreeBSD and Darwin. handle.c Incoming command handling input.c Input command handling irc.c TLS client connection - ui.c Curses UI and mIRC formatting + format.c IRC formatting + ui.c Curses UI term.c Terminal features unsupported by curses edit.c Line editing tab.c Tab-complete diff --git a/chat.h b/chat.h index 9bd1d3c..50f5c87 100644 --- a/chat.h +++ b/chat.h @@ -23,6 +23,9 @@ #include #include +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + #define err(...) do { uiHide(); err(__VA_ARGS__); } while (0) #define errx(...) do { uiHide(); errx(__VA_ARGS__); } while (0) @@ -80,6 +83,19 @@ enum { IRCUnderline = 037, }; +struct Format { + const wchar_t *str; + size_t len; + bool bold; + bool italic; + bool underline; + bool reverse; + int fg; + int bg; +}; +void formatReset(struct Format *format); +bool formatParse(struct Format *format, const wchar_t *stop); + void handle(char *line); void input(struct Tag tag, char *line); void inputTab(void); diff --git a/format.c b/format.c new file mode 100644 index 0000000..800388b --- /dev/null +++ b/format.c @@ -0,0 +1,89 @@ +/* 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 "chat.h" + +void formatReset(struct Format *format) { + format->bold = false; + format->italic = false; + format->underline = false; + format->reverse = false; + format->fg = -1; + format->bg = -1; +} + +static void parseColor(struct Format *format) { + size_t len = MIN(wcsspn(format->str, L"0123456789"), 2); + if (!len) { + format->fg = -1; + format->bg = -1; + return; + } + format->fg = 0; + for (size_t i = 0; i < len; ++i) { + format->fg *= 10; + format->fg += format->str[i] - L'0'; + } + if (format->fg > IRCLightGray) format->fg = -1; + format->str = &format->str[len]; + + len = 0; + if (format->str[0] == L',') { + len = MIN(wcsspn(&format->str[1], L"0123456789"), 2); + } + if (!len) return; + format->bg = 0; + for (size_t i = 0; i < len; ++i) { + format->bg *= 10; + format->bg += format->str[1 + i] - L'0'; + } + if (format->bg > IRCLightGray) format->bg = -1; + format->str = &format->str[1 + len]; +} + +static const wchar_t Stops[] = { + L' ', + IRCBold, IRCColor, IRCReverse, IRCReset, IRCItalic, IRCUnderline, + L'\0', +}; + +bool formatParse(struct Format *format, const wchar_t *stop) { + format->str += format->len; + if (!format->str[0]) return false; + + switch (format->str[0]) { + break; case IRCBold: format->str++; format->bold ^= true; + break; case IRCItalic: format->str++; format->italic ^= true; + break; case IRCUnderline: format->str++; format->underline ^= true; + break; case IRCReverse: format->str++; format->reverse ^= true; + break; case IRCColor: format->str++; parseColor(format); + break; case IRCReset: format->str++; formatReset(format); + } + + if (format->str[0] == L' ') { + format->len = 1 + wcscspn(&format->str[1], Stops); + } else { + format->len = wcscspn(format->str, Stops); + } + if (stop && stop > format->str && stop < &format->str[format->len]) { + format->len = stop - format->str; + } + return true; +} diff --git a/ui.c b/ui.c index 879fea3..90a75af 100644 --- a/ui.c +++ b/ui.c @@ -34,9 +34,6 @@ #include "chat.h" #undef uiFmt -#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#define MAX(a, b) ((a) > (b) ? (a) : (b)) - #define CTRL(c) ((c) ^ 0100) static void colorInit(void) { @@ -242,81 +239,35 @@ static const short IRCColors[] = { [IRCLightGray] = 0 + COLOR_WHITE, }; -static const wchar_t *parseColor(short *pair, const wchar_t *str) { - short fg = 0; - size_t fgLen = MIN(wcsspn(str, L"0123456789"), 2); - if (!fgLen) { *pair = -1; return str; } - for (size_t i = 0; i < fgLen; ++i) { - fg = fg * 10 + (str[i] - L'0'); - } - str = &str[fgLen]; - - short bg = 0; - size_t bgLen = 0; - if (str[0] == L',') bgLen = MIN(wcsspn(&str[1], L"0123456789"), 2); - for (size_t i = 0; i < bgLen; ++i) { - bg = bg * 10 + (str[1 + i] - L'0'); - } - if (bgLen) str = &str[1 + bgLen]; - - if (*pair == -1) *pair = 0; - *pair = (*pair & 0xF0) | IRCColors[fg & 0x0F]; - if (bgLen) *pair = (*pair & 0x0F) | (IRCColors[bg & 0x0F] << 4); - - return str; -} - -static int wordWrap(WINDOW *win, const wchar_t *str) { - size_t len = wcscspn(str, L" "); - size_t width = 1; - for (size_t i = 0; i < len; ++i) { - if (iswprint(str[i])) width += wcwidth(str[i]); - } - - int _, x, xMax; - getyx(win, _, x); - getmaxyx(win, _, xMax); - - if (width >= (size_t)(xMax - x)) { - waddch(win, '\n'); - return 1; - } else { - waddch(win, ' '); - return 0; - } -} - -static const wchar_t IRCCodes[] = { - L' ', - IRCBold, - IRCColor, - IRCReverse, - IRCReset, - IRCItalic, - IRCUnderline, - L'\0', -}; - static int addIRC(WINDOW *win, const wchar_t *str) { - attr_t attr = A_NORMAL; - short pair = -1; int lines = 0; - for (;;) { - size_t cc = wcscspn(str, IRCCodes); - wattr_set(win, attr | attr8(pair), 1 + pair8(pair), NULL); - waddnwstr(win, str, cc); - if (!str[cc]) break; - - str = &str[cc]; - switch (*str++) { - break; case L' ': lines += wordWrap(win, str); - break; case IRCBold: attr ^= A_BOLD; - break; case IRCItalic: attr ^= A_ITALIC; - break; case IRCUnderline: attr ^= A_UNDERLINE; - break; case IRCReverse: attr ^= A_REVERSE; - break; case IRCColor: str = parseColor(&pair, str); - break; case IRCReset: attr = A_NORMAL; pair = -1; + struct Format format = { .str = str }; + formatReset(&format); + while (formatParse(&format, NULL)) { + int _, x, xMax; + getyx(win, _, x); + getmaxyx(win, _, xMax); + if (xMax - x - 1 < wcswidth(format.str, format.len)) { + if (format.str[0] == L' ') { + format.str++; + format.len--; + } + waddch(win, '\n'); + lines++; } + + attr_t attr = A_NORMAL; + if (format.bold) attr |= A_BOLD; + if (format.italic) attr |= A_ITALIC; + if (format.underline) attr |= A_UNDERLINE; + if (format.reverse) attr |= A_REVERSE; + + short pair = -1; + if (format.fg >= 0) pair = IRCColors[format.fg]; + if (format.bg >= 0) pair |= IRCColors[format.bg] << 4; + + wattr_set(win, attr | attr8(pair), 1 + pair8(pair), NULL); + waddnwstr(win, format.str, format.len); } return lines; }