summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--README9
-rw-r--r--chat.c43
-rw-r--r--chat.h104
-rw-r--r--edit.c147
-rw-r--r--handle.c163
-rw-r--r--input.c75
-rw-r--r--irc.c13
-rw-r--r--tab.c35
-rw-r--r--tag.c77
-rw-r--r--ui.c225
-rw-r--r--url.c50
12 files changed, 595 insertions, 348 deletions
diff --git a/Makefile b/Makefile
index 57369d6..bf7b011 100644
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,7 @@ CFLAGS += -Wall -Wextra -Wpedantic
CFLAGS += -I/usr/local/include -I/usr/local/opt/libressl/include
LDFLAGS += -L/usr/local/lib -L/usr/local/opt/libressl/lib
LDLIBS = -lcursesw -ltls
-OBJS = chat.o edit.o handle.o input.o irc.o pls.o tab.o ui.o url.o
+OBJS = chat.o edit.o handle.o input.o irc.o pls.o tab.o tag.o ui.o url.o
all: tags chat
diff --git a/README b/README
index 8fa4ad7..9a93b9d 100644
--- a/README
+++ b/README
@@ -3,12 +3,13 @@ Simple IRC client for use over anonymous SSH.
This software requires LibreSSL and targets FreeBSD and Darwin.
chat.h Shared state and function prototypes
- chat.c Command line parsing and poll loop
+ chat.c Command line parsing and event loop
+ tag.c Tag (channel, query) management
+ handle.c Incoming command handling
+ input.c Input command handling
+ irc.c TLS client connection
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
tab.c Tab-complete
url.c URL detection
pls.c Functions which should not have to be written
diff --git a/chat.c b/chat.c
index 332cfd6..c85a1fe 100644
--- a/chat.c
+++ b/chat.c
@@ -29,6 +29,22 @@
#include "chat.h"
+void selfNick(const char *nick) {
+ free(self.nick);
+ self.nick = strdup(nick);
+ if (!self.nick) err(EX_OSERR, "strdup");
+}
+void selfUser(const char *user) {
+ free(self.user);
+ self.user = strdup(user);
+ if (!self.user) err(EX_OSERR, "strdup");
+}
+void selfJoin(const char *join) {
+ free(self.join);
+ self.join = strdup(join);
+ if (!self.join) err(EX_OSERR, "strdup");
+}
+
static union {
struct {
struct pollfd ui;
@@ -44,7 +60,7 @@ static union {
void spawn(char *const argv[]) {
if (fds.pipe.events) {
- uiLog(L"spawn: existing pipe");
+ uiLog(TAG_DEFAULT, L"spawn: existing pipe");
return;
}
@@ -77,7 +93,7 @@ static void pipeRead(void) {
if (len) {
buf[len] = '\0';
len = strcspn(buf, "\n");
- uiFmt("%.*s", (int)len, buf);
+ uiFmt(TAG_DEFAULT, "%.*s", (int)len, buf);
} else {
close(fds.pipe.fd);
fds.pipe.events = 0;
@@ -108,15 +124,15 @@ static void sigchld(int sig) {
pid_t pid = wait(&status);
if (pid < 0) err(EX_OSERR, "wait");
if (WIFEXITED(status) && WEXITSTATUS(status)) {
- uiFmt("spawn: exit %d", WEXITSTATUS(status));
+ uiFmt(TAG_DEFAULT, "spawn: exit %d", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
- uiFmt("spawn: signal %d", WTERMSIG(status));
+ uiFmt(TAG_DEFAULT, "spawn: signal %d", WTERMSIG(status));
}
}
static void sigint(int sig) {
(void)sig;
- input("/quit");
+ input(TAG_DEFAULT, "/quit");
uiExit();
exit(EX_OK);
}
@@ -129,7 +145,7 @@ static char *prompt(const char *prompt) {
fflush(stdout);
ssize_t len = getline(&line, &cap, stdin);
- if (ferror(stdin)) err(EX_IOERR, "getline");
+ //if (ferror(stdin)) err(EX_IOERR, "getline");
if (feof(stdin)) exit(EX_OK);
if (len < 2) continue;
@@ -149,25 +165,24 @@ int main(int argc, char *argv[]) {
switch (opt) {
break; case 'W': webirc = optarg;
break; case 'h': host = strdup(optarg);
- break; case 'j': chat.join = strdup(optarg);
- break; case 'n': chat.nick = strdup(optarg);
+ break; case 'j': selfJoin(optarg);
+ break; case 'n': selfNick(optarg);
break; case 'p': port = optarg;
- break; case 'u': chat.user = strdup(optarg);
- break; case 'v': chat.verbose = true;
+ break; case 'u': selfUser(optarg);
+ break; case 'v': self.verbose = true;
break; case 'w': pass = optarg;
break; default: return EX_USAGE;
}
}
if (!host) host = prompt("Host: ");
- if (!chat.join) chat.join = prompt("Join: ");
- if (!chat.nick) chat.nick = prompt("Name: ");
- if (!chat.user) chat.user = strdup(chat.nick);
+ if (!self.nick) self.nick = prompt("Name: ");
+ if (!self.user) selfUser(self.nick);
inputTab();
uiInit();
- uiLog(L"Traveling...");
+ uiLog(TAG_DEFAULT, L"Traveling...");
uiDraw();
fds.irc.fd = ircConnect(host, port, pass, webirc);
diff --git a/chat.h b/chat.h
index 9a1b855..9219334 100644
--- a/chat.h
+++ b/chat.h
@@ -30,18 +30,23 @@ struct {
char *nick;
char *user;
char *join;
-} chat;
+} self;
-void spawn(char *const argv[]);
+void selfNick(const char *nick);
+void selfUser(const char *user);
+void selfJoin(const char *join);
-int ircConnect(
- const char *host, const char *port, const char *pass, const char *webPass
-);
-void ircRead(void);
-void ircWrite(const char *ptr, size_t len);
+struct Tag {
+ size_t id;
+ const char *name;
+};
-__attribute__((format(printf, 1, 2)))
-void ircFmt(const char *format, ...);
+enum { TAGS_LEN = 256 };
+const struct Tag TAG_ALL;
+const struct Tag TAG_DEFAULT;
+struct Tag tagFor(const char *name);
+struct Tag tagName(const char *name);
+struct Tag tagNum(size_t num);
enum {
IRC_BOLD = 002,
@@ -52,47 +57,72 @@ enum {
IRC_UNDERLINE = 037,
};
+void handle(char *line);
+void input(struct Tag tag, char *line);
+void inputTab(void);
+
+int ircConnect(
+ const char *host, const char *port, const char *pass, const char *webPass
+);
+void ircRead(void);
+void ircWrite(const char *ptr, size_t len);
+void ircFmt(const char *format, ...) __attribute__((format(printf, 1, 2)));
+
void uiInit(void);
void uiHide(void);
void uiExit(void);
void uiDraw(void);
void uiBeep(void);
void uiRead(void);
-void uiTopic(const wchar_t *topic);
-void uiTopicStr(const char *topic);
-void uiLog(const wchar_t *line);
-void uiFmt(const wchar_t *format, ...);
-
-// HACK: clang won't check wchar_t *format strings.
-#ifdef NDEBUG
-#define uiFmt(format, ...) uiFmt(L##format, __VA_ARGS__)
-#else
-#define uiFmt(format, ...) do { \
- snprintf(NULL, 0, format, __VA_ARGS__); \
- uiFmt(L##format, __VA_ARGS__); \
-} while(0)
-#endif
-
+void uiFocus(struct Tag tag);
+void uiTopic(struct Tag tag, const char *topic);
+void uiLog(struct Tag tag, const wchar_t *line);
+void uiFmt(struct Tag tag, const wchar_t *format, ...);
+
+enum Edit {
+ EDIT_LEFT,
+ EDIT_RIGHT,
+ EDIT_HOME,
+ EDIT_END,
+ EDIT_BACK_WORD,
+ EDIT_FORE_WORD,
+ EDIT_INSERT,
+ EDIT_BACKSPACE,
+ EDIT_DELETE,
+ EDIT_KILL_BACK_WORD,
+ EDIT_KILL_FORE_WORD,
+ EDIT_KILL_LINE,
+ EDIT_COMPLETE,
+ EDIT_ENTER,
+};
+void edit(struct Tag tag, enum Edit op, wchar_t ch);
const wchar_t *editHead(void);
const wchar_t *editTail(void);
-bool edit(bool meta, bool ctrl, wchar_t ch);
-
-void handle(char *line);
-
-void inputTab(void);
-void input(char *line);
-
-void urlScan(const char *str);
-void urlList(void);
-void urlOpen(size_t i);
-void tabTouch(const char *word);
-void tabRemove(const char *word);
+void tabTouch(struct Tag tag, const char *word);
+void tabRemove(struct Tag tag, const char *word);
+void tabClear(struct Tag tag);
void tabReplace(const char *prev, const char *next);
-const char *tabNext(const char *prefix);
+const char *tabNext(struct Tag tag, const char *prefix);
void tabAccept(void);
void tabReject(void);
+void urlScan(struct Tag tag, const char *str);
+void urlList(struct Tag tag);
+void urlOpen(struct Tag tag, size_t fromEnd);
+
+void spawn(char *const argv[]);
+
wchar_t *ambstowcs(const char *src);
char *awcstombs(const wchar_t *src);
int vaswprintf(wchar_t **ret, const wchar_t *format, va_list ap);
+
+// HACK: clang won't check wchar_t *format strings.
+#ifdef NDEBUG
+#define uiFmt(tag, format, ...) uiFmt(tag, L##format, __VA_ARGS__)
+#else
+#define uiFmt(tag, format, ...) do { \
+ snprintf(NULL, 0, format, __VA_ARGS__); \
+ uiFmt(tag, L##format, __VA_ARGS__); \
+} while(0)
+#endif
diff --git a/edit.c b/edit.c
index dde3396..8db92fc 100644
--- a/edit.c
+++ b/edit.c
@@ -34,6 +34,7 @@ static struct {
.end = line.buf,
};
+// XXX: editTail must always be called after editHead.
static wchar_t tail;
const wchar_t *editHead(void) {
tail = *line.ptr;
@@ -41,8 +42,9 @@ const wchar_t *editHead(void) {
return line.buf;
}
const wchar_t *editTail(void) {
- *line.ptr = tail;
+ if (tail) *line.ptr = tail;
*line.end = L'\0';
+ tail = L'\0';
return line.ptr;
}
@@ -52,25 +54,19 @@ static void left(void) {
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 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 backWord(void) {
+ left();
+ editHead();
+ wchar_t *word = wcsrchr(line.buf, ' ');
+ editTail();
+ line.ptr = (word ? &word[1] : line.buf);
}
-static void delete(void) {
- if (line.ptr == line.end) return;
+static void foreWord(void) {
right();
- backspace();
+ editTail();
+ wchar_t *word = wcschr(line.ptr, ' ');
+ line.ptr = (word ? word : line.end);
}
static void insert(wchar_t ch) {
@@ -81,31 +77,18 @@ static void insert(wchar_t ch) {
*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 void backWord(void) {
- left();
- editHead();
- wchar_t *word = wcsrchr(line.buf, ' ');
- editTail();
- line.ptr = (word ? &word[1] : line.buf);
+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 foreWord(void) {
+static void delete(void) {
+ if (line.ptr == line.end) return;
right();
- editHead();
- editTail();
- wchar_t *word = wcschr(line.ptr, ' ');
- line.ptr = (word ? word : line.end);
+ backspace();
}
static void killBackWord(void) {
@@ -121,12 +104,9 @@ static void killForeWord(void) {
line.end -= line.ptr - from;
line.ptr = from;
}
-static void killLine(void) {
- line.end = line.ptr;
-}
static char *prefix;
-static void complete(void) {
+static void complete(struct Tag tag) {
if (!line.tab) {
editHead();
line.tab = wcsrchr(line.buf, L' ');
@@ -136,7 +116,7 @@ static void complete(void) {
editTail();
}
- const char *next = tabNext(prefix);
+ const char *next = tabNext(tag, prefix);
if (!next) return;
wchar_t *wcs = ambstowcs(next);
@@ -179,52 +159,37 @@ static void reject(void) {
tabReject();
}
-static bool editMeta(wchar_t ch) {
- switch (ch) {
- break; case L'b': reject(); backWord();
- break; case L'f': reject(); foreWord();
- break; case L'\b': reject(); killBackWord();
- break; case L'd': reject(); killForeWord();
+static void enter(struct Tag tag) {
+ if (line.end == line.buf) return;
+ editTail();
+ char *str = awcstombs(line.buf);
+ if (!str) err(EX_DATAERR, "awcstombs");
+ input(tag, str);
+ free(str);
+ line.ptr = line.buf;
+ line.end = line.buf;
+}
- break; default: return false;
- }
- return true;
-}
-
-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'W': reject(); killBackWord();
- break; case L'K': reject(); killLine();
-
- 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);
- }
+void edit(struct Tag tag, enum Edit op, wchar_t ch) {
+ switch (op) {
+ break; case EDIT_LEFT: reject(); left();
+ break; case EDIT_RIGHT: reject(); right();
+ break; case EDIT_HOME: reject(); line.ptr = line.buf;
+ break; case EDIT_END: reject(); line.ptr = line.end;
+
+ break; case EDIT_BACK_WORD: reject(); backWord();
+ break; case EDIT_FORE_WORD: reject(); foreWord();
+
+ break; case EDIT_INSERT: accept(); insert(ch);
+ break; case EDIT_BACKSPACE: reject(); backspace();
+ break; case EDIT_DELETE: reject(); delete();
+
+ break; case EDIT_KILL_BACK_WORD: reject(); killBackWord();
+ break; case EDIT_KILL_FORE_WORD: reject(); killForeWord();
+ break; case EDIT_KILL_LINE: reject(); line.end = line.ptr;
+
+ break; case EDIT_COMPLETE: complete(tag);
+
+ break; case EDIT_ENTER: accept(); enter(tag);
}
- return true;
}
diff --git a/handle.c b/handle.c
index be41828..faf44aa 100644
--- a/handle.c
+++ b/handle.c
@@ -74,6 +74,16 @@ static void shift(
va_end(ap);
}
+static bool isSelf(const char *nick, const char *user) {
+ if (!user) return false;
+ if (!strcmp(user, self.user)) return true;
+ if (!strcmp(nick, self.nick)) {
+ if (strcmp(user, self.user)) selfUser(user);
+ return true;
+ }
+ return false;
+}
+
typedef void (*Handler)(char *prefix, char *params);
static void handlePing(char *prefix, char *params) {
@@ -84,104 +94,118 @@ static void handlePing(char *prefix, char *params) {
static void handle432(char *prefix, char *params) {
char *mesg;
shift(prefix, NULL, NULL, NULL, params, 3, 0, NULL, NULL, &mesg);
- uiLog(L"You can't use that name here");
- uiFmt("Sheriff says, \"%s\"", mesg);
- uiLog(L"Type /nick <name> to choose a new one");
+ uiLog(TAG_DEFAULT, L"You can't use that name here");
+ uiFmt(TAG_DEFAULT, "Sheriff says, \"%s\"", mesg);
+ uiLog(TAG_DEFAULT, L"Type /nick <name> to choose a new one");
}
static void handle001(char *prefix, char *params) {
char *nick;
shift(prefix, NULL, NULL, NULL, params, 1, 0, &nick);
- if (strcmp(nick, chat.nick)) {
- free(chat.nick);
- chat.nick = strdup(nick);
- }
- ircFmt("JOIN %s\r\n", chat.join);
+ if (strcmp(nick, self.nick)) selfNick(nick);
+ tabTouch(TAG_DEFAULT, self.nick);
+ if (self.join) ircFmt("JOIN %s\r\n", self.join);
+ uiLog(TAG_DEFAULT, L"You have arrived");
+}
+
+static void handle372(char *prefix, char *params) {
+ char *mesg;
+ shift(prefix, NULL, NULL, NULL, params, 2, 0, NULL, &mesg);
+ if (mesg[0] == '-' && mesg[1] == ' ') mesg = &mesg[2];
+ uiFmt(TAG_DEFAULT, "%s", mesg);
}
static void handleJoin(char *prefix, char *params) {
char *nick, *user, *chan;
shift(prefix, &nick, &user, NULL, params, 1, 0, &chan);
+ struct Tag tag = tagFor(chan);
+ if (isSelf(nick, user)) {
+ tabTouch(TAG_DEFAULT, chan);
+ uiFocus(tag);
+ } else {
+ tabTouch(tag, nick);
+ }
uiFmt(
- "\3%d%s\3 arrives in \3%d%s\3",
+ tag, "\3%d%s\3 arrives in \3%d%s\3",
color(user), nick, color(chan), chan
);
- if (!strcmp(nick, chat.nick) && strcmp(user, chat.user)) {
- free(chat.user);
- chat.user = strdup(user);
- }
- tabTouch(nick);
}
static void handlePart(char *prefix, char *params) {
char *nick, *user, *chan, *mesg;
shift(prefix, &nick, &user, NULL, params, 1, 1, &chan, &mesg);
+ struct Tag tag = tagFor(chan);
+ (void)(isSelf(nick, user) ? tabClear(tag) : tabRemove(tag, nick));
if (mesg) {
uiFmt(
- "\3%d%s\3 leaves \3%d%s\3, \"%s\"",
+ tag, "\3%d%s\3 leaves \3%d%s\3, \"%s\"",
color(user), nick, color(chan), chan, mesg
);
} else {
uiFmt(
- "\3%d%s\3 leaves \3%d%s\3",
+ tag, "\3%d%s\3 leaves \3%d%s\3",
color(user), nick, color(chan), chan
);
}
- tabRemove(nick);
-}
-
-static void handleQuit(char *prefix, char *params) {
- char *nick, *user, *mesg;
- shift(prefix, &nick, &user, NULL, params, 0, 1, &mesg);
- if (mesg) {
- char *quot = (mesg[0] == '"') ? "" : "\"";
- uiFmt(
- "\3%d%s\3 leaves, %s%s%s",
- color(user), nick, quot, mesg, quot
- );
- } else {
- uiFmt("\3%d%s\3 leaves", color(user), nick);
- }
- tabRemove(nick);
}
static void handleKick(char *prefix, char *params) {
char *nick, *user, *chan, *kick, *mesg;
shift(prefix, &nick, &user, NULL, params, 2, 1, &chan, &kick, &mesg);
+ struct Tag tag = tagFor(chan);
+ (void)(isSelf(nick, user) ? tabClear(tag) : tabRemove(tag, nick));
if (mesg) {
uiFmt(
- "\3%d%s\3 kicks \3%d%s\3 out of \3%d%s\3, \"%s\"",
+ tag, "\3%d%s\3 kicks \3%d%s\3 out of \3%d%s\3, \"%s\"",
color(user), nick, color(kick), kick, color(chan), chan, mesg
);
} else {
uiFmt(
- "\3%d%s\3 kicks \3%d%s\3 out of \3%d%s\3",
+ tag, "\3%d%s\3 kicks \3%d%s\3 out of \3%d%s\3",
color(user), nick, color(kick), kick, color(chan), chan
);
}
- tabRemove(nick);
+}
+
+static void handleQuit(char *prefix, char *params) {
+ char *nick, *user, *mesg;
+ shift(prefix, &nick, &user, NULL, params, 0, 1, &mesg);
+ // TODO: Send to tags where nick is in tab.
+ tabRemove(TAG_ALL, nick);
+ if (mesg) {
+ char *quot = (mesg[0] == '"') ? "" : "\"";
+ uiFmt(
+ TAG_DEFAULT, "\3%d%s\3 leaves, %s%s%s",
+ color(user), nick, quot, mesg, quot
+ );
+ } else {
+ uiFmt(TAG_DEFAULT, "\3%d%s\3 leaves", color(user), nick);
+ }
}
static void handle332(char *prefix, char *params) {
char *chan, *topic;
shift(prefix, NULL, NULL, NULL, params, 3, 0, NULL, &chan, &topic);
+ struct Tag tag = tagFor(chan);
+ urlScan(tag, topic);
+ uiTopic(tag, topic);
uiFmt(
- "The sign in \3%d%s\3 reads, \"%s\"",
+ tag, "The sign in \3%d%s\3 reads, \"%s\"",
color(chan), chan, topic
);
- urlScan(topic);
- uiTopicStr(topic);
}
static void handleTopic(char *prefix, char *params) {
char *nick, *user, *chan, *topic;
shift(prefix, &nick, &user, NULL, params, 2, 0, &chan, &topic);
+ struct Tag tag = tagFor(chan);
+ if (!isSelf(nick, user)) tabTouch(tag, nick);
+ urlScan(tag, topic);
+ uiTopic(tag, topic);
uiFmt(
- "\3%d%s\3 places a new sign in \3%d%s\3, \"%s\"",
+ tag, "\3%d%s\3 places a new sign in \3%d%s\3, \"%s\"",
color(user), nick, color(chan), chan, topic
);
- urlScan(topic);
- uiTopicStr(topic);
}
static void handle366(char *prefix, char *params) {
@@ -190,17 +214,20 @@ static void handle366(char *prefix, char *params) {
ircFmt("WHO %s\r\n", chan);
}
+// FIXME: Track tag?
static struct {
char buf[4096];
size_t len;
} who;
static void handle352(char *prefix, char *params) {
- char *user, *nick;
+ char *chan, *user, *nick;
shift(
prefix, NULL, NULL, NULL,
- params, 6, 0, NULL, NULL, &user, NULL, NULL, &nick
+ params, 6, 0, NULL, &chan, &user, NULL, NULL, &nick
);
+ struct Tag tag = tagFor(chan);
+ if (!isSelf(nick, user)) tabTouch(tag, nick);
size_t cap = sizeof(who.buf) - who.len;
int len = snprintf(
&who.buf[who.len], cap,
@@ -208,14 +235,14 @@ static void handle352(char *prefix, char *params) {
(who.len ? ", " : ""), color(user), nick
);
if ((size_t)len < cap) who.len += len;
- tabTouch(nick);
}
static void handle315(char *prefix, char *params) {
char *chan;
shift(prefix, NULL, NULL, NULL, params, 2, 0, NULL, &chan);
+ struct Tag tag = tagFor(chan);
uiFmt(
- "In \3%d%s\3 are %s",
+ tag, "In \3%d%s\3 are %s",
color(chan), chan, who.buf
);
who.len = 0;
@@ -224,58 +251,58 @@ static void handle315(char *prefix, char *params) {
static void handleNick(char *prefix, char *params) {
char *prev, *user, *next;
shift(prefix, &prev, &user, NULL, params, 1, 0, &next);
+ if (isSelf(prev, user)) selfNick(next);
+ // TODO: Send to tags where prev is in tab.
+ tabReplace(prev, next);
uiFmt(
- "\3%d%s\3 is now known as \3%d%s\3",
+ TAG_DEFAULT, "\3%d%s\3 is now known as \3%d%s\3",
color(user), prev, color(user), next
);
- if (!strcmp(user, chat.user)) {
- free(chat.nick);
- chat.nick = strdup(next);
- }
- tabReplace(prev, next);
}
-static void handleCTCP(char *nick, char *user, char *mesg) {
+static void handleCTCP(struct Tag tag, char *nick, char *user, char *mesg) {
mesg = &mesg[1];
char *ctcp = strsep(&mesg, " ");
char *params = strsep(&mesg, "\1");
if (strcmp(ctcp, "ACTION")) return;
+ if (!isSelf(nick, user)) tabTouch(tag, nick);
+ urlScan(tag, params);
uiFmt(
- "\3%d* %s\3 %s",
+ tag, "\3%d* %s\3 %s",
color(user), nick, params
);
- if (strcmp(user, chat.user)) tabTouch(nick);
- urlScan(params);
}
static void handlePrivmsg(char *prefix, char *params) {
- char *nick, *user, *mesg;
- shift(prefix, &nick, &user, NULL, params, 2, 0, NULL, &mesg);
+ char *nick, *user, *chan, *mesg;
+ shift(prefix, &nick, &user, NULL, params, 2, 0, &chan, &mesg);
+ struct Tag tag = (strcmp(chan, self.nick) ? tagFor(chan) : tagFor(nick));
if (mesg[0] == '\1') {
- handleCTCP(nick, user, mesg);
+ handleCTCP(tag, nick, user, mesg);
return;
}
- bool self = !strcmp(user, chat.user);
- bool ping = !strncasecmp(mesg, chat.nick, strlen(chat.nick));
+ if (!isSelf(nick, user)) tabTouch(tag, nick);
+ urlScan(tag, mesg);
+ bool ping = !strncasecmp(mesg, self.nick, strlen(self.nick));
+ bool self = isSelf(nick, user);
uiFmt(
- "%c\3%d%c%s%c\17 %s",
+ tag, "%c\3%d%c%s%c\17 %s",
ping["\17\26"], color(user), self["<("], nick, self[">)"], mesg
);
- if (!self) tabTouch(nick);
if (ping) uiBeep();
- urlScan(mesg);
}
static void handleNotice(char *prefix, char *params) {
char *nick, *user, *chan, *mesg;
shift(prefix, &nick, &user, NULL, params, 2, 0, &chan, &mesg);
- if (strcmp(chan, chat.join)) return;
+ struct Tag tag = TAG_DEFAULT;
+ if (user) tag = (strcmp(chan, self.nick) ? tagFor(chan) : tagFor(nick));
+ if (!isSelf(nick, user)) tabTouch(tag, nick);
+ urlScan(tag, mesg);
uiFmt(
- "\3%d-%s-\3 %s",
+ tag, "\3%d-%s-\3 %s",
color(user), nick, mesg
);
- tabTouch(nick);
- urlScan(mesg);
}
static const struct {
@@ -287,6 +314,8 @@ static const struct {
{ "332", handle332 },
{ "352", handle352 },
{ "366", handle366 },
+ { "372", handle372 },
+ { "375", handle372 },
{ "432", handle432 },
{ "433", handle432 },
{ "JOIN", handleJoin },
diff --git a/input.c b/input.c
index 56c38bf..f4e3106 100644
--- a/input.c
+++ b/input.c
@@ -23,12 +23,13 @@
#include "chat.h"
-static void privmsg(bool action, const char *mesg) {
+static void privmsg(struct Tag tag, bool action, const char *mesg) {
+ if (tag.id == TAG_DEFAULT.id) return;
char *line;
int send;
asprintf(
&line, ":%s!%s %nPRIVMSG %s :%s%s%s",
- chat.nick, chat.user, &send, chat.join,
+ self.nick, self.user, &send, tag.name,
(action ? "\1ACTION " : ""), mesg, (action ? "\1" : "")
);
if (!line) err(EX_OSERR, "asprintf");
@@ -37,35 +38,47 @@ static void privmsg(bool action, const char *mesg) {
free(line);
}
-typedef void (*Handler)(char *params);
+typedef void (*Handler)(struct Tag tag, char *params);
-static void inputMe(char *params) {
- privmsg(true, params ? params : "");
+static void inputMe(struct Tag tag, char *params) {
+ privmsg(tag, true, params ? params : "");
}
-static void inputNick(char *params) {
+static void inputNick(struct Tag tag, char *params) {
+ (void)tag;
char *nick = strsep(&params, " ");
if (nick) {
ircFmt("NICK %s\r\n", nick);
} else {
- uiLog(L"/nick requires a name");
+ uiLog(TAG_DEFAULT, L"/nick requires a name");
}
}
-static void inputWho(char *params) {
- (void)params;
- ircFmt("WHO %s\r\n", chat.join);
+static void inputJoin(struct Tag tag, char *params) {
+ (void)tag;
+ char *chan = strsep(&params, " ");
+ if (chan) {
+ ircFmt("JOIN %s\r\n", chan);
+ } else {
+ uiLog(TAG_DEFAULT, L"/join requires a channel");
+ }
}
-static void inputTopic(char *params) {
- if (params) {
- ircFmt("TOPIC %s :%s\r\n", chat.join, params);
+static void inputWho(struct Tag tag, char *params) {
+ (void)params; // TODO
+ ircFmt("WHO %s\r\n", tag.name);
+}
+
+static void inputTopic(struct Tag tag, char *params) {
+ if (params) { // TODO
+ ircFmt("TOPIC %s :%s\r\n", tag.name, params);
} else {
- ircFmt("TOPIC %s\r\n", chat.join);
+ ircFmt("TOPIC %s\r\n", tag.name);
}
}
-static void inputQuit(char *params) {
+static void inputQuit(struct Tag tag, char *params) {
+ (void)tag;
if (params) {
ircFmt("QUIT :%s\r\n", params);
} else {
@@ -73,25 +86,34 @@ static void inputQuit(char *params) {
}
}
-static void inputUrl(char *params) {
+static void inputUrl(struct Tag tag, char *params) {
(void)params;
- urlList();
+ urlList(tag);
}
-static void inputOpen(char *params) {
- if (!params) { urlOpen(1); return; }
+static void inputOpen(struct Tag tag, char *params) {
+ if (!params) { urlOpen(tag, 1); return; }
size_t from = strtoul(strsep(&params, "-,"), NULL, 0);
- if (!params) { urlOpen(from); return; }
+ if (!params) { urlOpen(tag, from); return; }
size_t to = strtoul(strsep(&params, "-,"), NULL, 0);
if (to < from) to = from;
for (size_t i = from; i <= to; ++i) {
- urlOpen(i);
+ urlOpen(tag, i);
}
}
+static void inputView(struct Tag tag, char *params) {
+ char *view = strsep(&params, " ");
+ if (!view) return;
+ size_t num = strtoul(view, &view, 0);
+ tag = (view[0] ? tagName(view) : tagNum(num));
+ if (tag.name) uiFocus(tag);
+}
+
static const struct {
const char *command;
Handler handler;
} COMMANDS[] = {
+ { "/join", inputJoin },
{ "/me", inputMe },
{ "/names", inputWho },
{ "/nick", inputNick },
@@ -99,27 +121,28 @@ static const struct {
{ "/quit", inputQuit },
{ "/topic", inputTopic },
{ "/url", inputUrl },
+ { "/view", inputView },
{ "/who", inputWho },
};
static const size_t COMMANDS_LEN = sizeof(COMMANDS) / sizeof(COMMANDS[0]);
-void input(char *input) {
+void input(struct Tag tag, char *input) {
if (input[0] != '/') {
- privmsg(false, input);
+ privmsg(tag, false, input);
return;
}
char *command = strsep(&input, " ");
if (input && !input[0]) input = NULL;
for (size_t i = 0; i < COMMANDS_LEN; ++i) {
if (strcasecmp(command, COMMANDS[i].command)) continue;
- COMMANDS[i].handler(input);
+ COMMANDS[i].handler(tag, input);
return;
}
- uiFmt("%s isn't a recognized command", command);
+ uiFmt(TAG_DEFAULT, "%s isn't a recognized command", command);
}
void inputTab(void) {
for (size_t i = 0; i < COMMANDS_LEN; ++i) {
- tabTouch(COMMANDS[i].command);
+ tabTouch(TAG_DEFAULT, COMMANDS[i].command);
}
}
diff --git a/irc.c b/irc.c
index b9bef73..579f23b 100644
--- a/irc.c
+++ b/irc.c
@@ -39,7 +39,7 @@ static void webirc(const char *pass) {
if (sp) len = sp - ssh;
ircFmt(
"WEBIRC %s %s %.*s %.*s\r\n",
- pass, chat.user, len, ssh, len, ssh
+ pass, self.user, len, ssh, len, ssh
);
}
@@ -83,11 +83,8 @@ int ircConnect(
if (webPass) webirc(webPass);
if (pass) ircFmt("PASS :%s\r\n", pass);
- ircFmt(
- "NICK %s\r\n"
- "USER %s 0 * :%s\r\n",
- chat.nick, chat.user, chat.nick
- );
+ ircFmt("NICK %s\r\n", self.nick);
+ ircFmt("USER %s 0 * :%s\r\n", self.user, self.nick);
return sock;
}
@@ -109,7 +106,7 @@ void ircFmt(const char *format, ...) {
int len = vasprintf(&buf, format, ap);
va_end(ap);
if (!buf) err(EX_OSERR, "vasprintf");
- if (chat.verbose) uiFmt("<<< %.*s", len - 2, buf);
+ if (self.verbose) uiFmt(tagFor("(irc)"), "\00314<<<\3 %.*s", len - 2, buf);
ircWrite(buf, len);
free(buf);
}
@@ -129,7 +126,7 @@ void ircRead(void) {
char *crlf, *line = buf;
while ((crlf = strnstr(line, "\r\n", &buf[len] - line))) {
crlf[0] = '\0';
- if (chat.verbose) uiFmt(">>> %s", line);
+ if (self.verbose) uiFmt(tagFor("(irc)"), "\00314>>>\3 %s", line);
handle(line);
line = &crlf[2];
}
diff --git a/tab.c b/tab.c
index a9ddfe5..a6bb795 100644
--- a/tab.c
+++ b/tab.c
@@ -22,6 +22,7 @@
#include "chat.h"
static struct Entry {
+ size_t tag;
char *word;
struct Entry *prev;
struct Entry *next;
@@ -46,8 +47,9 @@ static void touch(struct Entry *entry) {
prepend(entry);
}
-void tabTouch(const char *word) {
+void tabTouch(struct Tag tag, const char *word) {
for (struct Entry *entry = head; entry; entry = entry->next) {
+ if (entry->tag != tag.id) continue;
if (strcmp(entry->word, word)) continue;
touch(entry);
return;
@@ -55,20 +57,28 @@ void tabTouch(const char *word) {
struct Entry *entry = malloc(sizeof(*entry));
if (!entry) err(EX_OSERR, "malloc");
+
+ entry->tag = tag.id;
entry->word = strdup(word);
+ if (!entry->word) err(EX_OSERR, "strdup");
+
prepend(entry);
}
void tabReplace(const char *prev, const char *next) {
- tabTouch(prev);
- free(head->word);
- head->word = strdup(next);
+ for (struct Entry *entry = head; entry; entry = entry->next) {
+ if (strcmp(entry->word, prev)) continue;
+ free(entry->word);
+ entry->word = strdup(next);
+ if (!entry->word) err(EX_OSERR, "strdup");
+ }
}
static struct Entry *match;
-void tabRemove(const char *word) {
+void tabRemove(struct Tag tag, const char *word) {
for (struct Entry *entry = head; entry; entry = entry->next) {
+ if (tag.id != TAG_ALL.id && entry->tag != tag.id) continue;
if (strcmp(entry->word, word)) continue;
unlink(entry);
if (match == entry) match = entry->next;
@@ -78,17 +88,28 @@ void tabRemove(const char *word) {
}
}
-const char *tabNext(const char *prefix) {
+void tabClear(struct Tag tag) {
+ for (struct Entry *entry = head; entry; entry = entry->next) {
+ if (entry->tag != tag.id) continue;
+ unlink(entry);
+ if (match == entry) match = entry->next;
+ free(entry->word);
+ free(entry);
+ }
+}
+
+const char *tabNext(struct Tag tag, const char *prefix) {
size_t len = strlen(prefix);
struct Entry *start = (match ? match->next : head);
for (struct Entry *entry = start; entry; entry = entry->next) {
+ if (entry->tag != TAG_DEFAULT.id && entry->tag != tag.id) continue;
if (strncasecmp(entry->word, prefix, len)) continue;
match = entry;
return entry->word;
}
if (!match) return NULL;
match = NULL;
- return tabNext(prefix);
+ return tabNext(tag, prefix);
}
void tabAccept(void) {
diff --git a/tag.c b/tag.c
new file mode 100644
index 0000000..014e84c
--- /dev/null
+++ b/tag.c
@@ -0,0 +1,77 @@
+/* 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 <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+#include "chat.h"
+
+const struct Tag TAG_ALL = { (size_t)-1, NULL };
+const struct Tag TAG_DEFAULT = { 0, "(status)" };
+
+static struct {
+ char *name[TAGS_LEN];
+ size_t len;
+ size_t gap;
+} tags = {
+ .name = { "(status)" },
+ .len = 1,
+ .gap = 1,
+};
+
+static struct Tag Tag(size_t id) {
+ return (struct Tag) { id, tags.name[id] };
+}
+
+struct Tag tagName(const char *name) {
+ for (size_t id = 0; id < tags.len; ++id) {
+ if (!tags.name[id] || strcmp(tags.name[id], name)) continue;
+ return Tag(id);
+ }
+ return TAG_ALL;
+}
+
+struct Tag tagNum(size_t num) {
+ if (num < tags.gap) return Tag(num);
+ num -= tags.gap;
+ for (size_t id = tags.gap; id < tags.len; ++id) {
+ if (!tags.name[id]) continue;
+ if (!num--) return Tag(id);
+ }
+ return TAG_ALL;
+}
+
+struct Tag tagFor(const char *name) {
+ struct Tag tag = tagName(name);
+ if (tag.name) return tag;
+
+ size_t id = tags.gap;
+ tags.name[id] = strdup(name);
+ if (!tags.name[id]) err(EX_OSERR, "strdup");
+
+ if (tags.gap == tags.len) {
+ tags.gap++;
+ tags.len++;
+ } else {
+ for (tags.gap++; tags.gap < tags.len; ++tags.gap) {
+ if (!tags.name[tags.gap]) break;
+ }
+ }
+
+ return Tag(id);
+}
diff --git a/ui.c b/ui.c
index 9778473..844e777 100644
--- a/ui.c
+++ b/ui.c
@@ -95,13 +95,19 @@ static int logHeight(void) {
return LINES - 4;
}
-static struct {
+struct View {
WINDOW *topic;
WINDOW *log;
- WINDOW *input;
- bool hide;
- bool mark;
int scroll;
+ bool mark;
+};
+
+static struct {
+ bool hide;
+ WINDOW *input;
+ struct Tag tag;
+ struct View views[TAGS_LEN];
+ size_t len;
} ui;
void uiInit(void) {
@@ -113,14 +119,7 @@ void uiInit(void) {
focusEnable();
colorInit();
- ui.topic = newpad(2, TOPIC_COLS);
- mvwhline(ui.topic, 1, 0, ACS_HLINE, TOPIC_COLS);
-
- ui.log = newpad(LOG_LINES, COLS);
- wsetscrreg(ui.log, 0, LOG_LINES - 1);
- scrollok(ui.log, true);
- wmove(ui.log, LOG_LINES - logHeight() - 1, 0);
- ui.scroll = LOG_LINES;
+ ui.tag = TAG_DEFAULT;
ui.input = newpad(2, INPUT_COLS);
mvwhline(ui.input, 0, 0, ACS_HLINE, INPUT_COLS);
@@ -130,11 +129,6 @@ void uiInit(void) {
nodelay(ui.input, true);
}
-static void uiResize(void) {
- wresize(ui.log, LOG_LINES, COLS);
- wmove(ui.log, LOG_LINES - 1, COLS - 1);
-}
-
void uiHide(void) {
ui.hide = true;
endwin();
@@ -149,17 +143,46 @@ void uiExit(void) {
);
}
+static struct View *uiView(struct Tag tag) {
+ struct View *view = &ui.views[tag.id];
+ if (view->log) return view;
+
+ view->topic = newpad(2, TOPIC_COLS);
+ mvwhline(view->topic, 1, 0, ACS_HLINE, TOPIC_COLS);
+
+ view->log = newpad(LOG_LINES, COLS);
+ wsetscrreg(view->log, 0, LOG_LINES - 1);
+ scrollok(view->log, true);
+ wmove(view->log, LOG_LINES - logHeight() - 1, 0);
+
+ view->scroll = LOG_LINES;
+ view->mark = false;
+
+ if (tag.id >= ui.len) ui.len = tag.id + 1;
+ return view;
+}
+
+static void uiResize(void) {
+ for (size_t i = 0; i < ui.len; ++i) {
+ struct View *view = &ui.views[i];
+ if (!view->log) continue;
+ wresize(view->log, LOG_LINES, COLS);
+ wmove(view->log, LOG_LINES - 1, COLS - 1);
+ }
+}
+
void uiDraw(void) {
if (ui.hide) return;
+ struct View *view = uiView(ui.tag);
pnoutrefresh(
- ui.topic,
+ view->topic,
0, 0,
0, 0,
1, lastCol()
);
pnoutrefresh(
- ui.log,
- ui.scroll - logHeight(), 0,
+ view->log,
+ view->scroll - logHeight(), 0,
2, 0,
lastLine() - 2, lastCol()
);
@@ -178,6 +201,16 @@ static void uiRedraw(void) {
clearok(curscr, true);
}
+void uiFocus(struct Tag tag) {
+ struct View *view = uiView(ui.tag);
+ view->mark = true;
+ view = uiView(tag);
+ view->mark = false;
+ touchwin(view->topic);
+ touchwin(view->log);
+ ui.tag = tag;
+}
+
void uiBeep(void) {
beep(); // always be beeping
}
@@ -276,93 +309,133 @@ static void addIRC(WINDOW *win, const wchar_t *str) {
}
}
-void uiTopic(const wchar_t *topic) {
- wmove(ui.topic, 0, 0);
- addIRC(ui.topic, topic);
- wclrtoeol(ui.topic);
-}
-
-void uiTopicStr(const char *topic) {
+void uiTopic(struct Tag tag, const char *topic) {
wchar_t *wcs = ambstowcs(topic);
if (!wcs) err(EX_DATAERR, "ambstowcs");
- uiTopic(wcs);
+ struct View *view = uiView(tag);
+ wmove(view->topic, 0, 0);
+ addIRC(view->topic, wcs);
+ wclrtoeol(view->topic);
free(wcs);
}
-void uiLog(const wchar_t *line) {
- waddch(ui.log, '\n');
- if (ui.mark) {
- waddch(ui.log, '\n');
- ui.mark = false;
+void uiLog(struct Tag tag, const wchar_t *line) {
+ struct View *view = uiView(tag);
+ waddch(view->log, '\n');
+ if (view->mark) {
+ waddch(view->log, '\n');
+ view->mark = false;
}
- addIRC(ui.log, line);
+ addIRC(view->log, line);
}
-void uiFmt(const wchar_t *format, ...) {
+void uiFmt(struct Tag tag, const wchar_t *format, ...) {
wchar_t *buf;
va_list ap;
va_start(ap, format);
vaswprintf(&buf, format, ap);
va_end(ap);
if (!buf) err(EX_OSERR, "vaswprintf");
- uiLog(buf);
+ uiLog(tag, buf);
free(buf);
}
static void logUp(void) {
- if (ui.scroll == logHeight()) return;
- if (ui.scroll == LOG_LINES) ui.mark = true;
- ui.scroll = MAX(ui.scroll - logHeight() / 2, logHeight());
+ struct View *view = uiView(ui.tag);
+ if (view->scroll == logHeight()) return;
+ if (view->scroll == LOG_LINES) view->mark = true;
+ view->scroll = MAX(view->scroll - logHeight() / 2, logHeight());
}
static void logDown(void) {
- if (ui.scroll == LOG_LINES) return;
- ui.scroll = MIN(ui.scroll + logHeight() / 2, LOG_LINES);
- if (ui.scroll == LOG_LINES) ui.mark = false;
+ struct View *view = uiView(ui.tag);
+ if (view->scroll == LOG_LINES) return;
+ view->scroll = MIN(view->scroll + logHeight() / 2, LOG_LINES);
+ if (view->scroll == LOG_LINES) view->mark = false;
}
-static bool keyChar(wint_t ch) {
+static bool keyChar(wchar_t ch) {
static bool esc, csi;
- bool update = false;
+ if (ch == L'\33') {
+ esc = true;
+ return false;
+ }
+ if (esc && ch == L'[') {
+ esc = false;
+ csi = true;
+ return false;
+ }
+ if (csi) {
+ if (ch == L'O') uiView(ui.tag)->mark = true;
+ if (ch == L'I') uiView(ui.tag)->mark = false;
+ csi = false;
+ return false;
+ }
+ if (ch == L'\177') ch = L'\b';
+
+ bool update = true;
+ if (esc) {
+ switch (ch) {
+ break; case L'b': edit(ui.tag, EDIT_BACK_WORD, 0);
+ break; case L'f': edit(ui.tag, EDIT_FORE_WORD, 0);
+ break; case L'\b': edit(ui.tag, EDIT_KILL_BACK_WORD, 0);
+ break; case L'd': edit(ui.tag, EDIT_KILL_FORE_WORD, 0);
+ break; default: {
+ update = false;
+ if (ch >= L'0' && ch <= L'9') {
+ struct Tag tag = tagNum(ch - L'0');
+ if (tag.name) uiFocus(tag);
+ }
+ }
+ }
+ esc = false;
+ return update;
+ }
+
switch (ch) {
- break; case CTRL('L'): uiRedraw();
- 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; case CTRL(L'L'): uiRedraw(); return false;
+
+ break; case CTRL(L'A'): edit(ui.tag, EDIT_HOME, 0);
+ break; case CTRL(L'B'): edit(ui.tag, EDIT_LEFT, 0);
+ break; case CTRL(L'D'): edit(ui.tag, EDIT_DELETE, 0);
+ break; case CTRL(L'E'): edit(ui.tag, EDIT_END, 0);
+ break; case CTRL(L'F'): edit(ui.tag, EDIT_RIGHT, 0);
+ break; case CTRL(L'K'): edit(ui.tag, EDIT_KILL_LINE, 0);
+ break; case CTRL(L'W'): edit(ui.tag, EDIT_KILL_BACK_WORD, 0);
+
+ break; case CTRL(L'C'): edit(ui.tag, EDIT_INSERT, IRC_COLOR);
+ break; case CTRL(L'N'): edit(ui.tag, EDIT_INSERT, IRC_RESET);
+ break; case CTRL(L'O'): edit(ui.tag, EDIT_INSERT, IRC_BOLD);
+ break; case CTRL(L'R'): edit(ui.tag, EDIT_INSERT, IRC_COLOR);
+ break; case CTRL(L'T'): edit(ui.tag, EDIT_INSERT, IRC_ITALIC);
+ break; case CTRL(L'U'): edit(ui.tag, EDIT_INSERT, IRC_UNDERLINE);
+ break; case CTRL(L'V'): edit(ui.tag, EDIT_INSERT, IRC_REVERSE);
+
+ break; case L'\b': edit(ui.tag, EDIT_BACKSPACE, 0);
+ break; case L'\t': edit(ui.tag, EDIT_COMPLETE, 0);
+ break; case L'\n': edit(ui.tag, EDIT_ENTER, 0);
+
break; default: {
- if (esc && ch == L'[') {
- csi = true;
- return false;
- } else if (csi) {
- if (ch == L'O') ui.mark = true;
- if (ch == L'I') ui.mark = false;
- } else if (iswcntrl(ch)) {
- update = edit(esc, true, UNCTRL(ch));
- } else {
- update = edit(esc, false, ch);
- }
+ if (!iswprint(ch)) return false;
+ edit(ui.tag, EDIT_INSERT, ch);
}
}
- esc = false;
- csi = false;
- return update;
+ return true;
}
-static bool keyCode(wint_t ch) {
+static bool keyCode(wchar_t ch) {
switch (ch) {
- break; case KEY_RESIZE: uiResize();
- break; case KEY_PPAGE: logUp();
- break; case KEY_NPAGE: logDown();
- 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');
+ break; case KEY_RESIZE: uiResize(); return false;
+ break; case KEY_PPAGE: logUp(); return false;
+ break; case KEY_NPAGE: logDown(); return false;
+ break; case KEY_LEFT: edit(ui.tag, EDIT_LEFT, ch);
+ break; case KEY_RIGHT: edit(ui.tag, EDIT_RIGHT, ch);
+ break; case KEY_HOME: edit(ui.tag, EDIT_HOME, ch);
+ break; case KEY_END: edit(ui.tag, EDIT_END, ch);
+ break; case KEY_DC: edit(ui.tag, EDIT_DELETE, ch);
+ break; case KEY_BACKSPACE: edit(ui.tag, EDIT_BACKSPACE, ch);
+ break; case KEY_ENTER: edit(ui.tag, EDIT_ENTER, ch);
}
- return false;
+ return true;
}
void uiRead(void) {
diff --git a/url.c b/url.c
index 1c57126..b7172ce 100644
--- a/url.c
+++ b/url.c
@@ -30,40 +30,56 @@ static const char *SCHEMES[] = {
};
static const size_t SCHEMES_LEN = sizeof(SCHEMES) / sizeof(SCHEMES[0]);
-enum { RING_LEN = 16 };
-static char *ring[RING_LEN];
-static size_t last;
+struct Entry {
+ size_t tag;
+ char *url;
+};
+
+enum { RING_LEN = 32 };
static_assert(!(RING_LEN & (RING_LEN - 1)), "power of two RING_LEN");
-static void push(const char *url, size_t len) {
- free(ring[last]);
- ring[last++] = strndup(url, len);
- last &= RING_LEN - 1;
+static struct {
+ struct Entry buf[RING_LEN];
+ size_t end;
+} ring;
+
+static void push(struct Tag tag, const char *url, size_t len) {
+ free(ring.buf[ring.end].url);
+ ring.buf[ring.end].tag = tag.id;
+ ring.buf[ring.end].url = strndup(url, len);
+ if (!ring.buf[ring.end].url) err(EX_OSERR, "strndup");
+ ring.end = (ring.end + 1) & (RING_LEN - 1);
}
-void urlScan(const char *str) {
+void urlScan(struct Tag tag, const char *str) {
while (str[0]) {
size_t len = 1;
for (size_t i = 0; i < SCHEMES_LEN; ++i) {
if (strncmp(str, SCHEMES[i], strlen(SCHEMES[i]))) continue;
len = strcspn(str, " >\"");
- push(str, len);
+ push(tag, str, len);
}
str = &str[len];
}
}
-void urlList(void) {
+void urlList(struct Tag tag) {
uiHide();
for (size_t i = 0; i < RING_LEN; ++i) {
- char *url = ring[(i + last) & (RING_LEN - 1)];
- if (url) printf("%s\n", url);
+ struct Entry entry = ring.buf[(ring.end + i) & (RING_LEN - 1)];
+ if (!entry.url || entry.tag != tag.id) continue;
+ printf("%s\n", entry.url);
}
}
-void urlOpen(size_t i) {
- char *url = ring[(last - i) & (RING_LEN - 1)];
- if (!url) return;
- char *argv[] = { "open", url, NULL };
- spawn(argv);
+void urlOpen(struct Tag tag, size_t fromEnd) {
+ size_t count = 0;
+ for (size_t i = 0; i < RING_LEN; ++i) {
+ struct Entry entry = ring.buf[(ring.end - i) & (RING_LEN - 1)];
+ if (!entry.url || entry.tag != tag.id) continue;
+ if (++count != fromEnd) continue;
+ char *argv[] = { "open", entry.url, NULL };
+ spawn(argv);
+ return;
+ }
}