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:
parent
69d772cbee
commit
7b3839b44b
@ -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
|
||||
|
||||
|
@ -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),
|
||||
])),
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user