diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 49503fa5d..30aa7f954 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -93,7 +93,7 @@ It is possible to run or bind multiple commands by separating them with `;;`. |<>|Switch to the previous tab, or switch [count] tabs back. |<>|Take a tab from another window. |<>|Unbind a keychain. -|<>|Re-open a closed tab. +|<>|Re-open the last closed tab or tabs. |<>|Show version information. |<>|Show the source of the current page in a new tab. |<>|Close all windows except for the current one. @@ -1065,7 +1065,7 @@ Unbind a keychain. [[undo]] === undo -Re-open a closed tab. +Re-open the last closed tab or tabs. [[version]] === version diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 5b179f9d6..95865b3bf 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -968,13 +968,15 @@ class CommandDispatcher: prev=prev, next_=next_, force=True)) return + first_tab = True for i, tab in enumerate(self._tabbed_browser.widgets()): if _to_close(i): - self._tabbed_browser.close_tab(tab) + self._tabbed_browser.close_tab(tab, new_undo=first_tab) + first_tab = False @cmdutils.register(instance='command-dispatcher', scope='window') def undo(self): - """Re-open a closed tab.""" + """Re-open the last closed tab or tabs.""" try: self._tabbed_browser.undo() except IndexError: diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 5c3b10148..2338d5dc9 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -71,7 +71,7 @@ class TabbedBrowser(tabwidget.TabWidget): _tab_insert_idx_left: Where to insert a new tab with tabs.new_tab_position set to 'prev'. _tab_insert_idx_right: Same as above, for 'next'. - _undo_stack: List of UndoEntry objects of closed tabs. + _undo_stack: List of lists of UndoEntry objects of closed tabs. shutting_down: Whether we're currently shutting down. _local_marks: Jump markers local to each page _global_marks: Jump markers used across all pages @@ -270,12 +270,13 @@ class TabbedBrowser(tabwidget.TabWidget): else: yes_action() - def close_tab(self, tab, *, add_undo=True): + def close_tab(self, tab, *, add_undo=True, new_undo=True): """Close a tab. Args: tab: The QWebView to be closed. add_undo: Whether the tab close can be undone. + new_undo: Whether the undo entry should be a new item in the stack. """ last_close = config.val.tabs.last_close count = self.count() @@ -283,7 +284,7 @@ class TabbedBrowser(tabwidget.TabWidget): if last_close == 'ignore' and count == 1: return - self._remove_tab(tab, add_undo=add_undo) + self._remove_tab(tab, add_undo=add_undo, new_undo=new_undo) if count == 1: # We just closed the last tab above. if last_close == 'close': @@ -296,12 +297,13 @@ class TabbedBrowser(tabwidget.TabWidget): elif last_close == 'default-page': self.openurl(config.val.url.default_page, newtab=True) - def _remove_tab(self, tab, *, add_undo=True, crashed=False): + def _remove_tab(self, tab, *, add_undo=True, new_undo=True, crashed=False): """Remove a tab from the tab list and delete it properly. Args: tab: The QWebView to be closed. add_undo: Whether the tab close can be undone. + new_undo: Whether the undo entry should be a new item in the stack. crashed: Whether we're closing a tab with crashed renderer process. """ idx = self.indexOf(tab) @@ -336,7 +338,10 @@ class TabbedBrowser(tabwidget.TabWidget): else: entry = UndoEntry(tab.url(), history_data, idx, tab.data.pinned) - self._undo_stack.append(entry) + if new_undo or not self._undo_stack: + self._undo_stack.append([entry]) + else: + self._undo_stack[-1].append(entry) tab.shutdown() self.removeTab(idx) @@ -347,7 +352,7 @@ class TabbedBrowser(tabwidget.TabWidget): tab.deleteLater() def undo(self): - """Undo removing of a tab.""" + """Undo removing of a tab or tabs.""" # Remove unused tab which may be created after the last tab is closed last_close = config.val.tabs.last_close use_current_tab = False @@ -366,16 +371,17 @@ class TabbedBrowser(tabwidget.TabWidget): use_current_tab = (only_one_tab_open and no_history and last_close_url_used) - entry = self._undo_stack.pop() + for entry in reversed(self._undo_stack.pop()): + if use_current_tab: + self.openurl(entry.url, newtab=False) + newtab = self.widget(0) + use_current_tab = False + else: + newtab = self.tabopen(entry.url, background=False, + idx=entry.index) - if use_current_tab: - self.openurl(entry.url, newtab=False) - newtab = self.widget(0) - else: - newtab = self.tabopen(entry.url, background=False, idx=entry.index) - - newtab.history.deserialize(entry.history) - self.set_tab_pinned(newtab, entry.pinned) + newtab.history.deserialize(entry.history) + self.set_tab_pinned(newtab, entry.pinned) @pyqtSlot('QUrl', bool) def openurl(self, url, newtab): diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index e3f2f506a..77487d33d 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -772,6 +772,18 @@ Feature: Tab management - data/numbers/2.txt - data/numbers/3.txt + Scenario: Undo the closing of tabs using :tab-only + When I open data/numbers/1.txt + And I open data/numbers/2.txt in a new tab + And I open data/numbers/3.txt in a new tab + And I run :tab-focus 2 + And I run :tab-only + And I run :undo + Then the following tabs should be open: + - data/numbers/1.txt (active) + - data/numbers/2.txt + - data/numbers/3.txt + # tabs.last_close # FIXME:qtwebengine