Focus completion by category.

Implement `completion-item-focus next-category` and
`completion-item-focus prev-category` to jump through completions by
category rather than by item.

Resolves #1567.
This commit is contained in:
Ryan Roden-Corrent 2016-08-08 19:11:27 -04:00
parent 69d772cbee
commit 7b3839b44b
3 changed files with 94 additions and 31 deletions

View File

@ -181,14 +181,44 @@ class CompletionView(QTreeView):
# Item is a real item, not a category header -> success
return idx
def _next_category_idx(self, upwards):
"""Get the index of the previous/next category.
Args:
upwards: Get previous item, not next.
Return:
A QModelIndex.
"""
idx = self.selectionModel().currentIndex()
if not idx.isValid():
return self._next_idx(upwards).sibling(0, 0)
idx = idx.parent()
direction = -1 if upwards else 1
while True:
idx = idx.sibling(idx.row() + direction, 0)
if not idx.isValid() and upwards:
# wrap around to the first item of the last category
return self.model().last_item().sibling(0, 0)
elif not idx.isValid() and not upwards:
# wrap around to the first item of the first category
idx = self.model().first_item()
self.scrollTo(idx.parent())
return idx
elif idx.isValid() and idx.child(0, 0).isValid():
# scroll to ensure the category is visible
self.scrollTo(idx)
return idx.child(0, 0)
@cmdutils.register(instance='completion', hide=True,
modes=[usertypes.KeyMode.command], scope='window')
@cmdutils.argument('which', choices=['next', 'prev'])
@cmdutils.argument('which', choices=['next', 'prev', 'next-category',
'prev-category'])
def completion_item_focus(self, which):
"""Shift the focus of the completion menu to another item.
Args:
which: 'next' or 'prev'
which: 'next', 'prev', 'next-category', or 'prev-category'.
"""
# selmodel can be None if 'show' and 'auto-open' are set to False
# https://github.com/The-Compiler/qutebrowser/issues/1731
@ -196,7 +226,15 @@ class CompletionView(QTreeView):
if selmodel is None:
return
idx = self._next_idx(which == 'prev')
if which == 'next':
idx = self._next_idx(upwards=False)
elif which == 'prev':
idx = self._next_idx(upwards=True)
elif which == 'next-category':
idx = self._next_category_idx(upwards=False)
elif which == 'prev-category':
idx = self._next_category_idx(upwards=True)
if not idx.isValid():
return

View File

@ -1602,6 +1602,8 @@ KEY_DATA = collections.OrderedDict([
('command-history-next', ['<Ctrl-N>']),
('completion-item-focus prev', ['<Shift-Tab>', '<Up>']),
('completion-item-focus next', ['<Tab>', '<Down>']),
('completion-item-focus next-category', ['<Ctrl-Tab>']),
('completion-item-focus prev-category', ['<Ctrl-Shift-Tab>']),
('completion-item-del', ['<Ctrl-D>']),
('command-accept', RETURN_KEYS),
])),

View File

@ -97,32 +97,56 @@ def test_maybe_resize_completion(completionview, config_stub, qtbot):
completionview.maybe_resize_completion()
@pytest.mark.parametrize('tree, count, expected', [
([['Aa']], 1, 'Aa'),
([['Aa']], -1, 'Aa'),
([['Aa'], ['Ba']], 1, 'Aa'),
([['Aa'], ['Ba']], -1, 'Ba'),
([['Aa'], ['Ba']], 2, 'Ba'),
([['Aa'], ['Ba']], -2, 'Aa'),
([['Aa', 'Ab', 'Ac'], ['Ba', 'Bb'], ['Ca']], 3, 'Ac'),
([['Aa', 'Ab', 'Ac'], ['Ba', 'Bb'], ['Ca']], 4, 'Ba'),
([['Aa', 'Ab', 'Ac'], ['Ba', 'Bb'], ['Ca']], 6, 'Ca'),
([['Aa', 'Ab', 'Ac'], ['Ba', 'Bb'], ['Ca']], 7, 'Aa'),
([['Aa', 'Ab', 'Ac'], ['Ba', 'Bb'], ['Ca']], -1, 'Ca'),
([['Aa', 'Ab', 'Ac'], ['Ba', 'Bb'], ['Ca']], -2, 'Bb'),
([['Aa', 'Ab', 'Ac'], ['Ba', 'Bb'], ['Ca']], -4, 'Ac'),
([[], ['Ba', 'Bb']], 1, 'Ba'),
([[], ['Ba', 'Bb']], -1, 'Bb'),
([[], [], ['Ca', 'Cb']], 1, 'Ca'),
([[], [], ['Ca', 'Cb']], -1, 'Cb'),
([['Aa'], []], 1, 'Aa'),
([['Aa'], []], -1, 'Aa'),
([['Aa'], [], []], 1, 'Aa'),
([['Aa'], [], []], -1, 'Aa'),
([[]], 1, None),
([[]], -1, None),
@pytest.mark.parametrize('which, tree, count, expected', [
('next', [['Aa']], 1, 'Aa'),
('prev', [['Aa']], 1, 'Aa'),
('next', [['Aa'], ['Ba']], 1, 'Aa'),
('prev', [['Aa'], ['Ba']], 1, 'Ba'),
('next', [['Aa'], ['Ba']], 2, 'Ba'),
('prev', [['Aa'], ['Ba']], 2, 'Aa'),
('next', [['Aa', 'Ab', 'Ac'], ['Ba', 'Bb'], ['Ca']], 3, 'Ac'),
('next', [['Aa', 'Ab', 'Ac'], ['Ba', 'Bb'], ['Ca']], 4, 'Ba'),
('next', [['Aa', 'Ab', 'Ac'], ['Ba', 'Bb'], ['Ca']], 6, 'Ca'),
('next', [['Aa', 'Ab', 'Ac'], ['Ba', 'Bb'], ['Ca']], 7, 'Aa'),
('prev', [['Aa', 'Ab', 'Ac'], ['Ba', 'Bb'], ['Ca']], 1, 'Ca'),
('prev', [['Aa', 'Ab', 'Ac'], ['Ba', 'Bb'], ['Ca']], 2, 'Bb'),
('prev', [['Aa', 'Ab', 'Ac'], ['Ba', 'Bb'], ['Ca']], 4, 'Ac'),
('next', [[], ['Ba', 'Bb']], 1, 'Ba'),
('prev', [[], ['Ba', 'Bb']], 1, 'Bb'),
('next', [[], [], ['Ca', 'Cb']], 1, 'Ca'),
('prev', [[], [], ['Ca', 'Cb']], 1, 'Cb'),
('next', [['Aa'], []], 1, 'Aa'),
('prev', [['Aa'], []], 1, 'Aa'),
('next', [['Aa'], [], []], 1, 'Aa'),
('prev', [['Aa'], [], []], 1, 'Aa'),
('next', [['Aa'], [], ['Ca', 'Cb']], 2, 'Ca'),
('prev', [['Aa'], [], ['Ca', 'Cb']], 1, 'Cb'),
('next', [[]], 1, None),
('prev', [[]], 1, None),
('next-category', [['Aa']], 1, 'Aa'),
('prev-category', [['Aa']], 1, 'Aa'),
('next-category', [['Aa'], ['Ba']], 1, 'Aa'),
('prev-category', [['Aa'], ['Ba']], 1, 'Ba'),
('next-category', [['Aa'], ['Ba']], 2, 'Ba'),
('prev-category', [['Aa'], ['Ba']], 2, 'Aa'),
('next-category', [['Aa', 'Ab', 'Ac'], ['Ba', 'Bb'], ['Ca']], 2, 'Ba'),
('prev-category', [['Aa', 'Ab', 'Ac'], ['Ba', 'Bb'], ['Ca']], 2, 'Ba'),
('next-category', [['Aa', 'Ab', 'Ac'], ['Ba', 'Bb'], ['Ca']], 3, 'Ca'),
('prev-category', [['Aa', 'Ab', 'Ac'], ['Ba', 'Bb'], ['Ca']], 3, 'Aa'),
('next-category', [[], ['Ba', 'Bb']], 1, 'Ba'),
('prev-category', [[], ['Ba', 'Bb']], 1, 'Ba'),
('next-category', [[], [], ['Ca', 'Cb']], 1, 'Ca'),
('prev-category', [[], [], ['Ca', 'Cb']], 1, 'Ca'),
('next-category', [[], [], ['Ca', 'Cb']], 2, 'Ca'),
('prev-category', [[], [], ['Ca', 'Cb']], 2, 'Ca'),
('next-category', [['Aa'], [], []], 1, 'Aa'),
('prev-category', [['Aa'], [], []], 1, 'Aa'),
('next-category', [['Aa'], [], ['Ca', 'Cb']], 2, 'Ca'),
('prev-category', [['Aa'], [], ['Ca', 'Cb']], 1, 'Ca'),
('next-category', [[]], 1, None),
('prev-category', [[]], 1, None),
])
def test_completion_item_focus(tree, count, expected, completionview):
def test_completion_item_focus(which, tree, count, expected, completionview):
"""Test that on_next_prev_item moves the selection properly.
Args:
@ -140,9 +164,8 @@ def test_completion_item_focus(tree, count, expected, completionview):
filtermodel = sortfilter.CompletionFilterModel(model,
parent=completionview)
completionview.set_model(filtermodel)
direction = 'prev' if count < 0 else 'next'
for _ in range(abs(count)):
completionview.completion_item_focus(direction)
for _ in range(count):
completionview.completion_item_focus(which)
idx = completionview.selectionModel().currentIndex()
assert filtermodel.data(idx) == expected