Commit Diff


commit - 91ef5ab4100ad6f754a0945d4ec468a52626ac63
commit + 65c4966563d067115fc09f5e5c8c43a917b1cf03
blob - bd2d752329a0e9cc48def01631b34b2561c5d131
blob + 6432f67d4568e5bd4e10fda23d48d6c2a7bff393
--- cmd.c
+++ cmd.c
@@ -22,6 +22,7 @@
 
 #include "compl.h"
 #include "defaults.h"
+#include "hist.h"
 #include "mcache.h"
 #include "minibuffer.h"
 #include "session.h"
@@ -326,7 +327,7 @@ cmd_push_button_new_tab(struct buffer *buffer)
 	if (vl == NULL || vl->parent->type != LINE_LINK)
 		return;
 
-	new_tab(vl->parent->alt, current_tab->hist_cur->h, current_tab);
+	new_tab(vl->parent->alt, hist_cur(current_tab->hist), current_tab);
 }
 
 void
@@ -429,7 +430,7 @@ cmd_execute_extended_command(struct buffer *buffer)
 	GUARD_RECURSIVE_MINIBUFFER();
 
 	enter_minibuffer(sensible_self_insert, eecmd_select, exit_minibuffer,
-	    &eecmd_history, compl_eecmd, NULL, 1);
+	    eecmd_history, compl_eecmd, NULL, 1);
 
 	len = sizeof(ministate.prompt);
 	strlcpy(ministate.prompt, "", len);
@@ -560,7 +561,7 @@ cmd_load_url(struct buffer *buffer)
 	GUARD_RECURSIVE_MINIBUFFER();
 
 	enter_minibuffer(sensible_self_insert, lu_select, exit_minibuffer,
-	    &lu_history, compl_lu, NULL, 0);
+	    lu_history, compl_lu, NULL, 0);
 	strlcpy(ministate.prompt, "Load URL: ", sizeof(ministate.prompt));
 }
 
@@ -570,16 +571,16 @@ cmd_load_current_url(struct buffer *buffer)
 	GUARD_RECURSIVE_MINIBUFFER();
 
 	enter_minibuffer(sensible_self_insert, lu_select, exit_minibuffer,
-	    &lu_history, compl_lu, NULL, 0);
+	    lu_history, compl_lu, NULL, 0);
 	strlcpy(ministate.prompt, "Load URL: ", sizeof(ministate.prompt));
-	strlcpy(ministate.buf, current_tab->hist_cur->h, sizeof(ministate.buf));
+	strlcpy(ministate.buf, hist_cur(current_tab->hist), sizeof(ministate.buf));
 	ministate.buffer.cpoff = utf8_cplen(ministate.buf);
 }
 
 void
 cmd_reload_page(struct buffer *buffer)
 {
-	load_url_in_tab(current_tab, current_tab->hist_cur->h, NULL,
+	load_url_in_tab(current_tab, hist_cur(current_tab->hist), NULL,
 	    LU_MODE_NOHIST|LU_MODE_NOCACHE);
 }
 
@@ -591,7 +592,8 @@ cmd_bookmark_page(struct buffer *buffer)
 	enter_minibuffer(sensible_self_insert, bp_select, exit_minibuffer, NULL,
 	    NULL, NULL, 0);
 	strlcpy(ministate.prompt, "Bookmark URL: ", sizeof(ministate.prompt));
-	strlcpy(ministate.buf, current_tab->hist_cur->h, sizeof(ministate.buf));
+	strlcpy(ministate.buf, hist_cur(current_tab->hist),
+	    sizeof(ministate.buf));
 	ministate.buffer.cpoff = utf8_cplen(ministate.buf);
 }
 
@@ -783,7 +785,7 @@ cmd_mini_complete_and_exit(struct buffer *buffer)
 	if (!in_minibuffer)
 		return;
 
-	if (ministate.compl.must_select && ministate.hist_cur == NULL) {
+	if (ministate.compl.must_select && ministate.hist == NULL) {
 		vl = ministate.compl.buffer.current_line;
 		if (vl == NULL || vl->parent->flags & L_HIDDEN ||
 		    vl->parent->type == LINE_COMPL) {
@@ -799,49 +801,47 @@ cmd_mini_complete_and_exit(struct buffer *buffer)
 void
 cmd_mini_previous_history_element(struct buffer *buffer)
 {
-	if (ministate.history == NULL) {
+	char *text;
+
+	if (ministate.hist == NULL) {
 		message("No history");
 		return;
 	}
 
-	if (ministate.hist_cur == NULL ||
-	    (ministate.hist_cur = TAILQ_PREV(ministate.hist_cur, mhisthead, entries)) == NULL) {
-		ministate.hist_cur = TAILQ_LAST(&ministate.history->head, mhisthead);
-		ministate.hist_off = ministate.history->len - 1;
-		if (ministate.hist_cur == NULL)
-			message("No prev history item");
-	} else {
-		ministate.hist_off--;
+	if (hist_prev(ministate.hist) == NULL) {
+		message("No prev history item");
+		return;
 	}
 
-	if (ministate.hist_cur != NULL) {
-		buffer->current_line->parent->line = ministate.hist_cur->h;
-		recompute_completions(0);
-	}
+	ministate.editing = 0;
+
+	/* XXX the minibuffer line is never modified so this is fine */
+	text = (char *)hist_cur(ministate.hist);
+	buffer->current_line->parent->line = text;
+	recompute_completions(0);
 }
 
 void
 cmd_mini_next_history_element(struct buffer *buffer)
 {
-	if (ministate.history == NULL) {
+	char *text;
+
+	if (ministate.hist == NULL) {
 		message("No history");
 		return;
 	}
 
-	if (ministate.hist_cur == NULL ||
-	    (ministate.hist_cur = TAILQ_NEXT(ministate.hist_cur, entries)) == NULL) {
-		ministate.hist_cur = TAILQ_FIRST(&ministate.history->head);
-		ministate.hist_off = 0;
-		if (ministate.hist_cur == NULL)
-			message("No next history item");
-	} else {
-		ministate.hist_off++;
+	if (hist_next(ministate.hist) == NULL) {
+		message("No next history item");
+		return;
 	}
 
-	if (ministate.hist_cur != NULL) {
-		buffer->current_line->parent->line = ministate.hist_cur->h;
-		recompute_completions(0);
-	}
+	ministate.editing = 0;
+
+	/* XXX the minibuffer line is never modified so this is fine */
+	text = (char *)hist_cur(ministate.hist);
+	buffer->current_line->parent->line = text;
+	recompute_completions(0);
 }
 
 void
@@ -1047,7 +1047,7 @@ cmd_write_buffer(struct buffer *buffer)
 		return;
 	}
 
-	url = current_tab->hist_cur->h;
+	url = hist_cur(current_tab->hist);
 
 	if ((f = strrchr(url, '/')) != NULL)
 		f++;
@@ -1077,8 +1077,7 @@ cmd_home(struct buffer *buffer)
 	    tilde[1] != '\0' && tilde[1] != '/') {
 		if ((t = strchr(tilde, '/')) != NULL)
 			*++t = '\0';
-		load_url_in_tab(current_tab, path, current_tab->hist_cur->h,
-		    LU_MODE_NOCACHE);
+		load_url_in_tab(current_tab, path, NULL, LU_MODE_NOCACHE);
 	} else
 		cmd_root(buffer);
 }
@@ -1086,13 +1085,11 @@ cmd_home(struct buffer *buffer)
 void
 cmd_root(struct buffer *buffer)
 {
-	load_url_in_tab(current_tab, "/", current_tab->hist_cur->h,
-	    LU_MODE_NOCACHE);
+	load_url_in_tab(current_tab, "/", NULL, LU_MODE_NOCACHE);
 }
 
 void
 cmd_up(struct buffer *buffer)
 {
-	load_url_in_tab(current_tab, "..", current_tab->hist_cur->h,
-	    LU_MODE_NOCACHE);
+	load_url_in_tab(current_tab, "..", NULL, LU_MODE_NOCACHE);
 }
blob - 27e9f1421a74a09bb2f79e3182a628e354849c82
blob + f6243324b8c79bb4caa3af734dd71f9c490da953
--- compl.c
+++ compl.c
@@ -19,6 +19,7 @@
 #include <stdlib.h>
 
 #include "compl.h"
+#include "hist.h"
 #include "telescope.h"
 #include "session.h"
 
@@ -76,8 +77,8 @@ compl_ts(void **data, void **ret, const char **descr)
 	*ret = *tab;
 
 	if (*(*tab)->buffer.page.title == '\0')
-		return (*tab)->hist_cur->h;
-	*descr = (*tab)->hist_cur->h;
+		return hist_cur((*tab)->hist);
+	*descr = hist_cur((*tab)->hist);
 	return (*tab)->buffer.page.title;
 }
 
blob - 90134a3d3e22fe68c2682313af3261d7b079d628
blob + c6eacfeded1e19b1e77d86523fac935cf8080f72
--- hist.c
+++ hist.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ * Copyright (c) 2021, 2024 Omar Polo <op@omarpolo.com>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -17,64 +17,266 @@
 #include "compat.h"
 
 #include <stdlib.h>
+#include <string.h>
 
-#include "telescope.h"
+#include "hist.h"
 
+struct hist {
+	TAILQ_HEAD(mhist, hist_item)	 head;
+	int				 flags;
+	size_t				 size;
+	ssize_t				 off;
+	struct hist_item		*cur;
+};
+
+struct hist_item {
+	char			*str;
+	size_t			 line_off;
+	size_t			 current_off;
+	TAILQ_ENTRY(hist_item)	 entries;
+};
+
+struct hist *
+hist_new(int flags)
+{
+	struct hist	*hist;
+
+	if ((hist = calloc(1, sizeof(*hist))) == NULL)
+		return (NULL);
+
+	TAILQ_INIT(&hist->head);
+	hist->flags = flags;
+	hist->off = -1;
+	return (hist);
+}
+
 void
-hist_clear(struct histhead *head)
+hist_free(struct hist *hist)
 {
-	struct hist *h, *th;
+	if (hist == NULL)
+		return;
 
-	TAILQ_FOREACH_SAFE(h, &head->head, entries, th) {
-		TAILQ_REMOVE(&head->head, h, entries);
+	hist_erase(hist);
+	free(hist);
+}
+
+static void
+hist_erase_from(struct hist *hist, struct hist_item *h)
+{
+	struct hist_item	*next;
+
+	while (h != NULL) {
+		next = TAILQ_NEXT(h, entries);
+
+		hist->size--;
+		TAILQ_REMOVE(&hist->head, h, entries);
+		free(h->str);
 		free(h);
+		h = next;
 	}
-	head->len = 0;
 }
 
 void
-hist_clear_forward(struct histhead *head, struct hist *h)
+hist_erase(struct hist *hist)
 {
-	struct hist *i;
+	hist_erase_from(hist, TAILQ_FIRST(&hist->head));
 
-	if (h == NULL)
-		return;
+	hist->size = 0;
+	hist->off = -1;
+	hist->cur = NULL;
+}
 
-	while ((i = TAILQ_NEXT(h, entries)) != NULL) {
-		TAILQ_REMOVE(&head->head, i, entries);
-		free(i);
-		head->len--;
+size_t
+hist_size(struct hist *hist)
+{
+	return (hist->size);
+}
+
+size_t
+hist_off(struct hist *hist)
+{
+	if (hist->off == -1)
+		return (0);
+	return (hist->off);
+}
+
+const char *
+hist_cur(struct hist *hist)
+{
+	if (hist->cur == NULL)
+		return (NULL);
+	return (hist->cur->str);
+}
+
+int
+hist_cur_offs(struct hist *hist, size_t *line, size_t *curr)
+{
+	*line = 0;
+	*curr = 0;
+
+	if (hist->cur == NULL)
+		return (-1);
+
+	*line = hist->cur->line_off;
+	*curr = hist->cur->current_off;
+	return (0);
+}
+
+int
+hist_set_cur(struct hist *hist, const char *str)
+{
+	char		*d;
+
+	if (hist->cur == NULL)
+		return (-1);
+
+	if ((d = strdup(str)) == NULL)
+		return (-1);
+
+	free(hist->cur->str);
+	hist->cur->str = d;
+	return (0);
+}
+
+int
+hist_set_offs(struct hist *hist, size_t line, size_t curr)
+{
+	if (hist->cur == NULL)
+		return (-1);
+
+	hist->cur->line_off = line;
+	hist->cur->current_off = line;
+	return (0);
+}
+
+const char *
+hist_nth(struct hist *hist, size_t n)
+{
+	size_t			 i;
+	struct hist_item	*h;
+
+	i = 0;
+	TAILQ_FOREACH(h, &hist->head, entries) {
+		if (i++ == n)
+			return (h->str);
 	}
+	return (NULL);
+}
 
-	TAILQ_REMOVE(&head->head, h, entries);
-	free(h);
-	head->len--;
+const char *
+hist_prev(struct hist *hist)
+{
+	struct hist_item	*h;
+	int			 wrap = hist->flags & HIST_WRAP;
+
+	if (hist->cur == NULL && !wrap)
+		return (NULL);
+
+	if (hist->cur == NULL ||
+	    (h = TAILQ_PREV(hist->cur, mhist, entries)) == NULL) {
+		if (!wrap || (h = TAILQ_LAST(&hist->head, mhist)) == NULL)
+			return (NULL);
+		hist->off = hist->size - 1;
+	} else
+		hist->off--;
+
+	hist->cur = h;
+	return (h->str);
 }
 
-void
-hist_push(struct histhead *head, struct hist *h)
+const char *
+hist_next(struct hist *hist)
 {
-	head->len++;
-	TAILQ_INSERT_TAIL(&head->head, h, entries);
+	struct hist_item	*h;
+	int			 wrap = hist->flags & HIST_WRAP;
+
+	if (hist->cur == NULL && !wrap)
+		return (NULL);
+
+	if (hist->cur == NULL ||
+	    (h = TAILQ_NEXT(hist->cur, entries)) == NULL) {
+		if (!wrap || (h = TAILQ_FIRST(&hist->head)) == NULL)
+			return (NULL);
+		hist->off = 0;
+	} else
+		hist->off++;
+
+	hist->cur = h;
+	return (h->str);
 }
 
 void
-hist_add_before(struct histhead *head, struct hist *curr, struct hist *h)
+hist_seek_start(struct hist *hist)
 {
-	head->len++;
-	TAILQ_INSERT_BEFORE(curr, h, entries);
+	hist->off = -1;
+	hist->cur = NULL;
 }
 
-struct hist *
-hist_pop(struct histhead *head)
+int
+hist_push(struct hist *hist, const char *str)
 {
-	struct hist	*h, *p;
+	struct hist_item	*h;
 
-	if ((h = TAILQ_LAST(&head->head, mhisthead)) == NULL)
-		return NULL;
-	if ((p = TAILQ_PREV(h, mhisthead, entries)) == NULL)
-		return NULL;
+	if ((h = calloc(1, sizeof(*h))) == NULL)
+		return (-1);
 
-	hist_clear_forward(head, h);
-	return p;
+	if ((h->str = strdup(str)) == NULL) {
+		free(h);
+		return (-1);
+	}
+
+	if (hist->cur != NULL)
+		hist_erase_from(hist, TAILQ_NEXT(hist->cur, entries));
+	hist->cur = h;
+	hist->off++;
+	hist->size++;
+	TAILQ_INSERT_TAIL(&hist->head, h, entries);
+	return (0);
 }
+
+int
+hist_prepend(struct hist *hist, const char *str)
+{
+	struct hist_item	*h;
+
+	if (hist->cur == NULL)
+		return (-1);
+
+	if ((h = calloc(1, sizeof(*h))) == NULL)
+		return (-1);
+
+	if ((h->str = strdup(str)) == NULL) {
+		free(h);
+		return (-1);
+	}
+
+	hist->size++;
+	hist->off++;
+	TAILQ_INSERT_BEFORE(hist->cur, h, entries);
+	return (0);
+}
+
+int
+hist_append(struct hist *hist, const char *str)
+{
+	struct hist_item	*h;
+
+	/*
+	 * Not sure.  The minibuffer needs to append even when there
+	 * are no items.
+	 */
+	if (hist->cur == NULL && !(hist->flags & HIST_WRAP))
+		return (-1);
+
+	if ((h = calloc(1, sizeof(*h))) == NULL)
+		return (-1);
+
+	if ((h->str = strdup(str)) == NULL) {
+		free(h);
+		return (-1);
+	}
+
+	hist->size++;
+	TAILQ_INSERT_TAIL(&hist->head, h, entries);
+	return (0);
+}
blob - e19d2936f5dbf647c825d2caa8bbb9d6cd7bd4ef
blob + d993896dd79343bae46a2ba6dc48362c4b802c8a
--- include/minibuffer.h
+++ include/minibuffer.h
@@ -36,10 +36,11 @@
  */
 typedef const char *(complfn)(void **, void **, const char **);
 
-extern struct histhead eecmd_history,
-	ir_history,
-	lu_history,
-	read_history;
+struct hist;
+extern struct hist *eecmd_history;
+extern struct hist *ir_history;
+extern struct hist *lu_history;
+extern struct hist *read_history;
 
 struct ministate {
 	char		*curmesg;
@@ -53,9 +54,8 @@ struct ministate {
 	struct vline	 vline;
 	struct buffer	 buffer;
 
-	struct histhead	*history;
-	struct hist	*hist_cur;
-	size_t		 hist_off;
+	struct hist	*hist;
+	int		 editing;
 
 	struct {
 		struct buffer	 buffer;
@@ -86,7 +86,7 @@ void	 swiper_select(void);
 void	 toc_select(void);
 
 void	 enter_minibuffer(void(*)(void), void(*)(void), void(*)(void),
-    struct histhead *, complfn *, void *, int);
+    struct hist *, complfn *, void *, int);
 
 void	 exit_minibuffer(void);
 void	 yornp(const char *, void (*)(int, struct tab *), struct tab *);
blob - 19ae69e25c10857032952eea8589d154d52115ee
blob + acb3a2423988a8d005fdc3feb99dc4e6348683c0
--- include/telescope.h
+++ include/telescope.h
@@ -173,17 +173,6 @@ struct tofu_entry {
 	int	verified;
 };
 
-struct histhead {
-	TAILQ_HEAD(mhisthead, hist)	head;
-	size_t				len;
-};
-struct hist {
-	char			h[1025];
-	size_t			line_off;
-	size_t			current_off;
-	TAILQ_ENTRY(hist)	entries;
-};
-
 struct buffer {
 	struct parser		 page;
 
@@ -206,6 +195,8 @@ struct buffer {
 #define TAB_LAZY	0x8	/* to lazy load tabs */
 
 #define NEW_TAB_URL	"about:new"
+
+struct hist;
 
 TAILQ_HEAD(tabshead, tab);
 extern struct tabshead tabshead;
@@ -219,9 +210,7 @@ struct tab {
 	enum trust_state	 trust;
 	struct proxy		*proxy;
 	struct iri		 iri;
-	struct histhead		 hist;
-	struct hist		*hist_cur;
-	size_t			 hist_off;
+	struct hist		*hist;
 	char			*last_input_url;
 
 	int			 code;
@@ -284,13 +273,6 @@ struct download	*download_by_id(uint32_t);
 /* help.c */
 void		 recompute_help(void);
 
-/* hist.c */
-void		 hist_clear(struct histhead *);
-void		 hist_clear_forward(struct histhead*, struct hist*);
-void		 hist_push(struct histhead*, struct hist*);
-void		 hist_add_before(struct histhead *, struct hist *, struct hist *);
-struct hist	*hist_pop(struct histhead *);
-
 /* mime.c */
 int		 setup_parser_for(struct tab*);
 
blob - /dev/null
blob + fdd02ec1da20a65b7c9f591451086fd6afcc58df (mode 644)
--- /dev/null
+++ hist.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2021, 2024 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+struct hist;
+
+#define HIST_LINEAR	0x0
+#define HIST_WRAP	0x1
+
+struct hist	*hist_new(int);
+void		 hist_free(struct hist *);
+void		 hist_erase(struct hist *);
+
+size_t		 hist_size(struct hist *);
+size_t		 hist_off(struct hist *);
+
+const char	*hist_cur(struct hist *);
+int		 hist_cur_offs(struct hist *, size_t *, size_t *);
+
+int		 hist_set_cur(struct hist *, const char *);
+int		 hist_set_offs(struct hist *, size_t, size_t);
+
+const char	*hist_nth(struct hist *, size_t);
+const char	*hist_prev(struct hist *);
+const char	*hist_next(struct hist *);
+
+void		 hist_seek_start(struct hist *);
+
+int		 hist_push(struct hist *, const char *);
+int		 hist_prepend(struct hist *, const char *);
+int		 hist_append(struct hist *, const char *);
+
blob - 15ad787db716874d579217b8bfb8b4c61a5a9890
blob + eb297a3141dc42fac25f1b19d59ee31bb68367d7
--- mcache.c
+++ mcache.c
@@ -21,6 +21,7 @@
 #include <string.h>
 
 #include "telescope.h"
+#include "hist.h"
 #include "mcache.h"
 #include "parser.h"
 #include "utils.h"
@@ -99,7 +100,7 @@ mcache_tab(struct tab *tab)
 	const char		*url;
 	FILE			*fp;
 
-	url = tab->hist_cur->h;
+	url = hist_cur(tab->hist);
 	l = strlen(url);
 	len = sizeof(*e) + l + 1;
 
blob - 3505c26ce050a82be817c9bde5ad177be56dc650
blob + ad7a389c7d41f9219d2c68bb3d40e02b30ded2c1
--- minibuffer.c
+++ minibuffer.c
@@ -23,6 +23,7 @@
 #include <string.h>
 
 #include "fs.h"
+#include "hist.h"
 #include "iri.h"
 #include "minibuffer.h"
 #include "session.h"
@@ -51,11 +52,10 @@ static struct tab *yornp_data;
 static void (*read_cb)(const char*, struct tab *);
 static struct tab *read_data;
 
-/* XXX: don't forget to init these in minibuffer_init */
-struct histhead eecmd_history,
-	ir_history,
-	lu_history,
-	read_history;
+struct hist *eecmd_history;
+struct hist *ir_history;
+struct hist *lu_history;
+struct hist *read_history;
 
 struct ministate ministate;
 
@@ -103,8 +103,8 @@ recompute_completions(int add)
 	if (in_minibuffer != MB_COMPREAD)
 		return;
 
-	if (ministate.hist_cur != NULL)
-		text = ministate.hist_cur->h;
+	if (!ministate.editing)
+		text = hist_cur(ministate.hist);
 	else
 		text = ministate.buf;
 
@@ -176,8 +176,8 @@ minibuffer_compl_text(void)
 {
 	struct vline	*vl;
 
-	if (ministate.hist_cur != NULL)
-		return ministate.hist_cur->h;
+	if (!ministate.editing)
+		return hist_cur(ministate.hist);
 
 	vl = ministate.compl.buffer.current_line;
 	if (vl == NULL || vl->parent->flags & L_HIDDEN ||
@@ -189,20 +189,10 @@ minibuffer_compl_text(void)
 static void
 minibuffer_hist_save_entry(void)
 {
-	struct hist	*hist;
-	const char	*t;
-
-	if (ministate.history == NULL)
+	if (ministate.hist == NULL)
 		return;
 
-	if ((hist = calloc(1, sizeof(*hist))) == NULL)
-		abort();
-
-	t = minibuffer_compl_text();
-	strlcpy(hist->h, t, sizeof(hist->h));
-
-	TAILQ_INSERT_TAIL(&ministate.history->head, hist, entries);
-	ministate.history->len++;
+	hist_append(ministate.hist, minibuffer_compl_text());
 }
 
 /*
@@ -213,11 +203,12 @@ minibuffer_hist_save_entry(void)
 void
 minibuffer_taint_hist(void)
 {
-	if (ministate.hist_cur == NULL)
+	if (ministate.editing)
 		return;
 
-	strlcpy(ministate.buf, ministate.hist_cur->h, sizeof(ministate.buf));
-	ministate.hist_cur = NULL;
+	ministate.editing = 1;
+	strlcpy(ministate.buf, hist_cur(ministate.hist),
+	    sizeof(ministate.buf));
 	ministate.buffer.current_line->parent->line = ministate.buf;
 }
 
@@ -285,7 +276,7 @@ ir_select_gemini(void)
 
 	minibuffer_hist_save_entry();
 
-	if (iri_parse(NULL, tab->hist_cur->h, &iri) == -1)
+	if (iri_parse(NULL, hist_cur(tab->hist), &iri) == -1)
 		goto err;
 	if (iri_setquery(&iri, minibuffer_compl_text()) == -1)
 		goto err;
@@ -332,7 +323,7 @@ lu_select(void)
 	char url[GEMINI_URL_LEN+1];
 
 	minibuffer_hist_save_entry();
-	humanify_url(minibuffer_compl_text(), current_tab->hist_cur->h,
+	humanify_url(minibuffer_compl_text(), hist_cur(current_tab->hist),
 	    url, sizeof(url));
 
 	exit_minibuffer();
@@ -514,7 +505,7 @@ populate_compl_buffer(complfn *fn, void *data)
 
 void
 enter_minibuffer(void (*self_insert_fn)(void), void (*donefn)(void),
-    void (*abortfn)(void), struct histhead *hist,
+    void (*abortfn)(void), struct hist *hist,
     complfn *complfn, void *compldata, int must_select)
 {
 	ministate.compl.must_select = must_select;
@@ -540,9 +531,10 @@ enter_minibuffer(void (*self_insert_fn)(void), void (*
 	ministate.buffer.cpoff = 0;
 	strlcpy(ministate.buf, "", sizeof(ministate.prompt));
 
-	ministate.history = hist;
-	ministate.hist_cur = NULL;
-	ministate.hist_off = 0;
+	ministate.editing = 1;
+	ministate.hist = hist;
+	if (ministate.hist)
+		hist_seek_start(ministate.hist);
 }
 
 void
@@ -591,7 +583,7 @@ minibuffer_read(const char *prompt, void (*fn)(const c
 	read_cb = fn;
 	read_data = data;
 	enter_minibuffer(read_self_insert, read_select, read_abort,
-	    &read_history, NULL, NULL, 0);
+	    read_history, NULL, NULL, 0);
 
 	len = sizeof(ministate.prompt);
 	strlcpy(ministate.prompt, prompt, len);
@@ -641,10 +633,11 @@ message(const char *fmt, ...)
 void
 minibuffer_init(void)
 {
-	TAILQ_INIT(&eecmd_history.head);
-	TAILQ_INIT(&ir_history.head);
-	TAILQ_INIT(&lu_history.head);
-	TAILQ_INIT(&read_history.head);
+	if ((eecmd_history = hist_new(HIST_WRAP)) == NULL ||
+	    (ir_history = hist_new(HIST_WRAP)) == NULL ||
+	    (lu_history = hist_new(HIST_WRAP)) == NULL ||
+	    (read_history = hist_new(HIST_WRAP)) == NULL)
+		err(1, "hist_new");
 
 	TAILQ_INIT(&ministate.compl.buffer.head);
 	TAILQ_INIT(&ministate.compl.buffer.page.head);
blob - 202790522d830e1e67c26e02f7719f0c893744b2
blob + 792a676e94ed7fc9dbe717a6bbc97d808856896f
--- parser/parser.c
+++ parser/parser.c
@@ -19,6 +19,7 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include "hist.h"
 #include "parser.h"
 #include "telescope.h"
 
@@ -70,7 +71,7 @@ parser_free(struct tab *tab)
 	 * heuristic: see if there is a "tilde user" and use that as
 	 * page title, using the full domain name as fallback.
 	 */
-	if ((tilde = strstr(tab->hist_cur->h, "/~")) != NULL) {
+	if ((tilde = strstr(hist_cur(tab->hist), "/~")) != NULL) {
 		strlcpy(tab->buffer.page.title, tilde+1,
 		    sizeof(tab->buffer.page.title));
 
blob - 21d12efffa439e96ca4a468420ba5df14a0d8507
blob + c17f3b88353b860490d35ebc7d8832291d82c0fb
--- session.c
+++ session.c
@@ -27,6 +27,7 @@
 
 #include "defaults.h"
 #include "fs.h"
+#include "hist.h"
 #include "minibuffer.h"
 #include "session.h"
 #include "ui.h"
@@ -42,7 +43,8 @@ switch_to_tab(struct tab *tab)
 	tab->flags &= ~TAB_URGENT;
 
 	if (operating && tab->flags & TAB_LAZY)
-		load_url_in_tab(tab, tab->hist_cur->h, NULL, LU_MODE_NOHIST);
+		load_url_in_tab(tab, hist_cur(tab->hist), NULL,
+		    LU_MODE_NOHIST);
 }
 
 unsigned int
@@ -66,7 +68,12 @@ new_tab(const char *url, const char *base, struct tab 
 		return NULL;
 	}
 
-	TAILQ_INIT(&tab->hist.head);
+	if ((tab->hist = hist_new(HIST_LINEAR)) == NULL) {
+		free(tab);
+		event_loopbreak();
+		return NULL;
+	}
+
 	TAILQ_INIT(&tab->buffer.head);
 	TAILQ_INIT(&tab->buffer.page.head);
 	evtimer_set(&tab->loadingev, NULL, NULL);
@@ -152,7 +159,7 @@ void
 free_tab(struct tab *tab)
 {
 	TAILQ_REMOVE(&ktabshead, tab, tabs);
-	hist_clear(&tab->hist);
+	hist_free(tab->hist);
 	free(tab);
 }
 
@@ -165,13 +172,12 @@ stop_tab(struct tab *tab)
 static inline void
 savetab(FILE *fp, struct tab *tab, int killed)
 {
-	struct hist	*h;
+	size_t		 i, size, cur;
 	size_t		 top_line, current_line;
-	int		 future;
 
 	get_scroll_position(tab, &top_line, &current_line);
 
-	fprintf(fp, "%s ", tab->hist_cur->h);
+	fprintf(fp, "%s ", hist_cur(tab->hist));
 	if (tab == current_tab)
 		fprintf(fp, "current,");
 	if (killed)
@@ -180,14 +186,13 @@ savetab(FILE *fp, struct tab *tab, int killed)
 	fprintf(fp, "top=%zu,cur=%zu %s\n", top_line, current_line,
 	    tab->buffer.page.title);
 
-	future = 0;
-	TAILQ_FOREACH(h, &tab->hist.head, entries) {
-		if (h == tab->hist_cur) {
-			future = 1;
+	cur = hist_off(tab->hist);
+	size = hist_size(tab->hist);
+	for (i = 0; i < size; ++i) {
+		if (i == cur)
 			continue;
-		}
-
-		fprintf(fp, "%s %s\n", future ? ">" : "<", h->h);
+		fprintf(fp, "%s %s\n", i > cur ? ">" : "<",
+		    hist_nth(tab->hist, i));
 	}
 }
 
@@ -589,8 +594,7 @@ parse_tab_line(char *line, struct tab **ct)
 
 	if ((tab = new_tab(uri, NULL, NULL)) == NULL)
 		err(1, "new_tab");
-	tab->hist_cur->line_off = tline;
-	tab->hist_cur->current_off = cline;
+	hist_set_offs(tab->hist, tline, cline);
 	strlcpy(tab->buffer.page.title, title, sizeof(tab->buffer.page.title));
 
 	if (current)
@@ -605,7 +609,6 @@ static void
 load_tabs(void)
 {
 	struct tab	*tab = NULL, *ct = NULL;
-	struct hist	*h;
 	FILE		*session;
 	size_t		 lineno = 0, linesize = 0;
 	ssize_t		 linelen;
@@ -632,14 +635,10 @@ load_tabs(void)
 			}
 			uri++;
 
-			if ((h = calloc(1, sizeof(*h))) == NULL)
-				err(1, "calloc");
-			strlcpy(h->h, uri, sizeof(h->h));
-
 			if (*line == '>') /* future hist */
-				hist_push(&tab->hist, h);
+				hist_append(tab->hist, uri);
 			else
-				hist_add_before(&tab->hist, tab->hist_cur, h);
+				hist_prepend(tab->hist, uri);
 		} else
 			tab = parse_tab_line(line, &ct);
 	}
blob - beb58f6449ac23f84e91a9788a99ee68f61b3806
blob + 5be7a5c39e9ff36b14dce25662daf2b6bab164cd
--- telescope.c
+++ telescope.c
@@ -33,6 +33,7 @@
 #include "control.h"
 #include "defaults.h"
 #include "fs.h"
+#include "hist.h"
 #include "iri.h"
 #include "mcache.h"
 #include "minibuffer.h"
@@ -191,7 +192,7 @@ handle_imsg_err(struct imsg *imsg, size_t datalen)
 	page[datalen-1] = '\0';
 
 	if (asprintf(&page, "# Error loading %s\n\n> %s\n",
-	    tab->hist_cur->h, page) == -1)
+	    hist_cur(tab->hist), page) == -1)
 		die();
 	load_page_from_str(tab, page);
 	free(page);
@@ -392,24 +393,21 @@ handle_imsg_got_meta(struct imsg *imsg, size_t datalen
 		load_page_from_str(tab, err_pages[tab->code]);
 	} else if (tab->code < 20) {	/* 1x */
 		free(tab->last_input_url);
-		tab->last_input_url = strdup(tab->hist_cur->h);
+		tab->last_input_url = strdup(hist_cur(tab->hist));
 		if (tab->last_input_url == NULL)
 			die();
 
 		load_page_from_str(tab, err_pages[tab->code]);
 		ui_require_input(tab, tab->code == 11, ir_select_gemini);
 	} else if (tab->code == 20) {
-		history_add(tab->hist_cur->h);
+		history_add(hist_cur(tab->hist));
 		if (setup_parser_for(tab)) {
 			ui_send_net(IMSG_PROCEED, tab->id, NULL, 0);
 		} else if (safe_mode) {
 			load_page_from_str(tab,
 			    err_pages[UNKNOWN_TYPE_OR_CSET]);
 		} else {
-			struct hist *h;
-
-			if ((h = hist_pop(&tab->hist)) != NULL)
-				tab->hist_cur = h;
+			hist_prev(tab->hist);
 			snprintf(buf, sizeof(buf),
 			    "Can't display \"%s\", save it?", tab->meta);
 			ui_yornp(buf, handle_maybe_save_page, tab);
@@ -422,7 +420,7 @@ handle_imsg_got_meta(struct imsg *imsg, size_t datalen
 			load_page_from_str(tab,
 			    err_pages[TOO_MUCH_REDIRECTS]);
 		} else
-			do_load_url(tab, tab->meta, tab->hist_cur->h,
+			do_load_url(tab, tab->meta, hist_cur(tab->hist),
 			    LU_MODE_NOCACHE);
 	} else { /* 4x, 5x & 6x */
 		load_page_from_str(tab, err_pages[tab->code]);
@@ -509,6 +507,7 @@ handle_imsg_eof(struct imsg *imsg, size_t datalen)
 {
 	struct tab	*tab = NULL;
 	struct download	*d = NULL;
+	const char	*h;
 
 	if ((tab = tab_by_id(imsg->hdr.peerid)) == NULL &&
 	    (d = download_by_id(imsg->hdr.peerid)) == NULL)
@@ -517,8 +516,9 @@ handle_imsg_eof(struct imsg *imsg, size_t datalen)
 	if (tab != NULL) {
 		if (!parser_free(tab))
 			die();
-		if (!strncmp(tab->hist_cur->h, "gemini://", 9) ||
-		    !strncmp(tab->hist_cur->h, "gopher://", 9))
+		h = hist_cur(tab->hist);
+		if (!strncmp(h, "gemini://", 9) ||
+		    !strncmp(h, "gopher://", 9))
 			mcache_tab(tab);
 		ui_on_tab_refresh(tab);
 		ui_on_tab_loaded(tab);
@@ -594,7 +594,7 @@ load_gemini_url(struct tab *tab, const char *url)
 	strlcpy(req.host, tab->iri.iri_host, sizeof(req.host));
 	strlcpy(req.port, tab->iri.iri_portstr, sizeof(req.port));
 
-	make_request(tab, &req, PROTO_GEMINI, tab->hist_cur->h);
+	make_request(tab, &req, PROTO_GEMINI, hist_cur(tab->hist));
 }
 
 static inline const char *
@@ -679,7 +679,7 @@ load_via_proxy(struct tab *tab, const char *url, struc
 
 	tab->proxy = p;
 
-	make_request(tab, &req, p->proto, tab->hist_cur->h);
+	make_request(tab, &req, p->proto, hist_cur(tab->hist));
 }
 
 static void
@@ -746,6 +746,7 @@ do_load_url(struct tab *tab, const char *url, const ch
 	struct proxy	*proxy;
 	int		 nocache = mode & LU_MODE_NOCACHE;
 	char		*t;
+	char		 buf[1025];
 
 	tab->proxy = NULL;
 	tab->trust = TS_UNKNOWN;
@@ -754,15 +755,16 @@ do_load_url(struct tab *tab, const char *url, const ch
 		if (asprintf(&t, "# error loading %s\n>%s\n",
 		    url, "Can't parse the IRI") == -1)
 			die();
-		strlcpy(tab->hist_cur->h, url, sizeof(tab->hist_cur->h));
+		hist_set_cur(tab->hist, url);
 		load_page_from_str(tab, t);
 		free(t);
 		return;
 	}
 
-	iri_unparse(&tab->iri, tab->hist_cur->h, sizeof(tab->hist_cur->h));
+	iri_unparse(&tab->iri, buf, sizeof(buf));
+	hist_set_cur(tab->hist, buf);
 
-	if (!nocache && mcache_lookup(tab->hist_cur->h, tab)) {
+	if (!nocache && mcache_lookup(buf, tab)) {
 		ui_on_tab_refresh(tab);
 		ui_on_tab_loaded(tab);
 		return;
@@ -775,7 +777,7 @@ do_load_url(struct tab *tab, const char *url, const ch
 			    p->port != NULL)
 				iri_setport(&tab->iri, p->port);
 
-			p->loadfn(tab, tab->hist_cur->h);
+			p->loadfn(tab, buf);
 			return;
 		}
 	}
@@ -797,34 +799,26 @@ do_load_url(struct tab *tab, const char *url, const ch
 void
 load_url(struct tab *tab, const char *url, const char *base, int mode)
 {
+	size_t line_off, curr_off;
 	int lazy = tab->flags & TAB_LAZY;
-	int nohist = mode & LU_MODE_NOHIST;
+	int dohist = !(mode & LU_MODE_NOHIST);
 
 	if (operating && lazy) {
 		tab->flags ^= TAB_LAZY;
 		lazy = 0;
-	} else if (tab->hist_cur != NULL)
-		get_scroll_position(tab, &tab->hist_cur->line_off,
-		    &tab->hist_cur->current_off);
+	} else if (hist_size(tab->hist) != 0) {
+		get_scroll_position(tab, &line_off, &curr_off);
+		hist_set_offs(tab->hist, line_off, curr_off);
+	}
 
-	if (!nohist && (!lazy || tab->hist_cur == NULL)) {
-		if (tab->hist_cur != NULL)
-			hist_clear_forward(&tab->hist,
-			    TAILQ_NEXT(tab->hist_cur, entries));
-
-		if ((tab->hist_cur = calloc(1, sizeof(*tab->hist_cur)))
-		    == NULL) {
+	if (dohist) {
+		if (hist_push(tab->hist, url) == -1) {
 			event_loopbreak();
 			return;
 		}
 
 		strlcpy(tab->buffer.page.title, url,
 		    sizeof(tab->buffer.page.title));
-		hist_push(&tab->hist, tab->hist_cur);
-
-		if (lazy)
-			strlcpy(tab->hist_cur->h, url,
-			    sizeof(tab->hist_cur->h));
 	}
 
 	if (!lazy)
@@ -840,7 +834,7 @@ load_url_in_tab(struct tab *tab, const char *url, cons
 	}
 
 	if (base == NULL)
-		base = tab->hist_cur->h;
+		base = hist_cur(tab->hist);
 
 	load_url(tab, url, base, mode);
 }
@@ -848,24 +842,22 @@ load_url_in_tab(struct tab *tab, const char *url, cons
 int
 load_previous_page(struct tab *tab)
 {
-	struct hist	*h;
+	const char	*h;
 
-	if ((h = TAILQ_PREV(tab->hist_cur, mhisthead, entries)) == NULL)
+	if ((h = hist_prev(tab->hist)) == NULL)
 		return 0;
-	tab->hist_cur = h;
-	do_load_url(tab, h->h, NULL, LU_MODE_NONE);
+	do_load_url(tab, h, NULL, LU_MODE_NONE);
 	return 1;
 }
 
 int
 load_next_page(struct tab *tab)
 {
-	struct hist	*h;
+	const char	*h;
 
-	if ((h = TAILQ_NEXT(tab->hist_cur, entries)) == NULL)
+	if ((h = hist_next(tab->hist)) == NULL)
 		return 0;
-	tab->hist_cur = h;
-	do_load_url(tab, h->h, NULL, LU_MODE_NONE);
+	do_load_url(tab, h, NULL, LU_MODE_NONE);
 	return 1;
 }
 
blob - 83bff426c3fc53cb1f21828c824714bebc25e481
blob + 795da31d0de4937c1a245d5b3064bb1420919f2a
--- ui.c
+++ ui.c
@@ -42,6 +42,7 @@
 #include <unistd.h>
 
 #include "defaults.h"
+#include "hist.h"
 #include "minibuffer.h"
 #include "session.h"
 #include "telescope.h"
@@ -649,7 +650,7 @@ redraw_tabline(void)
 		current = tab == current_tab;
 
 		if (*(title = tab->buffer.page.title) == '\0')
-			title = tab->hist_cur->h;
+			title = hist_cur(tab->hist);
 
 		if (tab->flags & TAB_URGENT)
 			strlcpy(buf, "!", sizeof(buf));
@@ -865,7 +866,7 @@ redraw_modeline(struct tab *tab)
 	wprintw(modeline, "%zu/%zu %s ",
 	    buffer->line_off + buffer->curs_y,
 	    buffer->line_max,
-	    tab->hist_cur->h);
+	    hist_cur(tab->hist));
 
 	getyx(modeline, y, x);
 	getmaxyx(modeline, max_y, max_x);
@@ -932,16 +933,16 @@ do_redraw_minibuffer(void)
 		    cmplbuf->line_max);
 
 	wprintw(echoarea, "%s", ministate.prompt);
-	if (ministate.hist_cur != NULL)
+	if (!ministate.editing)
 		wprintw(echoarea, "(%zu/%zu) ",
-		    ministate.hist_off + 1,
-		    ministate.history->len);
+		    hist_off(ministate.hist) + 1,
+		    hist_size(ministate.hist));
 
 	getyx(echoarea, off_y, off_x);
 
-	start = ministate.hist_cur != NULL
-		? ministate.hist_cur->h
-		: ministate.buf;
+	start = ministate.buf;
+	if (!ministate.editing)
+		start = hist_cur(ministate.hist);
 	line = buffer->current_line->parent->line + buffer->current_line->from;
 	c = utf8_nth(line, buffer->cpoff);
 	while (utf8_swidth_between(start, c) > (size_t)COLS/2) {
@@ -1229,13 +1230,15 @@ ui_main_loop(void)
 void
 ui_on_tab_loaded(struct tab *tab)
 {
+	size_t line_off, curr_off;
+
 	stop_loading_anim(tab);
-	message("Loaded %s", tab->hist_cur->h);
+	message("Loaded %s", hist_cur(tab->hist));
 
-	if (tab->hist_cur->current_off != 0 &&
+	hist_cur_offs(tab->hist, &line_off, &curr_off);
+	if (curr_off != 0 &&
 	    tab->buffer.current_line == TAILQ_FIRST(&tab->buffer.head)) {
-		set_scroll_position(tab, tab->hist_cur->line_off,
-		    tab->hist_cur->current_off);
+		set_scroll_position(tab, line_off, curr_off);
 		redraw_tab(tab);
 		return;
 	}
@@ -1326,7 +1329,7 @@ ui_require_input(struct tab *tab, int hide, void (*fn)
 	switch_to_tab(tab);
 
 	enter_minibuffer(sensible_self_insert, fn, exit_minibuffer,
-	    &ir_history, NULL, NULL, 0);
+	    ir_history, NULL, NULL, 0);
 	strlcpy(ministate.prompt, "Input required: ",
 	    sizeof(ministate.prompt));
 	redraw_tab(tab);