about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2023-02-05 14:35:40 -0500
committerJune McEnroe <june@causal.agency>2023-02-05 19:28:36 -0500
commit06be4c5956248021192e549d05aea066b5d2f13b (patch)
tree43accf040a26199bf42fb1fcbd48ea91df41a8d8
parentFlatten cache structs (diff)
downloadcatgirl-06be4c5956248021192e549d05aea066b5d2f13b.tar.gz
catgirl-06be4c5956248021192e549d05aea066b5d2f13b.zip
Fix what went wrong, part 1
-rw-r--r--Makefile2
-rw-r--r--cache.c212
-rw-r--r--chat.c2
-rw-r--r--chat.h33
-rw-r--r--command.c49
-rw-r--r--complete.c173
-rw-r--r--handle.c81
-rw-r--r--input.c13
-rw-r--r--window.c6
9 files changed, 267 insertions, 304 deletions
diff --git a/Makefile b/Makefile
index e8ef63a..3abba03 100644
--- a/Makefile
+++ b/Makefile
@@ -13,9 +13,9 @@ LDADD.ncursesw = -lncursesw
 LDLIBS = ${LDADD.libtls} ${LDADD.ncursesw}
 
 OBJS += buffer.o
-OBJS += cache.o
 OBJS += chat.o
 OBJS += command.o
+OBJS += complete.o
 OBJS += config.o
 OBJS += edit.o
 OBJS += filter.o
diff --git a/cache.c b/cache.c
deleted file mode 100644
index 3bff269..0000000
--- a/cache.c
+++ /dev/null
@@ -1,212 +0,0 @@
-/* Copyright (C) 2020, 2022  June McEnroe <june@causal.agency>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- *
- * Additional permission under GNU GPL version 3 section 7:
- *
- * If you modify this Program, or any covered work, by linking or
- * combining it with OpenSSL (or a modified version of that library),
- * containing parts covered by the terms of the OpenSSL License and the
- * original SSLeay license, the licensors of this Program grant you
- * additional permission to convey the resulting work. Corresponding
- * Source for a non-source form of such a combination shall include the
- * source code for the parts of OpenSSL used as well as that of the
- * covered work.
- */
-
-#include <err.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sysexits.h>
-
-#include "chat.h"
-
-static const struct Entry DefaultEntry = { .color = Default };
-
-static struct Entry *entryAlloc(uint id, const char *key) {
-	struct Entry *entry = malloc(sizeof(*entry));
-	if (!entry) err(EX_OSERR, "malloc");
-	*entry = DefaultEntry;
-	entry->id = id;
-	entry->key = strdup(key);
-	if (!entry->key) err(EX_OSERR, "strdup");
-	return entry;
-}
-
-static void entryFree(struct Entry *entry) {
-	free(entry->key);
-	free(entry);
-}
-
-static uint gen;
-static struct Entry *head;
-static struct Entry *tail;
-
-static struct Entry *detach(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;
-	if (tail == entry) tail = entry->prev;
-	entry->prev = NULL;
-	entry->next = NULL;
-	return entry;
-}
-
-static struct Entry *prepend(struct Entry *entry) {
-	entry->prev = NULL;
-	entry->next = head;
-	if (head) head->prev = entry;
-	head = entry;
-	tail = (tail ?: entry);
-	return entry;
-}
-
-static struct Entry *append(struct Entry *entry) {
-	entry->next = NULL;
-	entry->prev = tail;
-	if (tail) tail->next = entry;
-	tail = entry;
-	head = (head ?: entry);
-	return entry;
-}
-
-static struct Entry *find(uint id, const char *key) {
-	for (struct Entry *entry = head; entry; entry = entry->next) {
-		if (entry->id != id) continue;
-		if (strcmp(entry->key, key)) continue;
-		return entry;
-	}
-	return NULL;
-}
-
-static struct Entry *insert(bool touch, uint id, const char *key) {
-	struct Entry *entry = find(id, key);
-	if (entry && touch) {
-		return prepend(detach(entry));
-	} else if (entry) {
-		return entry;
-	} else if (touch) {
-		return prepend(entryAlloc(id, key));
-	} else {
-		return append(entryAlloc(id, key));
-	}
-}
-
-const struct Entry *cacheGet(uint id, const char *key) {
-	struct Entry *entry = find(id, key);
-	return (entry ?: &DefaultEntry);
-}
-
-struct Entry *cacheInsert(bool touch, uint id, const char *key) {
-	return insert(touch, id, key);
-}
-
-void cacheReplace(bool touch, const char *old, const char *new) {
-	struct Entry *next = NULL;
-	for (struct Entry *entry = head; entry; entry = next) {
-		next = entry->next;
-		if (strcmp(entry->key, old)) continue;
-		free(entry->key);
-		entry->key = strdup(new);
-		if (!entry->key) err(EX_OSERR, "strdup");
-		if (touch) prepend(detach(entry));
-	}
-}
-
-void cacheRemove(uint id, const char *key) {
-	gen++;
-	struct Entry *next = NULL;
-	for (struct Entry *entry = head; entry; entry = next) {
-		next = entry->next;
-		if (id && entry->id != id) continue;
-		if (strcmp(entry->key, key)) continue;
-		detach(entry);
-		entryFree(entry);
-		if (id) break;
-	}
-}
-
-void cacheClear(uint id) {
-	gen++;
-	struct Entry *next = NULL;
-	for (struct Entry *entry = head; entry; entry = next) {
-		next = entry->next;
-		if (entry->id != id) continue;
-		detach(entry);
-		entryFree(entry);
-	}
-}
-
-const char *cacheComplete(struct Cursor *curs, uint id, const char *prefix) {
-	size_t len = strlen(prefix);
-	if (curs->gen != gen) curs->entry = NULL;
-	for (
-		curs->gen = gen, curs->entry = (curs->entry ? curs->entry->next : head);
-		curs->entry;
-		curs->entry = curs->entry->next
-	) {
-		if (curs->entry->id && curs->entry->id != id) continue;
-		if (strncasecmp(curs->entry->key, prefix, len)) continue;
-		return curs->entry->key;
-	}
-	return NULL;
-}
-
-const char *cacheSearch(struct Cursor *curs, uint id, const char *substr) {
-	if (curs->gen != gen) curs->entry = NULL;
-	for (
-		curs->gen = gen, curs->entry = (curs->entry ? curs->entry->next : head);
-		curs->entry;
-		curs->entry = curs->entry->next
-	) {
-		if (curs->entry->id && curs->entry->id != id) continue;
-		if (!strstr(curs->entry->key, substr)) continue;
-		return curs->entry->key;
-	}
-	return NULL;
-}
-
-const char *cacheNextKey(struct Cursor *curs, uint id) {
-	if (curs->gen != gen) curs->entry = NULL;
-	for (
-		curs->gen = gen, curs->entry = (curs->entry ? curs->entry->next : head);
-		curs->entry;
-		curs->entry = curs->entry->next
-	) {
-		if (curs->entry->id != id) continue;
-		return curs->entry->key;
-	}
-	return NULL;
-}
-
-uint cacheNextID(struct Cursor *curs, const char *key) {
-	if (curs->gen != gen) curs->entry = NULL;
-	for (
-		curs->gen = gen, curs->entry = (curs->entry ? curs->entry->next : head);
-		curs->entry;
-		curs->entry = curs->entry->next
-	) {
-		if (!curs->entry->id) continue;
-		if (strcmp(curs->entry->key, key)) continue;
-		return curs->entry->id;
-	}
-	return None;
-}
-
-void cacheTouch(struct Cursor *curs) {
-	if (curs->gen == gen && curs->entry) {
-		prepend(detach(curs->entry));
-	}
-}
diff --git a/chat.c b/chat.c
index 39b1a93..8c425d4 100644
--- a/chat.c
+++ b/chat.c
@@ -374,7 +374,7 @@ int main(int argc, char *argv[]) {
 	set(&network.name, host);
 	set(&self.nick, "*");
 
-	inputCache();
+	inputCompletion();
 
 	ircConfig(insecure, trust, cert, priv);
 
diff --git a/chat.h b/chat.h
index 8086c9e..6a3f669 100644
--- a/chat.h
+++ b/chat.h
@@ -304,7 +304,7 @@ const char *commandIsPrivmsg(uint id, const char *input);
 const char *commandIsNotice(uint id, const char *input);
 const char *commandIsAction(uint id, const char *input);
 size_t commandWillSplit(uint id, const char *input);
-void commandCache(void);
+void commandCompletion(void);
 
 enum Heat {
 	Ice,
@@ -346,7 +346,7 @@ void inputWait(void);
 void inputUpdate(void);
 bool inputPending(uint id);
 void inputRead(void);
-void inputCache(void);
+void inputCompletion(void);
 int inputSave(FILE *file);
 void inputLoad(FILE *file, size_t version);
 
@@ -408,28 +408,19 @@ int bufferReflow(
 	struct Buffer *buffer, int cols, enum Heat thresh, size_t tail
 );
 
-struct Entry {
-	uint id;
-	char *key;
-	enum Color color;
-	uint prefixBits;
-	struct Entry *prev;
-	struct Entry *next;
-};
 struct Cursor {
 	uint gen;
-	struct Entry *entry;
+	struct Node *node;
 };
-const struct Entry *cacheGet(uint id, const char *key);
-struct Entry *cacheInsert(bool touch, uint id, const char *key);
-void cacheReplace(bool touch, const char *old, const char *new);
-void cacheRemove(uint id, const char *key);
-void cacheClear(uint id);
-const char *cacheComplete(struct Cursor *curs, uint id, const char *prefix);
-const char *cacheSearch(struct Cursor *curs, uint id, const char *substr);
-uint cacheNextID(struct Cursor *curs, const char *key);
-const char *cacheNextKey(struct Cursor *curs, uint id);
-void cacheTouch(struct Cursor *curs);
+void completePush(uint id, const char *str);
+void completePull(uint id, const char *str);
+void completeReplace(const char *old, const char *new);
+void completeRemove(uint id, const char *str);
+const char *completePrefix(struct Cursor *curs, uint id, const char *prefix);
+const char *completeSubstr(struct Cursor *curs, uint id, const char *substr);
+uint completeNextID(struct Cursor *curs, const char *str);
+void completeAccept(struct Cursor *curs);
+void completeReject(struct Cursor *curs);
 
 extern struct Util urlOpenUtil;
 extern struct Util urlCopyUtil;
diff --git a/command.c b/command.c
index 23b5e89..4a92ea2 100644
--- a/command.c
+++ b/command.c
@@ -138,9 +138,10 @@ static void commandMsg(uint id, char *params) {
 	if (!params) return;
 	char *nick = strsep(&params, " ");
 	uint msg = idFor(nick);
-	if (idColors[msg] == Default) {
-		idColors[msg] = cacheGet(id, nick)->color;
-	}
+	(void)id;
+	//if (idColors[msg] == Default) {
+	//	idColors[msg] = cacheGet(id, nick)->color;
+	//}
 	if (params) {
 		splitMessage("PRIVMSG", msg, params);
 	} else {
@@ -226,16 +227,16 @@ static void commandOps(uint id, char *params) {
 		idColors[id], idNames[id]
 	);
 	bool first = true;
-	struct Cursor curs = {0};
-	for (const char *nick; (nick = cacheNextKey(&curs, id));) {
-		char prefix = bitPrefix(curs.entry->prefixBits);
-		if (!prefix || prefix == '+') continue;
-		ptr = seprintf(
-			ptr, end, "%s\3%02d%c%s\3",
-			(first ? "" : ", "), curs.entry->color, prefix, nick
-		);
-		first = false;
-	}
+	//struct Cursor curs = {0};
+	//for (const char *nick; (nick = cacheNextKey(&curs, id));) {
+	//	char prefix = bitPrefix(curs.entry->prefixBits);
+	//	if (!prefix || prefix == '+') continue;
+	//	ptr = seprintf(
+	//		ptr, end, "%s\3%02d%c%s\3",
+	//		(first ? "" : ", "), curs.entry->color, prefix, nick
+	//	);
+	//	first = false;
+	//}
 	if (first) {
 		uiFormat(
 			id, Warm, NULL, "\3%02d%s\3 is a lawless wasteland",
@@ -402,9 +403,10 @@ static void commandCS(uint id, char *params) {
 static void commandQuery(uint id, char *params) {
 	if (!params) return;
 	uint query = idFor(params);
-	if (idColors[query] == Default) {
-		idColors[query] = cacheGet(id, params)->color;
-	}
+	(void)id;
+	//if (idColors[query] == Default) {
+	//	idColors[query] = cacheGet(id, params)->color;
+	//}
 	windowShow(windowFor(query));
 }
 
@@ -419,10 +421,11 @@ static void commandWindow(uint id, char *params) {
 			windowShow(windowFor(id));
 			return;
 		}
-		for (struct Cursor curs = {0}; cacheSearch(&curs, None, params);) {
-			id = idFind(curs.entry->key);
+		struct Cursor curs = {0};
+		for (const char *str; (str = completeSubstr(&curs, None, params));) {
+			id = idFind(str);
 			if (!id) continue;
-			cacheTouch(&curs);
+			completeAccept(&curs);
 			windowShow(windowFor(id));
 			break;
 		}
@@ -694,8 +697,8 @@ void command(uint id, char *input) {
 
 	struct Cursor curs = {0};
 	const char *cmd = strsep(&input, " ");
-	const char *unique = cacheComplete(&curs, None, cmd);
-	if (unique && !cacheComplete(&curs, None, cmd)) {
+	const char *unique = completePrefix(&curs, None, cmd);
+	if (unique && !completePrefix(&curs, None, cmd)) {
 		cmd = unique;
 	}
 
@@ -723,9 +726,9 @@ void command(uint id, char *input) {
 	handler->fn(id, input);
 }
 
-void commandCache(void) {
+void commandCompletion(void) {
 	for (size_t i = 0; i < ARRAY_LEN(Commands); ++i) {
 		if (!commandAvailable(&Commands[i])) continue;
-		cacheInsert(false, None, Commands[i].cmd);
+		completePush(None, Commands[i].cmd);
 	}
 }
diff --git a/complete.c b/complete.c
new file mode 100644
index 0000000..6d7756f
--- /dev/null
+++ b/complete.c
@@ -0,0 +1,173 @@
+/* Copyright (C) 2020, 2022  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ * Additional permission under GNU GPL version 3 section 7:
+ *
+ * If you modify this Program, or any covered work, by linking or
+ * combining it with OpenSSL (or a modified version of that library),
+ * containing parts covered by the terms of the OpenSSL License and the
+ * original SSLeay license, the licensors of this Program grant you
+ * additional permission to convey the resulting work. Corresponding
+ * Source for a non-source form of such a combination shall include the
+ * source code for the parts of OpenSSL used as well as that of the
+ * covered work.
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+#include "chat.h"
+
+struct Node {
+	uint id;
+	char *str;
+	struct Node *prev;
+	struct Node *next;
+};
+
+static uint gen;
+static struct Node *head;
+static struct Node *tail;
+
+static struct Node *alloc(uint id, const char *str) {
+	struct Node *node = calloc(1, sizeof(*node));
+	if (!node) err(EX_OSERR, "calloc");
+	node->id = id;
+	node->str = strdup(str);
+	if (!node->str) err(EX_OSERR, "strdup");
+	return node;
+}
+
+static struct Node *detach(struct Node *node) {
+	if (node->prev) node->prev->next = node->next;
+	if (node->next) node->next->prev = node->prev;
+	if (head == node) head = node->next;
+	if (tail == node) tail = node->prev;
+	node->prev = NULL;
+	node->next = NULL;
+	return node;
+}
+
+static struct Node *prepend(struct Node *node) {
+	node->prev = NULL;
+	node->next = head;
+	if (head) head->prev = node;
+	head = node;
+	tail = (tail ?: node);
+	return node;
+}
+
+static struct Node *append(struct Node *node) {
+	node->next = NULL;
+	node->prev = tail;
+	if (tail) tail->next = node;
+	tail = node;
+	head = (head ?: node);
+	return node;
+}
+
+static struct Node *find(uint id, const char *str) {
+	for (struct Node *node = head; node; node = node->next) {
+		if (node->id == id && !strcmp(node->str, str)) return node;
+	}
+	return NULL;
+}
+
+void completePush(uint id, const char *str) {
+	if (!find(id, str)) append(alloc(id, str));
+}
+
+void completePull(uint id, const char *str) {
+	struct Node *node = find(id, str);
+	prepend(node ? detach(node) : alloc(id, str));
+}
+
+void completeReplace(const char *old, const char *new) {
+	struct Node *next = NULL;
+	for (struct Node *node = head; node; node = next) {
+		next = node->next;
+		if (strcmp(node->str, old)) continue;
+		free(node->str);
+		node->str = strdup(new);
+		if (!node->str) err(EX_OSERR, "strdup");
+		prepend(detach(node));
+	}
+}
+
+void completeRemove(uint id, const char *str) {
+	struct Node *next = NULL;
+	for (struct Node *node = head; node; node = next) {
+		next = node->next;
+		if (id && node->id != id) continue;
+		if (str && strcmp(node->str, str)) continue;
+		detach(node);
+		free(node->str);
+		free(node);
+	}
+	gen++;
+}
+
+const char *completePrefix(struct Cursor *curs, uint id, const char *prefix) {
+	size_t len = strlen(prefix);
+	if (curs->gen != gen) curs->node = NULL;
+	for (
+		curs->gen = gen, curs->node = (curs->node ? curs->node->next : head);
+		curs->node;
+		curs->node = curs->node->next
+	) {
+		if (curs->node->id && curs->node->id != id) continue;
+		if (!strncasecmp(curs->node->str, prefix, len)) return curs->node->str;
+	}
+	return NULL;
+}
+
+const char *completeSubstr(struct Cursor *curs, uint id, const char *substr) {
+	if (curs->gen != gen) curs->node = NULL;
+	for (
+		curs->gen = gen, curs->node = (curs->node ? curs->node->next : head);
+		curs->node;
+		curs->node = curs->node->next
+	) {
+		if (curs->node->id && curs->node->id != id) continue;
+		if (strstr(curs->node->str, substr)) return curs->node->str;
+	}
+	return NULL;
+}
+
+uint completeNextID(struct Cursor *curs, const char *str) {
+	if (curs->gen != gen) curs->node = NULL;
+	for (
+		curs->gen = gen, curs->node = (curs->node ? curs->node->next : head);
+		curs->node;
+		curs->node = curs->node->next
+	) {
+		if (!curs->node->id) continue;
+		if (!strcmp(curs->node->str, str)) return curs->node->id;
+	}
+	return None;
+}
+
+void completeAccept(struct Cursor *curs) {
+	if (curs->gen == gen && curs->node) {
+		prepend(detach(curs->node));
+	}
+	curs->node = NULL;
+}
+
+void completeReject(struct Cursor *curs) {
+	curs->node = NULL;
+}
diff --git a/handle.c b/handle.c
index 74c3a28..2d09f7a 100644
--- a/handle.c
+++ b/handle.c
@@ -266,7 +266,7 @@ static void handleErrorSASLFail(struct Message *msg) {
 static void handleReplyWelcome(struct Message *msg) {
 	require(msg, false, 1);
 	set(&self.nick, msg->params[0]);
-	cacheInsert(true, Network, self.nick);
+	completePull(Network, self.nick);
 	if (self.mode) ircFormat("MODE %s %s\r\n", self.nick, self.mode);
 	if (self.join) {
 		uint count = 1;
@@ -278,7 +278,7 @@ static void handleReplyWelcome(struct Message *msg) {
 		replies[ReplyTopicAuto] += count;
 		replies[ReplyNamesAuto] += count;
 	}
-	commandCache();
+	commandCompletion();
 	handleReplyGeneric(msg);
 }
 
@@ -372,13 +372,13 @@ static void handleJoin(struct Message *msg) {
 			set(&self.host, msg->host);
 		}
 		idColors[id] = hash(msg->params[0]);
-		cacheInsert(true, None, msg->params[0])->color = idColors[id];
+		completePull(None, msg->params[0]); // color = idColors[id];
 		if (replies[ReplyJoin]) {
 			windowShow(windowFor(id));
 			replies[ReplyJoin]--;
 		}
 	}
-	cacheInsert(true, id, msg->nick)->color = hash(msg->user);
+	completePull(id, msg->nick); // color = hash(msg->user);
 	if (msg->params[2] && !strcasecmp(msg->params[2], msg->nick)) {
 		msg->params[2] = NULL;
 	}
@@ -410,9 +410,9 @@ static void handlePart(struct Message *msg) {
 	require(msg, true, 1);
 	uint id = idFor(msg->params[0]);
 	if (!strcmp(msg->nick, self.nick)) {
-		cacheClear(id);
+		completeRemove(id, NULL);
 	}
-	cacheRemove(id, msg->nick);
+	completeRemove(id, msg->nick);
 	enum Heat heat = filterCheck(Cold, id, msg);
 	if (heat > Ice) urlScan(id, msg->nick, msg->params[1]);
 	uiFormat(
@@ -432,14 +432,14 @@ static void handleKick(struct Message *msg) {
 	require(msg, true, 2);
 	uint id = idFor(msg->params[0]);
 	bool kicked = !strcmp(msg->params[1], self.nick);
-	cacheInsert(true, id, msg->nick)->color = hash(msg->user);
+	completePull(id, msg->nick); // color = hash(msg->user);
 	urlScan(id, msg->nick, msg->params[2]);
 	uiFormat(
 		id, (kicked ? Hot : Cold), tagTime(msg),
 		"%s\3%02d%s\17\tkicks \3%02d%s\3 out of \3%02d%s\3%s%s",
 		(kicked ? "\26" : ""),
 		hash(msg->user), msg->nick,
-		cacheGet(id, msg->params[1])->color, msg->params[1],
+		Default /*cacheGet(id, msg->params[1])->color*/, msg->params[1],
 		hash(msg->params[0]), msg->params[0],
 		(msg->params[2] ? ": " : ""), (msg->params[2] ?: "")
 	);
@@ -448,8 +448,8 @@ static void handleKick(struct Message *msg) {
 		msg->nick, msg->params[1], msg->params[0],
 		(msg->params[2] ? ": " : ""), (msg->params[2] ?: "")
 	);
-	cacheRemove(id, msg->params[1]);
-	if (kicked) cacheClear(id);
+	completeRemove(id, msg->params[1]);
+	if (kicked) completeRemove(id, NULL);
 }
 
 static void handleNick(struct Message *msg) {
@@ -459,7 +459,7 @@ static void handleNick(struct Message *msg) {
 		inputUpdate();
 	}
 	struct Cursor curs = {0};
-	for (uint id; (id = cacheNextID(&curs, msg->nick));) {
+	for (uint id; (id = completeNextID(&curs, msg->nick));) {
 		if (!strcmp(idNames[id], msg->nick)) {
 			set(&idNames[id], msg->params[0]);
 		}
@@ -474,13 +474,13 @@ static void handleNick(struct Message *msg) {
 			msg->nick, msg->params[0]
 		);
 	}
-	cacheReplace(true, msg->nick, msg->params[0]);
+	completeReplace(msg->nick, msg->params[0]);
 }
 
 static void handleSetname(struct Message *msg) {
 	require(msg, true, 1);
 	struct Cursor curs = {0};
-	for (uint id; (id = cacheNextID(&curs, msg->nick));) {
+	for (uint id; (id = completeNextID(&curs, msg->nick));) {
 		uiFormat(
 			id, filterCheck(Cold, id, msg), tagTime(msg),
 			"\3%02d%s\3\tis now known as \3%02d%s\3 (%s\17)",
@@ -493,7 +493,7 @@ static void handleSetname(struct Message *msg) {
 static void handleQuit(struct Message *msg) {
 	require(msg, true, 0);
 	struct Cursor curs = {0};
-	for (uint id; (id = cacheNextID(&curs, msg->nick));) {
+	for (uint id; (id = completeNextID(&curs, msg->nick));) {
 		enum Heat heat = filterCheck(Cold, id, msg);
 		if (heat > Ice) urlScan(id, msg->nick, msg->params[0]);
 		uiFormat(
@@ -509,7 +509,7 @@ static void handleQuit(struct Message *msg) {
 			(msg->params[0] ? ": " : ""), (msg->params[0] ?: "")
 		);
 	}
-	cacheRemove(None, msg->nick);
+	completeRemove(None, msg->nick);
 }
 
 static void handleInvite(struct Message *msg) {
@@ -555,7 +555,7 @@ static void handleErrorUserOnChannel(struct Message *msg) {
 	uiFormat(
 		id, Warm, tagTime(msg),
 		"\3%02d%s\3 is already in \3%02d%s\3",
-		cacheGet(id, msg->params[1])->color, msg->params[1],
+		Default /*cacheGet(id, msg->params[1])->color*/, msg->params[1],
 		hash(msg->params[2]), msg->params[2]
 	);
 }
@@ -575,9 +575,12 @@ static void handleReplyNames(struct Message *msg) {
 		for (char *p = prefixes; p < nick; ++p) {
 			bits |= prefixBit(*p);
 		}
+		completePush(id, nick);
+		/*
 		struct Entry *entry = cacheInsert(false, id, nick);
 		if (user) entry->color = color;
 		entry->prefixBits = bits;
+		*/
 		if (!replies[ReplyNames] && !replies[ReplyNamesAuto]) continue;
 		ptr = seprintf(
 			ptr, end, "%s\3%02d%s\3", (ptr > buf ? ", " : ""), color, prefixes
@@ -609,24 +612,24 @@ static void handleReplyNoTopic(struct Message *msg) {
 	);
 }
 
-static void topicCache(uint id, const char *topic) {
+static void topicComplete(uint id, const char *topic) {
 	char buf[512];
 	struct Cursor curs = {0};
-	const char *prev = cacheComplete(&curs, id, "/topic ");
+	const char *prev = completePrefix(&curs, id, "/topic ");
 	if (prev) {
 		snprintf(buf, sizeof(buf), "%s", prev);
-		cacheRemove(id, buf);
+		completeRemove(id, buf);
 	}
 	if (topic) {
 		snprintf(buf, sizeof(buf), "/topic %s", topic);
-		cacheInsert(false, id, buf);
+		completePush(id, buf);
 	}
 }
 
 static void handleReplyTopic(struct Message *msg) {
 	require(msg, false, 3);
 	uint id = idFor(msg->params[1]);
-	topicCache(id, msg->params[2]);
+	topicComplete(id, msg->params[2]);
 	if (!replies[ReplyTopic] && !replies[ReplyTopicAuto]) return;
 	urlScan(id, NULL, msg->params[2]);
 	uiFormat(
@@ -677,7 +680,7 @@ static void handleTopic(struct Message *msg) {
 	require(msg, true, 2);
 	uint id = idFor(msg->params[0]);
 	if (!msg->params[1][0]) {
-		topicCache(id, NULL);
+		topicComplete(id, NULL);
 		uiFormat(
 			id, Warm, tagTime(msg),
 			"\3%02d%s\3\tremoves the sign in \3%02d%s\3",
@@ -691,7 +694,7 @@ static void handleTopic(struct Message *msg) {
 	}
 
 	struct Cursor curs = {0};
-	const char *prev = cacheComplete(&curs, id, "/topic ");
+	const char *prev = completePrefix(&curs, id, "/topic ");
 	if (prev) {
 		prev += 7;
 	} else {
@@ -741,7 +744,7 @@ log:
 		id, tagTime(msg), "%s places a new sign in %s: %s",
 		msg->nick, msg->params[0], msg->params[1]
 	);
-	topicCache(id, msg->params[1]);
+	topicComplete(id, msg->params[1]);
 	urlScan(id, msg->nick, msg->params[1]);
 }
 
@@ -864,16 +867,18 @@ static void handleMode(struct Message *msg) {
 			char prefix = network.prefixes[
 				strchr(network.prefixModes, *ch) - network.prefixModes
 			];
+			/*
 			if (set) {
 				cacheInsert(false, id, nick)->prefixBits |= prefixBit(prefix);
 			} else {
 				cacheInsert(false, id, nick)->prefixBits &= ~prefixBit(prefix);
 			}
+			*/
 			uiFormat(
 				id, Cold, tagTime(msg),
 				"\3%02d%s\3\t%s \3%02d%c%s\3 %s%s in \3%02d%s\3",
 				hash(msg->user), msg->nick, verb,
-				cacheGet(id, nick)->color, prefix, nick,
+				Default /*cacheGet(id, nick)->color*/, prefix, nick,
 				mode, name, hash(msg->params[0]), msg->params[0]
 			);
 			logFormat(
@@ -1011,7 +1016,7 @@ static void handleReplyBanList(struct Message *msg) {
 			id, Warm, tagTime(msg),
 			"Banned from \3%02d%s\3 since %s by \3%02d%s\3: %s",
 			hash(msg->params[1]), msg->params[1],
-			since, cacheGet(id, msg->params[3])->color, msg->params[3],
+			since, Default /*cacheGet(id, msg->params[3])->color*/, msg->params[3],
 			msg->params[2]
 		);
 	} else {
@@ -1034,7 +1039,7 @@ static void onList(const char *list, struct Message *msg) {
 			id, Warm, tagTime(msg),
 			"On the \3%02d%s\3 %s list since %s by \3%02d%s\3: %s",
 			hash(msg->params[1]), msg->params[1], list,
-			since, cacheGet(id, msg->params[3])->color, msg->params[3],
+			since, Default /*cacheGet(id, msg->params[3])->color*/, msg->params[3],
 			msg->params[2]
 		);
 	} else {
@@ -1067,7 +1072,7 @@ static void handleReplyList(struct Message *msg) {
 
 static void handleReplyWhoisUser(struct Message *msg) {
 	require(msg, false, 6);
-	cacheInsert(true, Network, msg->params[1])->color = hash(msg->params[2]);
+	//cacheInsert(true, Network, msg->params[1])->color = hash(msg->params[2]);
 	uiFormat(
 		Network, Warm, tagTime(msg),
 		"\3%02d%s\3\tis %s!%s@%s (%s\17)",
@@ -1082,7 +1087,7 @@ static void handleReplyWhoisServer(struct Message *msg) {
 	uiFormat(
 		Network, Warm, tagTime(msg),
 		"\3%02d%s\3\t%s connected to %s (%s)",
-		cacheGet(Network, msg->params[1])->color, msg->params[1],
+		Default /*cacheGet(Network, msg->params[1])->color*/, msg->params[1],
 		(replies[ReplyWhowas] ? "was" : "is"), msg->params[2], msg->params[3]
 	);
 }
@@ -1106,7 +1111,7 @@ static void handleReplyWhoisIdle(struct Message *msg) {
 	uiFormat(
 		Network, Warm, tagTime(msg),
 		"\3%02d%s\3\tis idle for %lu %s%s%s%s",
-		cacheGet(Network, msg->params[1])->color, msg->params[1],
+		Default /*cacheGet(Network, msg->params[1])->color*/, msg->params[1],
 		idle, unit, (idle != 1 ? "s" : ""),
 		(msg->params[3] ? ", signed on " : ""), (msg->params[3] ? signon : "")
 	);
@@ -1128,7 +1133,7 @@ static void handleReplyWhoisChannels(struct Message *msg) {
 	uiFormat(
 		Network, Warm, tagTime(msg),
 		"\3%02d%s\3\tis in %s",
-		cacheGet(Network, msg->params[1])->color, msg->params[1], buf
+		Default /*cacheGet(Network, msg->params[1])->color*/, msg->params[1], buf
 	);
 }
 
@@ -1142,21 +1147,23 @@ static void handleReplyWhoisGeneric(struct Message *msg) {
 	uiFormat(
 		Network, Warm, tagTime(msg),
 		"\3%02d%s\3\t%s%s%s",
-		cacheGet(Network, msg->params[1])->color, msg->params[1],
+		Default /*cacheGet(Network, msg->params[1])->color*/, msg->params[1],
 		msg->params[2], (msg->params[3] ? " " : ""), (msg->params[3] ?: "")
 	);
 }
 
 static void handleReplyEndOfWhois(struct Message *msg) {
 	require(msg, false, 2);
+	/*
 	if (strcmp(msg->params[1], self.nick)) {
 		cacheRemove(Network, msg->params[1]);
 	}
+	*/
 }
 
 static void handleReplyWhowasUser(struct Message *msg) {
 	require(msg, false, 6);
-	cacheInsert(true, Network, msg->params[1])->color = hash(msg->params[2]);
+	//cacheInsert(true, Network, msg->params[1])->color = hash(msg->params[2]);
 	uiFormat(
 		Network, Warm, tagTime(msg),
 		"\3%02d%s\3\twas %s!%s@%s (%s)",
@@ -1167,9 +1174,11 @@ static void handleReplyWhowasUser(struct Message *msg) {
 
 static void handleReplyEndOfWhowas(struct Message *msg) {
 	require(msg, false, 2);
+	/*
 	if (strcmp(msg->params[1], self.nick)) {
 		cacheRemove(Network, msg->params[1]);
 	}
+	*/
 }
 
 static void handleReplyAway(struct Message *msg) {
@@ -1179,7 +1188,7 @@ static void handleReplyAway(struct Message *msg) {
 	uiFormat(
 		id, (id == Network ? Warm : Cold), tagTime(msg),
 		"\3%02d%s\3\tis away: %s",
-		cacheGet(id, msg->params[1])->color, msg->params[1], msg->params[2]
+		Default /*cacheGet(id, msg->params[1])->color*/, msg->params[1], msg->params[2]
 	);
 	logFormat(
 		id, tagTime(msg), "%s is away: %s",
@@ -1250,7 +1259,7 @@ static char *colorMentions(char *ptr, char *end, uint id, const char *msg) {
 
 		size_t len = strcspn(msg, ",:<> ");
 		char *p = seprintf(ptr, end, "%.*s", (int)len, msg);
-		enum Color color = cacheGet(id, ptr)->color;
+		enum Color color = Default /*cacheGet(id, ptr)->color*/;
 		if (color != Default) {
 			ptr = seprintf(ptr, end, "\3%02d%.*s\3", color, (int)len, msg);
 		} else {
@@ -1290,7 +1299,7 @@ static void handlePrivmsg(struct Message *msg) {
 	heat = filterCheck(heat, id, msg);
 	if (heat > Warm && !mine && !query) highlight = true;
 	if (!notice && !mine && heat > Ice) {
-		cacheInsert(true, id, msg->nick)->color = hash(msg->user);
+		completePull(id, msg->nick); // color = hash(msg->user)
 	}
 	if (heat > Ice) urlScan(id, msg->nick, msg->params[1]);
 
diff --git a/input.c b/input.c
index 15f733d..b11fc22 100644
--- a/input.c
+++ b/input.c
@@ -261,12 +261,12 @@ static const struct {
 	{ L"\\wave", L"ヾ(^∇^)" },
 };
 
-void inputCache(void) {
+void inputCompletion(void) {
 	char mbs[256];
 	for (size_t i = 0; i < ARRAY_LEN(Macros); ++i) {
 		size_t n = wcstombs(mbs, Macros[i].name, sizeof(mbs));
 		assert(n != (size_t)-1);
-		cacheInsert(false, None, mbs);
+		completePush(None, mbs);
 	}
 }
 
@@ -300,13 +300,12 @@ static struct {
 } tab;
 
 static void tabAccept(void) {
-	cacheTouch(&tab.curs);
-	tab.curs = (struct Cursor) {0};
+	completeAccept(&tab.curs);
 	tab.len = 0;
 }
 
 static void tabReject(void) {
-	tab.curs = (struct Cursor) {0};
+	completeReject(&tab.curs);
 	tab.len = 0;
 }
 
@@ -334,9 +333,9 @@ static int tabComplete(struct Edit *e, uint id) {
 		tab.suffix = true;
 	}
 
-	const char *comp = cacheComplete(&tab.curs, id, tab.pre);
+	const char *comp = completePrefix(&tab.curs, id, tab.pre);
 	if (!comp) {
-		comp = cacheComplete(&tab.curs, id, tab.pre);
+		comp = completePrefix(&tab.curs, id, tab.pre);
 		tab.suffix ^= true;
 	}
 	if (!comp) {
diff --git a/window.c b/window.c
index 0c675e9..e64095a 100644
--- a/window.c
+++ b/window.c
@@ -93,7 +93,7 @@ static struct Window *windowRemove(uint num) {
 }
 
 static void windowFree(struct Window *window) {
-	cacheRemove(None, idNames[window->id]);
+	completeRemove(None, idNames[window->id]);
 	bufferFree(window->buffer);
 	free(window);
 }
@@ -118,7 +118,7 @@ uint windowFor(uint id) {
 		window->thresh = windowThreshold;
 	}
 	window->buffer = bufferAlloc();
-	cacheInsert(false, None, idNames[id])->color = idColors[id];
+	completePush(None, idNames[id]); // color = idColors[id]
 
 	return windowPush(window);
 }
@@ -477,7 +477,7 @@ void windowClose(uint num) {
 	if (num >= count) return;
 	if (windows[num]->id == Network) return;
 	struct Window *window = windowRemove(num);
-	cacheClear(window->id);
+	completeRemove(window->id, NULL);
 	windowFree(window);
 	if (swap >= num) swap--;
 	if (show == num) {