Commit Diff


commit - fff512aae8ab52742cc4622c2c069b80485b47e3
commit + 6c74799d01bfb8706f1972917da936fc2eee66da
blob - 34787152d2da79894284b909dec383aa033e419e
blob + e328ac9f7af797b1c7ef58320f2ff30a7d1ccef1
--- ChangeLog
+++ ChangeLog
@@ -1,3 +1,12 @@
+2022-01-05  Omar Polo  <op@omarpolo.com>
+
+	* defaults.c (load_default_keys): bind `u' to tab-undo-close.
+	(config_setvari): add "max-killed-tabs" to control the maximum number of killed tabs to keep.
+
+	* cmd.c (cmd_tab_undo_close): allow to re-open closed tabs.
+
+	* session.c (kill_tab): save killed tabs into a queue.
+
 2022-01-02  Omar Polo  <op@omarpolo.com>
 
 	* telescope.c (handle_imsg_session): implement persistent tab history
blob - adbdf3b9d75a5755f48b67d2e0a8137838d20c24
blob + 3d737a1f4f8e1e94d14ea6b63705227c91b032d6
--- cmd.c
+++ cmd.c
@@ -459,7 +459,7 @@ cmd_tab_close(struct buffer *buffer)
 	if ((t = TAILQ_NEXT(tab, tabs)) != NULL ||
 	    (t = TAILQ_PREV(tab, tabshead, tabs)) != NULL) {
 		switch_to_tab(t);
-		free_tab(tab);
+		kill_tab(tab);
 	} else
 		message("Can't close the only tab.");
 
@@ -474,11 +474,24 @@ cmd_tab_close_other(struct buffer *buffer)
 		if (t == current_tab)
 			continue;
 
-		free_tab(t);
+		kill_tab(t);
 	}
 }
 
 void
+cmd_tab_undo_close(struct buffer *buffer)
+{
+	struct tab *t;
+
+	if ((t = unkill_tab()) == NULL) {
+		message("No recently-closed tabs");
+		return;
+	}
+
+	switch_to_tab(t);
+}
+
+void
 cmd_tab_new(struct buffer *buffer)
 {
 	const char *url;
blob - d3e8e03401973d0af7f6105aa76cd809ea4dee79
blob + 3a94d6f4dfe7251a846e41b3996fbfdfa3da85bf
--- cmd.h
+++ cmd.h
@@ -57,6 +57,7 @@ CMD(cmd_suspend_telescope,	"Suspend the current Telesc
 CMD(cmd_swiper,			"Jump to a line using the minibuffer.");
 CMD(cmd_tab_close,		"Close the current tab.");
 CMD(cmd_tab_close_other,	"Close all tabs but the current one.");
+CMD(cmd_tab_undo_close,		"Reopen last closed tab.");
 CMD(cmd_tab_move,		"Move the current tab to the right.");
 CMD(cmd_tab_move_to,		"Move the current tab to the left.");
 CMD(cmd_tab_new,		"Open a new tab.");
blob - e20b1fc63a211b1bdf2509f5b9bbd54f6f9b1787
blob + 76189e2e1a837c7a98b1f43edda5c47f0433399b
--- defaults.c
+++ defaults.c
@@ -37,6 +37,7 @@ int fill_column = 120;
 int hide_pre_blocks = 0;
 int hide_pre_closing_line = 0;
 int hide_pre_context = 0;
+int max_killed_tabs = 10;
 int olivetti_mode = 1;
 int set_title = 1;
 int tab_bar_show = 1;
@@ -377,6 +378,8 @@ load_default_keys(void)
 
 	global_set_key("H",		cmd_previous_page);
 	global_set_key("L",		cmd_next_page);
+
+	global_set_key("u",		cmd_tab_undo_close);
 
 	/* tmp */
 	global_set_key("q",		cmd_kill_telescope);
@@ -537,6 +540,9 @@ config_setvari(const char *var, int val)
 			tab_bar_show = 0;
 		else
 			tab_bar_show = 1;
+	} else if (!strcmp(var, "max-killed-tabs")) {
+		if (val >= 0)
+			max_killed_tabs = val;
 	} else {
 		return 0;
 	}
blob - d6f309f1e81cbc284fc8df38ac3bbe09c1926ad1
blob + 177c8b83606b33ad09561dae2b66e7d0413a05db
--- defaults.h
+++ defaults.h
@@ -28,6 +28,7 @@ extern int	 fill_column;
 extern int	 hide_pre_blocks;
 extern int	 hide_pre_closing_line;
 extern int	 hide_pre_context;
+extern int	 max_killed_tabs;
 extern int	 olivetti_mode;
 extern int	 set_title;
 extern int	 tab_bar_show;
blob - 00c622fa9fc5ec86cc0db1acca0159e026fe9a1a
blob + 62b6b4f265d04a920df1f6b96c644d498c7b6ea5
--- fs.c
+++ fs.c
@@ -506,10 +506,16 @@ handle_session_tab(struct imsg *imsg, size_t datalen)
 
 	fprintf(session, "%s", tab.uri);
 
-	if (tab.flags & TAB_CURRENT)
-		fprintf(session, " current ");
-	else
+	if (tab.flags == 0)
 		fprintf(session, " - ");
+	else {
+		fprintf(session, " ");
+		if (tab.flags & TAB_CURRENT)
+			fprintf(session, "current,");
+		if (tab.flags & TAB_KILLED)
+			fprintf(session, "killed,");
+		fprintf(session, " ");
+	}
 
 	fprintf(session, "%s\n", tab.title);
 }
@@ -714,6 +720,8 @@ parse_session_line(char *line, const char **title, uin
 	while ((ap = strsep(&s, ",")) != NULL) {
 		if (!strcmp(ap, "current"))
 			*flags |= TAB_CURRENT;
+		else if (!strcmp(ap, "killed"))
+			*flags |= TAB_KILLED;
 	}
 }
 
blob - c3b8bf4d957d13e5a94dc793b4a5a7a47613696d
blob + 41ca7dcb1df7409e3b89e2e685af4c7d26be107a
--- session.c
+++ session.c
@@ -84,21 +84,67 @@ new_tab(const char *url, const char *base, struct tab 
 }
 
 /*
- * Free every resource linked to the tab, including the tab itself.
- * Removes the tab from the tablist, but doesn't update the
- * current_tab though.
+ * Move a tab from the tablist to the killed tab list and erase its
+ * contents.  NB: doesn't update the current_tab.
  */
 void
-free_tab(struct tab *tab)
+kill_tab(struct tab *tab)
 {
+	int count;
+
 	stop_tab(tab);
+	erase_buffer(&tab->buffer);
+	TAILQ_REMOVE(&tabshead, tab, tabs);
 	ui_schedule_redraw();
 	autosave_hook();
 
 	if (evtimer_pending(&tab->loadingev, NULL))
 		evtimer_del(&tab->loadingev);
 
-	TAILQ_REMOVE(&tabshead, tab, tabs);
+	TAILQ_INSERT_HEAD(&ktabshead, tab, tabs);
+
+	/* gc closed tabs */
+	count = 0;
+	TAILQ_FOREACH(tab, &ktabshead, tabs)
+		count++;
+	while (count > max_killed_tabs) {
+		count--;
+		free_tab(TAILQ_LAST(&ktabshead, tabshead));
+	}
+}
+
+/*
+ * Resurrects the lastest killed tab and returns it. The tab is already
+ * added to the tab list with the TAB_LAZY flag set.  NB: this doesn't
+ * update current_tab.
+ */
+struct tab *
+unkill_tab(void)
+{
+	struct tab *t;
+
+	if (TAILQ_EMPTY(&ktabshead))
+		return NULL;
+
+	autosave_hook();
+
+	t = TAILQ_FIRST(&ktabshead);
+	TAILQ_REMOVE(&ktabshead, t, tabs);
+	TAILQ_INSERT_TAIL(&tabshead, t, tabs);
+	t->flags |= TAB_LAZY;
+	return t;
+}
+
+/*
+ * Free every resource linked to the tab, including the tab itself.
+ * Removes the tab from the *killed* tablist, but doesn't update the
+ * current_tab though.
+ */
+void
+free_tab(struct tab *tab)
+{
+	/* TODO: free the history */
+	TAILQ_REMOVE(&ktabshead, tab, tabs);
 	free(tab);
 }
 
@@ -108,44 +154,55 @@ stop_tab(struct tab *tab)
 	ui_send_net(IMSG_STOP, tab->id, NULL, 0);
 }
 
-void
-save_session(void)
+static inline void
+sendtab(struct tab *tab, int killed)
 {
 	struct session_tab	 st;
 	struct session_tab_hist	 sth;
-	struct tab		*tab;
 	struct hist		*h;
 	int			 future;
 
-	if (safe_mode)
-		return;
+	memset(&st, 0, sizeof(st));
 
-	ui_send_fs(IMSG_SESSION_START, 0, NULL, 0);
+	if (tab == current_tab)
+		st.flags |= TAB_CURRENT;
+	if (killed)
+		st.flags |= TAB_KILLED;
 
-	TAILQ_FOREACH(tab, &tabshead, tabs) {
-		memset(&st, 0, sizeof(st));
+	strlcpy(st.uri, tab->hist_cur->h, sizeof(st.uri));
+	strlcpy(st.title, tab->buffer.page.title, sizeof(st.title));
+	ui_send_fs(IMSG_SESSION_TAB, 0, &st, sizeof(st));
 
-		if (tab == current_tab)
-			st.flags = TAB_CURRENT;
+	future = 0;
+	TAILQ_FOREACH(h, &tab->hist.head, entries) {
+		if (h == tab->hist_cur) {
+			future = 1;
+			continue;
+		}
 
-		strlcpy(st.uri, tab->hist_cur->h, sizeof(st.uri));
-		strlcpy(st.title, tab->buffer.page.title, sizeof(st.title));
-		ui_send_fs(IMSG_SESSION_TAB, 0, &st, sizeof(st));
+		memset(&sth, 0, sizeof(sth));
+		strlcpy(sth.uri, h->h, sizeof(sth.uri));
+		sth.future = future;
+		ui_send_fs(IMSG_SESSION_TAB_HIST, 0, &sth, sizeof(sth));
+	}
 
-		future = 0;
-		TAILQ_FOREACH(h, &tab->hist.head, entries) {
-			if (h == tab->hist_cur) {
-				future = 1;
-				continue;
-			}
+}
 
-			memset(&sth, 0, sizeof(sth));
-			strlcpy(sth.uri, h->h, sizeof(sth.uri));
-			sth.future = future;
-			ui_send_fs(IMSG_SESSION_TAB_HIST, 0, &sth, sizeof(sth));
-		}
-	}
+void
+save_session(void)
+{
+	struct tab		*tab;
 
+	if (safe_mode)
+		return;
+
+	ui_send_fs(IMSG_SESSION_START, 0, NULL, 0);
+
+	TAILQ_FOREACH(tab, &tabshead, tabs)
+		sendtab(tab, 0);
+	TAILQ_FOREACH(tab, &ktabshead, tabs)
+		sendtab(tab, 1);
+
 	ui_send_fs(IMSG_SESSION_END, 0, NULL, 0);
 }
 
blob - 6d2ba716953cd94537053699950aa11304f5a8c8
blob + 27af81e3dc4e84f9178b54b4636499d516f6cc25
--- session.h
+++ session.h
@@ -33,6 +33,8 @@ struct session_tab_hist {
 void		 switch_to_tab(struct tab *);
 unsigned int	 tab_new_id(void);
 struct tab	*new_tab(const char *, const char *base, struct tab *);
+void		 kill_tab(struct tab *);
+struct tab	*unkill_tab(void);
 void		 free_tab(struct tab *);
 void		 stop_tab(struct tab*);
 
blob - 9c81b8fbd2cbbad08aa49c3f9bdd02cc73516ecf
blob + 7dac83364bc3bc2eea62d67b52a8b09862b41ec4
--- telescope.c
+++ telescope.c
@@ -63,6 +63,7 @@ int			safe_mode;
 static struct imsgev	*iev_fs, *iev_net;
 
 struct tabshead		 tabshead = TAILQ_HEAD_INITIALIZER(tabshead);
+struct tabshead		 ktabshead = TAILQ_HEAD_INITIALIZER(ktabshead);
 struct proxylist	 proxies = TAILQ_HEAD_INITIALIZER(proxies);
 
 enum telescope_process {
@@ -526,6 +527,8 @@ handle_imsg_session(struct imsg *imsg, size_t datalen)
 		    sizeof(tab->buffer.page.title));
 		if (st.flags & TAB_CURRENT)
 			curr = tab;
+		if (st.flags & TAB_KILLED)
+			kill_tab(tab);
 		break;
 
 	case IMSG_SESSION_TAB_HIST:
blob - 1cb51617e81908a9f7c4f87713bf8de8bc19c253
blob + 927d3cb37b29dd04e0d0c3970be303617229a575
--- telescope.h
+++ telescope.h
@@ -204,12 +204,15 @@ struct buffer {
 };
 
 #define TAB_CURRENT	0x1	/* only for save_session */
-#define TAB_URGENT	0x2
-#define TAB_LAZY	0x4	/* to lazy load tabs */
+#define TAB_KILLED	0x2	/* only for save_session */
+#define TAB_URGENT	0x4
+#define TAB_LAZY	0x8	/* to lazy load tabs */
 
 #define NEW_TAB_URL	"about:new"
 
-extern TAILQ_HEAD(tabshead, tab) tabshead;
+TAILQ_HEAD(tabshead, tab);
+extern struct tabshead tabshead;
+extern struct tabshead ktabshead;
 struct tab {
 	TAILQ_ENTRY(tab)	 tabs;
 	uint32_t		 id;