summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--README3
-rw-r--r--chat.h14
-rw-r--r--edit.c193
-rw-r--r--pls.c2
-rw-r--r--tab.c6
-rw-r--r--ui.c213
7 files changed, 254 insertions, 179 deletions
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 <stdarg.h>
#include <stdbool.h>
+#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>
@@ -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 <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
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <sysexits.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#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 <stdlib.h>
#include <wchar.h>
+#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 _, x;
- getyx(ui.input, _, x);
+ int y, x;
+ getyx(ui.input, y, 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);
}