Merge commit '419793c0b9ff4f293babea7623dcaf4787bbaa35'
This commit is contained in:
commit
c32c01ffc0
@ -153,6 +153,7 @@ Contributors, sorted by the number of commits in descending order:
|
||||
* Lamar Pavel
|
||||
* Marshall Lochbaum
|
||||
* Bruno Oliveira
|
||||
* thuck
|
||||
* Martin Tournoij
|
||||
* Imran Sobir
|
||||
* Alexander Cogneau
|
||||
@ -163,6 +164,7 @@ Contributors, sorted by the number of commits in descending order:
|
||||
* Joel Torstensson
|
||||
* Patric Schmitz
|
||||
* Tarcisio Fedrizzi
|
||||
* Jay Kamat
|
||||
* Claude
|
||||
* Fritz Reichwald
|
||||
* Corentin Julé
|
||||
@ -184,7 +186,6 @@ Contributors, sorted by the number of commits in descending order:
|
||||
* ZDarian
|
||||
* Milan Svoboda
|
||||
* John ShaggyTwoDope Jenkins
|
||||
* Jay Kamat
|
||||
* Clayton Craft
|
||||
* Peter Vilim
|
||||
* Jacob Sword
|
||||
@ -218,7 +219,6 @@ Contributors, sorted by the number of commits in descending order:
|
||||
* Jussi Timperi
|
||||
* Cosmin Popescu
|
||||
* Brian Jackson
|
||||
* thuck
|
||||
* sbinix
|
||||
* rsteube
|
||||
* neeasade
|
||||
|
@ -82,6 +82,7 @@ It is possible to run or bind multiple commands by separating them with `;;`.
|
||||
|<<tab-move,tab-move>>|Move the current tab according to the argument and [count].
|
||||
|<<tab-next,tab-next>>|Switch to the next tab, or switch [count] tabs forward.
|
||||
|<<tab-only,tab-only>>|Close all tabs except for the current one.
|
||||
|<<tab-pin,tab-pin>>|Pin/Unpin the current/[count]th tab.
|
||||
|<<tab-prev,tab-prev>>|Switch to the previous tab, or switch [count] tabs back.
|
||||
|<<unbind,unbind>>|Unbind a keychain.
|
||||
|<<undo,undo>>|Re-open a closed tab (optionally skipping [count] closed tabs).
|
||||
@ -835,7 +836,7 @@ Duplicate the current tab.
|
||||
|
||||
[[tab-close]]
|
||||
=== tab-close
|
||||
Syntax: +:tab-close [*--prev*] [*--next*] [*--opposite*]+
|
||||
Syntax: +:tab-close [*--prev*] [*--next*] [*--opposite*] [*--force*]+
|
||||
|
||||
Close the current/[count]th tab.
|
||||
|
||||
@ -844,6 +845,7 @@ Close the current/[count]th tab.
|
||||
* +*-n*+, +*--next*+: Force selecting the tab after the current tab.
|
||||
* +*-o*+, +*--opposite*+: Force selecting the tab in the opposite direction of what's configured in 'tabs->select-on-remove'.
|
||||
|
||||
* +*-f*+, +*--force*+: Avoid confirmation for pinned tabs.
|
||||
|
||||
==== count
|
||||
The tab index to close
|
||||
@ -896,13 +898,23 @@ How many tabs to switch forward.
|
||||
|
||||
[[tab-only]]
|
||||
=== tab-only
|
||||
Syntax: +:tab-only [*--prev*] [*--next*]+
|
||||
Syntax: +:tab-only [*--prev*] [*--next*] [*--force*]+
|
||||
|
||||
Close all tabs except for the current one.
|
||||
|
||||
==== optional arguments
|
||||
* +*-p*+, +*--prev*+: Keep tabs before the current.
|
||||
* +*-n*+, +*--next*+: Keep tabs after the current.
|
||||
* +*-f*+, +*--force*+: Avoid confirmation for pinned tabs.
|
||||
|
||||
[[tab-pin]]
|
||||
=== tab-pin
|
||||
Pin/Unpin the current/[count]th tab.
|
||||
|
||||
Pinning a tab shrinks it to tabs->pinned-width size. Attempting to close a pinned tab will cause a confirmation, unless --force is passed.
|
||||
|
||||
==== count
|
||||
The tab index to pin or unpin
|
||||
|
||||
[[tab-prev]]
|
||||
=== tab-prev
|
||||
|
@ -128,9 +128,11 @@
|
||||
|<<tabs-show-favicons,show-favicons>>|Whether to show favicons in the tab bar.
|
||||
|<<tabs-favicon-scale,favicon-scale>>|Scale for favicons in the tab bar. The tab size is unchanged, so big favicons also require extra `tabs->padding`.
|
||||
|<<tabs-width,width>>|The width of the tab bar if it's vertical, in px or as percentage of the window.
|
||||
|<<tabs-pinned-width,pinned-width>>|The width for pinned tabs with a horizontal tabbar, in px.
|
||||
|<<tabs-indicator-width,indicator-width>>|Width of the progress indicator (0 to disable).
|
||||
|<<tabs-tabs-are-windows,tabs-are-windows>>|Whether to open windows instead of tabs.
|
||||
|<<tabs-title-format,title-format>>|The format to use for the tab title. The following placeholders are defined:
|
||||
|<<tabs-title-format-pinned,title-format-pinned>>|The format to use for the tab title for pinned tabs. The same placeholders like for title-format are defined.
|
||||
|<<tabs-title-alignment,title-alignment>>|Alignment of the text inside of tabs
|
||||
|<<tabs-mousewheel-tab-switching,mousewheel-tab-switching>>|Switch between tabs using the mouse wheel.
|
||||
|<<tabs-padding,padding>>|Padding for tabs (top, bottom, left, right).
|
||||
@ -1221,6 +1223,12 @@ The width of the tab bar if it's vertical, in px or as percentage of the window.
|
||||
|
||||
Default: +pass:[20%]+
|
||||
|
||||
[[tabs-pinned-width]]
|
||||
=== pinned-width
|
||||
The width for pinned tabs with a horizontal tabbar, in px.
|
||||
|
||||
Default: +pass:[43]+
|
||||
|
||||
[[tabs-indicator-width]]
|
||||
=== indicator-width
|
||||
Width of the progress indicator (0 to disable).
|
||||
@ -1254,6 +1262,12 @@ The format to use for the tab title. The following placeholders are defined:
|
||||
|
||||
Default: +pass:[{index}: {title}]+
|
||||
|
||||
[[tabs-title-format-pinned]]
|
||||
=== title-format-pinned
|
||||
The format to use for the tab title for pinned tabs. The same placeholders like for title-format are defined.
|
||||
|
||||
Default: +pass:[{index}]+
|
||||
|
||||
[[tabs-title-alignment]]
|
||||
=== title-alignment
|
||||
Alignment of the text inside of tabs
|
||||
|
@ -96,6 +96,7 @@ class TabData:
|
||||
viewing_source: Set if we're currently showing a source view.
|
||||
override_target: Override for open_target for fake clicks (like hints).
|
||||
Only used for QtWebKit.
|
||||
pinned: Flag to pin the tab.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
@ -103,6 +104,7 @@ class TabData:
|
||||
self.viewing_source = False
|
||||
self.inspector = None
|
||||
self.override_target = None
|
||||
self.pinned = False
|
||||
|
||||
|
||||
class AbstractAction:
|
||||
|
@ -202,24 +202,21 @@ class CommandDispatcher:
|
||||
"{!r}!".format(conf_selection))
|
||||
return None
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
def tab_close(self, prev=False, next_=False, opposite=False, count=None):
|
||||
"""Close the current/[count]th tab.
|
||||
def _tab_close(self, tab, prev=False, next_=False, opposite=False):
|
||||
"""Helper function for tab_close be able to handle message.async.
|
||||
|
||||
Args:
|
||||
tab: Tab object to select be closed.
|
||||
prev: Force selecting the tab before the current tab.
|
||||
next_: Force selecting the tab after the current tab.
|
||||
opposite: Force selecting the tab in the opposite direction of
|
||||
what's configured in 'tabs->select-on-remove'.
|
||||
count: The tab index to close, or None
|
||||
"""
|
||||
tab = self._cntwidget(count)
|
||||
if tab is None:
|
||||
return
|
||||
tabbar = self._tabbed_browser.tabBar()
|
||||
selection_override = self._get_selection_override(prev, next_,
|
||||
opposite)
|
||||
|
||||
if selection_override is None:
|
||||
self._tabbed_browser.close_tab(tab)
|
||||
else:
|
||||
@ -228,6 +225,63 @@ class CommandDispatcher:
|
||||
self._tabbed_browser.close_tab(tab)
|
||||
tabbar.setSelectionBehaviorOnRemove(old_selection_behavior)
|
||||
|
||||
def _tab_close_prompt_if_pinned(self, tab, force, yes_action):
|
||||
"""Helper method for tab_close.
|
||||
|
||||
If tab is pinned, prompt. If everything is good, run yes_action.
|
||||
"""
|
||||
if tab.data.pinned and not force:
|
||||
message.confirm_async(
|
||||
title='Pinned Tab',
|
||||
text="Are you sure you want to close a pinned tab?",
|
||||
yes_action=yes_action, default=False)
|
||||
else:
|
||||
yes_action()
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
def tab_close(self, prev=False, next_=False, opposite=False,
|
||||
force=False, count=None):
|
||||
"""Close the current/[count]th tab.
|
||||
|
||||
Args:
|
||||
prev: Force selecting the tab before the current tab.
|
||||
next_: Force selecting the tab after the current tab.
|
||||
opposite: Force selecting the tab in the opposite direction of
|
||||
what's configured in 'tabs->select-on-remove'.
|
||||
force: Avoid confirmation for pinned tabs.
|
||||
count: The tab index to close, or None
|
||||
"""
|
||||
tab = self._cntwidget(count)
|
||||
if tab is None:
|
||||
return
|
||||
close = functools.partial(self._tab_close, tab, prev,
|
||||
next_, opposite)
|
||||
|
||||
self._tab_close_prompt_if_pinned(tab, force, close)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
name='tab-pin')
|
||||
@cmdutils.argument('count', count=True)
|
||||
def tab_pin(self, count=None):
|
||||
"""Pin/Unpin the current/[count]th tab.
|
||||
|
||||
Pinning a tab shrinks it to tabs->pinned-width size.
|
||||
Attempting to close a pinned tab will cause a confirmation,
|
||||
unless --force is passed.
|
||||
|
||||
Args:
|
||||
count: The tab index to pin or unpin, or None
|
||||
"""
|
||||
tab = self._cntwidget(count)
|
||||
if tab is None:
|
||||
return
|
||||
|
||||
to_pin = not tab.data.pinned
|
||||
tab_index = self._current_index() if count is None else count - 1
|
||||
cmdutils.check_overflow(tab_index + 1, 'int')
|
||||
self._tabbed_browser.set_tab_pinned(tab_index, to_pin)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', name='open',
|
||||
maxsplit=0, scope='window')
|
||||
@cmdutils.argument('url', completion=usertypes.Completion.url)
|
||||
@ -275,6 +329,8 @@ class CommandDispatcher:
|
||||
else:
|
||||
# Explicit count with a tab that doesn't exist.
|
||||
return
|
||||
elif curtab.data.pinned:
|
||||
message.info("Tab is pinned!")
|
||||
else:
|
||||
curtab.openurl(cur_url)
|
||||
|
||||
@ -457,6 +513,7 @@ class CommandDispatcher:
|
||||
newtab.data.keep_icon = True
|
||||
newtab.history.deserialize(history)
|
||||
newtab.zoom.set_factor(curtab.zoom.factor())
|
||||
new_tabbed_browser.set_tab_pinned(idx, curtab.data.pinned)
|
||||
return newtab
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@ -832,22 +889,36 @@ class CommandDispatcher:
|
||||
message.info("Zoom level: {}%".format(level), replace=True)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def tab_only(self, prev=False, next_=False):
|
||||
def tab_only(self, prev=False, next_=False, force=False):
|
||||
"""Close all tabs except for the current one.
|
||||
|
||||
Args:
|
||||
prev: Keep tabs before the current.
|
||||
next_: Keep tabs after the current.
|
||||
force: Avoid confirmation for pinned tabs.
|
||||
"""
|
||||
cmdutils.check_exclusive((prev, next_), 'pn')
|
||||
cur_idx = self._tabbed_browser.currentIndex()
|
||||
assert cur_idx != -1
|
||||
|
||||
def _to_close(i):
|
||||
"""Helper method to check if a tab should be closed or not."""
|
||||
return not (i == cur_idx or
|
||||
(prev and i < cur_idx) or
|
||||
(next_ and i > cur_idx))
|
||||
|
||||
# Check to see if we are closing any pinned tabs
|
||||
if not force:
|
||||
for i, tab in enumerate(self._tabbed_browser.widgets()):
|
||||
if _to_close(i) and tab.data.pinned:
|
||||
self._tab_close_prompt_if_pinned(
|
||||
tab, force,
|
||||
lambda: self.tab_only(
|
||||
prev=prev, next_=next_, force=True))
|
||||
return
|
||||
|
||||
for i, tab in enumerate(self._tabbed_browser.widgets()):
|
||||
if (i == cur_idx or (prev and i < cur_idx) or
|
||||
(next_ and i > cur_idx)):
|
||||
continue
|
||||
else:
|
||||
if _to_close(i):
|
||||
self._tabbed_browser.close_tab(tab)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
|
@ -690,6 +690,11 @@ def data(readonly=False):
|
||||
"The width of the tab bar if it's vertical, in px or as "
|
||||
"percentage of the window."),
|
||||
|
||||
('pinned-width',
|
||||
SettingValue(typ.Int(minval=10),
|
||||
'43'),
|
||||
"The width for pinned tabs with a horizontal tabbar, in px."),
|
||||
|
||||
('indicator-width',
|
||||
SettingValue(typ.Int(minval=0), '3'),
|
||||
"Width of the progress indicator (0 to disable)."),
|
||||
@ -716,6 +721,14 @@ def data(readonly=False):
|
||||
"* `{host}`: The host of the current web page.\n"
|
||||
"* `{backend}`: Either 'webkit' or 'webengine'"),
|
||||
|
||||
('title-format-pinned',
|
||||
SettingValue(typ.FormatString(
|
||||
fields=['perc', 'perc_raw', 'title', 'title_sep', 'index',
|
||||
'id', 'scroll_pos', 'host'], none_ok=True),
|
||||
'{index}'),
|
||||
"The format to use for the tab title for pinned tabs. "
|
||||
"The same placeholders like for title-format are defined."),
|
||||
|
||||
('title-alignment',
|
||||
SettingValue(typ.TextAlignment(), 'left'),
|
||||
"Alignment of the text inside of tabs"),
|
||||
@ -1716,6 +1729,7 @@ KEY_DATA = collections.OrderedDict([
|
||||
('follow-selected', RETURN_KEYS),
|
||||
('follow-selected -t', ['<Ctrl-Return>', '<Ctrl-Enter>']),
|
||||
('repeat-command', ['.']),
|
||||
('tab-pin', ['<Ctrl-p>']),
|
||||
('record-macro', ['q']),
|
||||
('run-macro', ['@']),
|
||||
])),
|
||||
|
@ -34,7 +34,8 @@ from qutebrowser.utils import (log, usertypes, utils, qtutils, objreg,
|
||||
urlutils, message, jinja)
|
||||
|
||||
|
||||
UndoEntry = collections.namedtuple('UndoEntry', ['url', 'history', 'index'])
|
||||
UndoEntry = collections.namedtuple('UndoEntry',
|
||||
['url', 'history', 'index', 'pinned'])
|
||||
|
||||
|
||||
class TabDeletedError(Exception):
|
||||
@ -244,6 +245,10 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
if last_close == 'ignore' and count == 1:
|
||||
return
|
||||
|
||||
# If we are removing a pinned tab, decrease count
|
||||
if tab.data.pinned:
|
||||
self.tabBar().pinned_count -= 1
|
||||
|
||||
self._remove_tab(tab, add_undo=add_undo)
|
||||
|
||||
if count == 1: # We just closed the last tab above.
|
||||
@ -294,7 +299,8 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
except browsertab.WebTabError:
|
||||
pass # special URL
|
||||
else:
|
||||
entry = UndoEntry(tab.url(), history_data, idx)
|
||||
entry = UndoEntry(tab.url(), history_data, idx,
|
||||
tab.data.pinned)
|
||||
self._undo_stack.append(entry)
|
||||
|
||||
tab.shutdown()
|
||||
@ -325,7 +331,7 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
use_current_tab = (only_one_tab_open and no_history and
|
||||
last_close_url_used)
|
||||
|
||||
url, history_data, idx = self._undo_stack.pop()
|
||||
url, history_data, idx, pinned = self._undo_stack.pop()
|
||||
|
||||
if use_current_tab:
|
||||
self.openurl(url, newtab=False)
|
||||
@ -334,6 +340,7 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
newtab = self.tabopen(url, background=False, idx=idx)
|
||||
|
||||
newtab.history.deserialize(history_data)
|
||||
self.set_tab_pinned(idx, pinned)
|
||||
|
||||
@pyqtSlot('QUrl', bool)
|
||||
def openurl(self, url, newtab):
|
||||
|
@ -94,6 +94,32 @@ class TabWidget(QTabWidget):
|
||||
bar.set_tab_data(idx, 'indicator-color', color)
|
||||
bar.update(bar.tabRect(idx))
|
||||
|
||||
def set_tab_pinned(self, idx, pinned, *, loading=False):
|
||||
"""Set the tab status as pinned.
|
||||
|
||||
Args:
|
||||
idx: The tab index.
|
||||
pinned: Pinned tab state to set.
|
||||
loading: Whether to ignore current data state when
|
||||
counting pinned_count.
|
||||
"""
|
||||
bar = self.tabBar()
|
||||
tab = self.widget(idx)
|
||||
|
||||
# Only modify pinned_count if we had a change
|
||||
# always modify pinned_count if we are loading
|
||||
if tab.data.pinned != pinned or loading:
|
||||
if pinned:
|
||||
bar.pinned_count += 1
|
||||
elif not pinned:
|
||||
bar.pinned_count -= 1
|
||||
|
||||
bar.set_tab_data(idx, 'pinned', pinned)
|
||||
tab.data.pinned = pinned
|
||||
self.update_tab_title(idx)
|
||||
|
||||
bar.refresh()
|
||||
|
||||
def tab_indicator_color(self, idx):
|
||||
"""Get the tab indicator color for the given index."""
|
||||
return self.tabBar().tab_indicator_color(idx)
|
||||
@ -109,12 +135,19 @@ class TabWidget(QTabWidget):
|
||||
|
||||
def update_tab_title(self, idx):
|
||||
"""Update the tab text for the given tab."""
|
||||
tab = self.widget(idx)
|
||||
fields = self.get_tab_fields(idx)
|
||||
fields['title'] = fields['title'].replace('&', '&&')
|
||||
fields['index'] = idx + 1
|
||||
|
||||
fmt = config.get('tabs', 'title-format')
|
||||
title = '' if fmt is None else fmt.format(**fields)
|
||||
fmt_pinned = config.get('tabs', 'title-format-pinned')
|
||||
|
||||
if tab.data.pinned:
|
||||
title = '' if fmt_pinned is None else fmt_pinned.format(**fields)
|
||||
else:
|
||||
title = '' if fmt is None else fmt.format(**fields)
|
||||
|
||||
self.tabBar().setTabText(idx, title)
|
||||
|
||||
def get_tab_fields(self, idx):
|
||||
@ -155,11 +188,12 @@ class TabWidget(QTabWidget):
|
||||
fields['scroll_pos'] = scroll_pos
|
||||
return fields
|
||||
|
||||
@config.change_filter('tabs', 'title-format')
|
||||
def update_tab_titles(self):
|
||||
def update_tab_titles(self, section='tabs', option='title-format'):
|
||||
"""Update all texts."""
|
||||
for idx in range(self.count()):
|
||||
self.update_tab_title(idx)
|
||||
if section == 'tabs' and option in ['title-format',
|
||||
'title-format-pinned']:
|
||||
for idx in range(self.count()):
|
||||
self.update_tab_title(idx)
|
||||
|
||||
def tabInserted(self, idx):
|
||||
"""Update titles when a tab was inserted."""
|
||||
@ -285,6 +319,7 @@ class TabBar(QTabBar):
|
||||
self._auto_hide_timer.timeout.connect(self._tabhide)
|
||||
self.setAutoFillBackground(True)
|
||||
self.set_colors()
|
||||
self.pinned_count = 0
|
||||
config_obj.changed.connect(self.set_colors)
|
||||
QTimer.singleShot(0, self._tabhide)
|
||||
config_obj.changed.connect(self.on_tab_colors_changed)
|
||||
@ -472,9 +507,31 @@ class TabBar(QTabBar):
|
||||
# get scroll buttons as soon as needed.
|
||||
size = minimum_size
|
||||
else:
|
||||
tab_width_pinned_conf = config.get('tabs', 'pinned-width')
|
||||
|
||||
try:
|
||||
pinned = self.tab_data(index, 'pinned')
|
||||
except KeyError:
|
||||
pinned = False
|
||||
|
||||
if pinned:
|
||||
size = QSize(tab_width_pinned_conf, height)
|
||||
qtutils.ensure_valid(size)
|
||||
return size
|
||||
|
||||
# If we *do* have enough space, tabs should occupy the whole window
|
||||
# width.
|
||||
width = self.width() / self.count()
|
||||
# width. If there are pinned tabs their size will be subtracted
|
||||
# from the total window width.
|
||||
# During shutdown the self.count goes down,
|
||||
# but the self.pinned_count not - this generates some odd behavior.
|
||||
# To avoid this we compare self.count against self.pinned_count.
|
||||
if self.pinned_count > 0 and self.count() > self.pinned_count:
|
||||
pinned_width = tab_width_pinned_conf * self.pinned_count
|
||||
no_pinned_width = self.width() - pinned_width
|
||||
width = no_pinned_width / (self.count() - self.pinned_count)
|
||||
else:
|
||||
width = self.width() / self.count()
|
||||
|
||||
# If width is not divisible by count, add a pixel to some tabs so
|
||||
# that there is no ugly leftover space.
|
||||
if index < self.width() % self.count():
|
||||
|
@ -195,6 +195,9 @@ class SessionManager(QObject):
|
||||
if 'scroll-pos' in user_data:
|
||||
pos = user_data['scroll-pos']
|
||||
data['scroll-pos'] = {'x': pos.x(), 'y': pos.y()}
|
||||
|
||||
data['pinned'] = tab.data.pinned
|
||||
|
||||
return data
|
||||
|
||||
def _save_tab(self, tab, active):
|
||||
@ -352,6 +355,9 @@ class SessionManager(QObject):
|
||||
pos = histentry['scroll-pos']
|
||||
user_data['scroll-pos'] = QPoint(pos['x'], pos['y'])
|
||||
|
||||
if 'pinned' in histentry:
|
||||
new_tab.data.pinned = histentry['pinned']
|
||||
|
||||
active = histentry.get('active', False)
|
||||
url = QUrl.fromEncoded(histentry['url'].encode('ascii'))
|
||||
if 'original-url' in histentry:
|
||||
@ -397,6 +403,9 @@ class SessionManager(QObject):
|
||||
self._load_tab(new_tab, tab)
|
||||
if tab.get('active', False):
|
||||
tab_to_focus = i
|
||||
if new_tab.data.pinned:
|
||||
tabbed_browser.set_tab_pinned(
|
||||
i, new_tab.data.pinned, loading=True)
|
||||
if tab_to_focus is not None:
|
||||
tabbed_browser.setCurrentIndex(tab_to_focus)
|
||||
if win.get('active', False):
|
||||
|
@ -159,8 +159,8 @@ def clean_open_tabs(quteproc):
|
||||
"""Clean up open windows and tabs."""
|
||||
quteproc.set_setting('tabs', 'last-close', 'blank')
|
||||
quteproc.send_cmd(':window-only')
|
||||
quteproc.send_cmd(':tab-only')
|
||||
quteproc.send_cmd(':tab-close')
|
||||
quteproc.send_cmd(':tab-only --force')
|
||||
quteproc.send_cmd(':tab-close --force')
|
||||
quteproc.wait_for_load_finished_url('about:blank')
|
||||
|
||||
|
||||
@ -543,31 +543,46 @@ def check_open_tabs(quteproc, request, tabs):
|
||||
"""
|
||||
session = quteproc.get_session()
|
||||
active_suffix = ' (active)'
|
||||
pinned_suffix = ' (pinned)'
|
||||
tabs = tabs.splitlines()
|
||||
assert len(session['windows']) == 1
|
||||
assert len(session['windows'][0]['tabs']) == len(tabs)
|
||||
|
||||
# If we don't have (active) anywhere, don't check it
|
||||
has_active = any(line.endswith(active_suffix) for line in tabs)
|
||||
has_active = any(active_suffix in line for line in tabs)
|
||||
has_pinned = any(pinned_suffix in line for line in tabs)
|
||||
|
||||
for i, line in enumerate(tabs):
|
||||
line = line.strip()
|
||||
assert line.startswith('- ')
|
||||
line = line[2:] # remove "- " prefix
|
||||
if line.endswith(active_suffix):
|
||||
path = line[:-len(active_suffix)]
|
||||
active = True
|
||||
else:
|
||||
path = line
|
||||
active = False
|
||||
|
||||
active = False
|
||||
pinned = False
|
||||
|
||||
while line.endswith(active_suffix) or line.endswith(pinned_suffix):
|
||||
if line.endswith(active_suffix):
|
||||
# active
|
||||
line = line[:-len(active_suffix)]
|
||||
active = True
|
||||
else:
|
||||
# pinned
|
||||
line = line[:-len(pinned_suffix)]
|
||||
pinned = True
|
||||
|
||||
session_tab = session['windows'][0]['tabs'][i]
|
||||
assert session_tab['history'][-1]['url'] == quteproc.path_to_url(path)
|
||||
current_page = session_tab['history'][-1]
|
||||
assert current_page['url'] == quteproc.path_to_url(line)
|
||||
if active:
|
||||
assert session_tab['active']
|
||||
elif has_active:
|
||||
assert 'active' not in session_tab
|
||||
|
||||
if pinned:
|
||||
assert current_page['pinned']
|
||||
elif has_pinned:
|
||||
assert not current_page['pinned']
|
||||
|
||||
|
||||
@bdd.then(bdd.parsers.re(r'the (?P<what>primary selection|clipboard) should '
|
||||
r'contain "(?P<content>.*)"'))
|
||||
|
@ -342,7 +342,7 @@ Feature: Saving and loading sessions
|
||||
Scenario: Loading a directory
|
||||
When I run :session-load (tmpdir)
|
||||
Then the error "Error while loading session: *" should be shown
|
||||
|
||||
|
||||
Scenario: Loading internal session without --force
|
||||
When I run :session-save --force _internal
|
||||
And I run :session-load _internal
|
||||
@ -367,3 +367,24 @@ Feature: Saving and loading sessions
|
||||
Scenario: Loading a session which doesn't exist
|
||||
When I run :session-load inexistent_session
|
||||
Then the error "Session inexistent_session not found!" should be shown
|
||||
|
||||
|
||||
# Test load/save of pinned tabs
|
||||
|
||||
Scenario: Saving/Loading a session with pinned tabs
|
||||
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-pin with count 2
|
||||
And I run :session-save pin_session
|
||||
And I run :tab-only --force
|
||||
And I run :tab-close --force
|
||||
And I run :session-load -c pin_session
|
||||
And I wait until data/numbers/3.txt is loaded
|
||||
And I run :tab-focus 2
|
||||
And I run :open hello world
|
||||
Then the message "Tab is pinned!" should be shown
|
||||
And the following tabs should be open:
|
||||
- data/numbers/1.txt
|
||||
- data/numbers/2.txt (active) (pinned)
|
||||
- data/numbers/3.txt
|
||||
|
@ -1026,3 +1026,121 @@ Feature: Tab management
|
||||
- tabs:
|
||||
- history:
|
||||
- url: http://localhost:*/data/hello.txt
|
||||
|
||||
# :tab-pin
|
||||
|
||||
Scenario: :tab-pin command
|
||||
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-pin
|
||||
Then the following tabs should be open:
|
||||
- data/numbers/1.txt
|
||||
- data/numbers/2.txt
|
||||
- data/numbers/3.txt (active) (pinned)
|
||||
|
||||
Scenario: :tab-pin unpin
|
||||
When I open data/numbers/1.txt
|
||||
And I run :tab-pin
|
||||
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-pin
|
||||
And I run :tab-pin
|
||||
Then the following tabs should be open:
|
||||
- data/numbers/1.txt (pinned)
|
||||
- data/numbers/2.txt
|
||||
- data/numbers/3.txt (active)
|
||||
|
||||
Scenario: :tab-pin to index 2
|
||||
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-pin with count 2
|
||||
Then the following tabs should be open:
|
||||
- data/numbers/1.txt
|
||||
- data/numbers/2.txt (pinned)
|
||||
- data/numbers/3.txt (active)
|
||||
|
||||
Scenario: Pinned :tab-close prompt yes
|
||||
When I open data/numbers/1.txt
|
||||
And I run :tab-pin
|
||||
And I open data/numbers/2.txt in a new tab
|
||||
And I run :tab-pin
|
||||
And I run :tab-close
|
||||
And I wait for "*want to close a pinned tab*" in the log
|
||||
And I run :prompt-accept yes
|
||||
Then the following tabs should be open:
|
||||
- data/numbers/1.txt (active) (pinned)
|
||||
|
||||
Scenario: Pinned :tab-close prompt no
|
||||
When I open data/numbers/1.txt
|
||||
And I run :tab-pin
|
||||
And I open data/numbers/2.txt in a new tab
|
||||
And I run :tab-pin
|
||||
And I run :tab-close
|
||||
And I wait for "*want to close a pinned tab*" in the log
|
||||
And I run :prompt-accept no
|
||||
Then the following tabs should be open:
|
||||
- data/numbers/1.txt (pinned)
|
||||
- data/numbers/2.txt (active) (pinned)
|
||||
|
||||
Scenario: Pinned :tab-only prompt yes
|
||||
When I open data/numbers/1.txt
|
||||
And I run :tab-pin
|
||||
And I open data/numbers/2.txt in a new tab
|
||||
And I run :tab-pin
|
||||
And I run :tab-next
|
||||
And I run :tab-only
|
||||
And I wait for "*want to close a pinned tab*" in the log
|
||||
And I run :prompt-accept yes
|
||||
Then the following tabs should be open:
|
||||
- data/numbers/1.txt (active) (pinned)
|
||||
|
||||
Scenario: Pinned :tab-only prompt no
|
||||
When I open data/numbers/1.txt
|
||||
And I run :tab-pin
|
||||
And I open data/numbers/2.txt in a new tab
|
||||
And I run :tab-pin
|
||||
And I run :tab-next
|
||||
And I run :tab-only
|
||||
And I wait for "*want to close a pinned tab*" in the log
|
||||
And I run :prompt-accept no
|
||||
Then the following tabs should be open:
|
||||
- data/numbers/1.txt (active) (pinned)
|
||||
- data/numbers/2.txt (pinned)
|
||||
|
||||
Scenario: Pinned :tab-only close all but pinned tab
|
||||
When I open data/numbers/1.txt
|
||||
And I open data/numbers/2.txt in a new tab
|
||||
And I run :tab-pin
|
||||
And I run :tab-only
|
||||
Then the following tabs should be open:
|
||||
- data/numbers/2.txt (active) (pinned)
|
||||
|
||||
Scenario: :tab-pin open url
|
||||
When I open data/numbers/1.txt
|
||||
And I run :tab-pin
|
||||
And I open data/numbers/2.txt without waiting
|
||||
Then the message "Tab is pinned!" should be shown
|
||||
And the following tabs should be open:
|
||||
- data/numbers/1.txt (active) (pinned)
|
||||
|
||||
Scenario: Cloning a pinned tab
|
||||
When I open data/numbers/1.txt
|
||||
And I run :tab-pin
|
||||
And I run :tab-clone
|
||||
And I wait until data/numbers/1.txt is loaded
|
||||
Then the following tabs should be open:
|
||||
- data/numbers/1.txt (pinned)
|
||||
- data/numbers/1.txt (pinned) (active)
|
||||
|
||||
Scenario: Undo a pinned tab
|
||||
When I open data/numbers/1.txt
|
||||
And I open data/numbers/2.txt in a new tab
|
||||
And I run :tab-pin
|
||||
And I run :tab-close --force
|
||||
And I run :undo
|
||||
And I wait until data/numbers/2.txt is loaded
|
||||
Then the following tabs should be open:
|
||||
- data/numbers/1.txt
|
||||
- data/numbers/2.txt (pinned) (active)
|
||||
|
@ -49,6 +49,8 @@ class TestTabWidget:
|
||||
'indicator-width': 3,
|
||||
'indicator-padding': configtypes.PaddingValues(2, 2, 0, 4),
|
||||
'title-format': '{index}: {title}',
|
||||
'title-format-pinned': '{index}',
|
||||
'pinned-width': 43,
|
||||
'title-alignment': Qt.AlignLeft,
|
||||
},
|
||||
'colors': {
|
||||
|
Loading…
Reference in New Issue
Block a user