commit 6c74799d01bfb8706f1972917da936fc2eee66da from: Omar Polo date: Wed Jan 05 15:17:32 2022 UTC allow to re-open closed tabs Save closed tabs to a "killed tabs" queue, for up to a max of "max-killed-tabs", and allow to re-open them. Keep the tab history but not its content. This integrates nicely with the session file, just add the "killed" flag as alias for TAB_KILLED and put it in the ktabshead queue upon initalization. It's thus possible to re-open closed tabs even after telescope has been closed, and tabs keeps their history! Add a new cmd, cmd_tab_undo_close, to try to re-open the most recently closed tab and bind it to `u'. While here, add a TODO for the tab history memory leak. commit - fff512aae8ab52742cc4622c2c069b80485b47e3 commit + 6c74799d01bfb8706f1972917da936fc2eee66da blob - 34787152d2da79894284b909dec383aa033e419e blob + e328ac9f7af797b1c7ef58320f2ff30a7d1ccef1 --- ChangeLog +++ ChangeLog @@ -1,3 +1,12 @@ +2022-01-05 Omar Polo + + * 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 * 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;