Make 0 a usable count for :tab-focus

Fixes #1768
This commit is contained in:
Florian Bruhin 2016-09-30 08:33:16 +02:00
parent 80b5c9127e
commit 822c100f52
6 changed files with 51 additions and 14 deletions

View File

@ -956,7 +956,7 @@ class CommandDispatcher:
@cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('index', choices=['last']) @cmdutils.argument('index', choices=['last'])
@cmdutils.argument('count', count=True) @cmdutils.argument('count', count=True, zero_count=True)
def tab_focus(self, index: typing.Union[str, int]=None, count=None): def tab_focus(self, index: typing.Union[str, int]=None, count=None):
"""Select the tab given as argument/[count]. """Select the tab given as argument/[count].
@ -969,6 +969,7 @@ class CommandDispatcher:
Negative indices count from the end, such that -1 is the Negative indices count from the end, such that -1 is the
last tab. last tab.
count: The tab index to focus, starting with 1. count: The tab index to focus, starting with 1.
The special value 0 focuses the rightmost tab.
""" """
if index == 'last': if index == 'last':
self._tab_focus_last() self._tab_focus_last()
@ -977,7 +978,10 @@ class CommandDispatcher:
if index is None: if index is None:
self.tab_next() self.tab_next()
return return
if index < 0: elif index == 0:
index = self._count()
elif index < 0:
index = self._count() + index + 1 index = self._count() + index + 1
if 1 <= index <= self._count(): if 1 <= index <= self._count():

View File

@ -33,12 +33,15 @@ class ArgInfo:
"""Information about an argument.""" """Information about an argument."""
def __init__(self, win_id=False, count=False, flag=None, hide=False, def __init__(self, win_id=False, count=False, hide=False, metavar=None,
metavar=None, completion=None, choices=None): zero_count=False, flag=None, completion=None, choices=None):
if win_id and count: if win_id and count:
raise TypeError("Argument marked as both count/win_id!") raise TypeError("Argument marked as both count/win_id!")
if zero_count and not count:
raise TypeError("zero_count argument cannot exist without count!")
self.win_id = win_id self.win_id = win_id
self.count = count self.count = count
self.zero_count = zero_count
self.flag = flag self.flag = flag
self.hide = hide self.hide = hide
self.metavar = metavar self.metavar = metavar
@ -48,6 +51,7 @@ class ArgInfo:
def __eq__(self, other): def __eq__(self, other):
return (self.win_id == other.win_id and return (self.win_id == other.win_id and
self.count == other.count and self.count == other.count and
self.zero_count == other.zero_count and
self.flag == other.flag and self.flag == other.flag and
self.hide == other.hide and self.hide == other.hide and
self.metavar == other.metavar and self.metavar == other.metavar and
@ -57,6 +61,7 @@ class ArgInfo:
def __repr__(self): def __repr__(self):
return utils.get_repr(self, win_id=self.win_id, count=self.count, return utils.get_repr(self, win_id=self.win_id, count=self.count,
flag=self.flag, hide=self.hide, flag=self.flag, hide=self.hide,
zero_count=self.zero_count,
metavar=self.metavar, completion=self.completion, metavar=self.metavar, completion=self.completion,
choices=self.choices, constructor=True) choices=self.choices, constructor=True)
@ -137,6 +142,7 @@ class Command:
self.opt_args = collections.OrderedDict() self.opt_args = collections.OrderedDict()
self.namespace = None self.namespace = None
self._count = None self._count = None
self._zero_count = None
self.pos_args = [] self.pos_args = []
self.desc = None self.desc = None
self.flags_with_args = [] self.flags_with_args = []
@ -148,7 +154,7 @@ class Command:
self._inspect_func() self._inspect_func()
def _check_prerequisites(self, win_id): def _check_prerequisites(self, win_id, count):
"""Check if the command is permitted to run currently. """Check if the command is permitted to run currently.
Args: Args:
@ -164,6 +170,11 @@ class Command:
"{}: Only available with {} " "{}: Only available with {} "
"backend.".format(self.name, self.backend.name)) "backend.".format(self.name, self.backend.name))
if count == 0 and not self._zero_count:
raise cmdexc.PrerequisitesError(
"{}: A zero count is not allowed for this command!"
.format(self.name))
if self.deprecated: if self.deprecated:
message.warning('{} is deprecated - {}'.format(self.name, message.warning('{} is deprecated - {}'.format(self.name,
self.deprecated)) self.deprecated))
@ -235,6 +246,9 @@ class Command:
assert param.kind != inspect.Parameter.POSITIONAL_ONLY assert param.kind != inspect.Parameter.POSITIONAL_ONLY
if param.name == 'self': if param.name == 'self':
continue continue
arg_info = self.get_arg_info(param)
if arg_info.count:
self._zero_count = arg_info.zero_count
if self._inspect_special_param(param): if self._inspect_special_param(param):
continue continue
if (param.kind == inspect.Parameter.KEYWORD_ONLY and if (param.kind == inspect.Parameter.KEYWORD_ONLY and
@ -511,7 +525,7 @@ class Command:
e.status, e)) e.status, e))
return return
self._count = count self._count = count
self._check_prerequisites(win_id) self._check_prerequisites(win_id, count)
posargs, kwargs = self._get_call_args(win_id) posargs, kwargs = self._get_call_args(win_id)
log.commands.debug('Calling {}'.format( log.commands.debug('Calling {}'.format(
debug_utils.format_call(self.handler, posargs, kwargs))) debug_utils.format_call(self.handler, posargs, kwargs)))

View File

@ -146,9 +146,6 @@ class BaseKeyParser(QObject):
(countstr, cmd_input) = re.match(r'^(\d*)(.*)', (countstr, cmd_input) = re.match(r'^(\d*)(.*)',
self._keystring).groups() self._keystring).groups()
count = int(countstr) if countstr else None count = int(countstr) if countstr else None
if count == 0 and not cmd_input:
cmd_input = self._keystring
count = None
else: else:
cmd_input = self._keystring cmd_input = self._keystring
count = None count = None

View File

@ -255,6 +255,17 @@ Feature: Tab management
- data/numbers/2.txt (active) - data/numbers/2.txt (active)
- data/numbers/3.txt - data/numbers/3.txt
Scenario: :tab-focus with count 0
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 with count 1
And I run :tab-focus with count 0
Then the following tabs should be open:
- data/numbers/1.txt
- data/numbers/2.txt
- data/numbers/3.txt (active)
Scenario: :tab-focus with invalid negative index Scenario: :tab-focus with invalid negative index
When I open data/numbers/1.txt When I open data/numbers/1.txt
And I open data/numbers/2.txt in a new tab And I open data/numbers/2.txt in a new tab

View File

@ -423,6 +423,17 @@ class TestArgument:
assert str(excinfo.value) == "Argument marked as both count/win_id!" assert str(excinfo.value) == "Argument marked as both count/win_id!"
def test_count_and_zero_count_arg(self):
with pytest.raises(TypeError) as excinfo:
@cmdutils.argument('arg', count=False, zero_count=True)
def fun(arg=0):
"""Blah."""
pass
expected = "zero_count argument cannot exist without count!"
assert str(excinfo.value) == expected
def test_no_docstring(self, caplog): def test_no_docstring(self, caplog):
with caplog.at_level(logging.WARNING): with caplog.at_level(logging.WARNING):
@cmdutils.register() @cmdutils.register()

View File

@ -246,9 +246,10 @@ class TestKeyChain:
assert keyparser._keystring == '' assert keyparser._keystring == ''
def test_0_press(self, handle_text, keyparser): def test_0_press(self, handle_text, keyparser):
handle_text((Qt.Key_0, '0')) handle_text((Qt.Key_X, '0'),
(Qt.Key_B, 'b'), (Qt.Key_A, 'a'))
keyparser.execute.assert_called_once_with( keyparser.execute.assert_called_once_with(
'0', keyparser.Type.chain, None) 'ba', keyparser.Type.chain, 0)
assert keyparser._keystring == '' assert keyparser._keystring == ''
def test_ambiguous_keychain(self, qapp, handle_text, config_stub, def test_ambiguous_keychain(self, qapp, handle_text, config_stub,
@ -314,9 +315,8 @@ class TestCount:
def test_count_0(self, handle_text, keyparser): def test_count_0(self, handle_text, keyparser):
handle_text((Qt.Key_0, '0'), (Qt.Key_B, 'b'), (Qt.Key_A, 'a')) handle_text((Qt.Key_0, '0'), (Qt.Key_B, 'b'), (Qt.Key_A, 'a'))
calls = [mock.call('0', keyparser.Type.chain, None), keyparser.execute.assert_called_once_with(
mock.call('ba', keyparser.Type.chain, None)] 'ba', keyparser.Type.chain, 0)
keyparser.execute.assert_has_calls(calls)
assert keyparser._keystring == '' assert keyparser._keystring == ''
def test_count_42(self, handle_text, keyparser): def test_count_42(self, handle_text, keyparser):