diff options
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | README | 3 | ||||
| -rw-r--r-- | chat.h | 14 | ||||
| -rw-r--r-- | edit.c | 193 | ||||
| -rw-r--r-- | pls.c | 2 | ||||
| -rw-r--r-- | tab.c | 6 | ||||
| -rw-r--r-- | ui.c | 213 | 
7 files changed, 254 insertions, 179 deletions
| @@ -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 @@ -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 @@ -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); @@ -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; +} @@ -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; @@ -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); @@ -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);  } | 
