Ensure completions are sorted after filtering.
I previously removed the sorting logic from SortFilter thinking it was unnecessary if we construct the model with a sorted list. However, this only worked when no pattern was set, and the items are misordered as soon as a pattern is input. This patch reintroduces alpha-sorting, which can be disabled by passing sort=False to the ListCategory constructor. The session completion test had to be tweaked as it simulated the incorrect assumption that the session list is not alpha-ordered; sessions come out of the session-manager pre-sorted so we may as well use alpha-sorting in the session completion model. Resolves #3156.
This commit is contained in:
parent
4c2aeb01a8
commit
47447c047a
@ -29,7 +29,7 @@ def option(*, info):
|
||||
model = completionmodel.CompletionModel(column_widths=(20, 70, 10))
|
||||
options = ((opt.name, opt.description, info.config.get_str(opt.name))
|
||||
for opt in configdata.DATA.values())
|
||||
model.add_category(listcategory.ListCategory("Options", sorted(options)))
|
||||
model.add_category(listcategory.ListCategory("Options", options))
|
||||
return model
|
||||
|
||||
|
||||
@ -39,7 +39,7 @@ def customized_option(*, info):
|
||||
options = ((opt.name, opt.description, info.config.get_str(opt.name))
|
||||
for opt, _value in info.config)
|
||||
model.add_category(listcategory.ListCategory("Customized options",
|
||||
sorted(options)))
|
||||
options))
|
||||
return model
|
||||
|
||||
|
||||
@ -66,8 +66,7 @@ def value(optname, *_values, info):
|
||||
|
||||
vals = opt.typ.complete()
|
||||
if vals is not None:
|
||||
model.add_category(listcategory.ListCategory("Completions",
|
||||
sorted(vals)))
|
||||
model.add_category(listcategory.ListCategory("Completions", vals))
|
||||
return model
|
||||
|
||||
|
||||
|
@ -31,7 +31,7 @@ class ListCategory(QSortFilterProxyModel):
|
||||
|
||||
"""Expose a list of items as a category for the CompletionModel."""
|
||||
|
||||
def __init__(self, name, items, delete_func=None, parent=None):
|
||||
def __init__(self, name, items, sort=True, delete_func=None, parent=None):
|
||||
super().__init__(parent)
|
||||
self.name = name
|
||||
self.srcmodel = QStandardItemModel(parent=self)
|
||||
@ -43,6 +43,7 @@ class ListCategory(QSortFilterProxyModel):
|
||||
self.srcmodel.appendRow([QStandardItem(x) for x in item])
|
||||
self.setSourceModel(self.srcmodel)
|
||||
self.delete_func = delete_func
|
||||
self._sort = sort
|
||||
|
||||
def set_pattern(self, val):
|
||||
"""Setter for pattern.
|
||||
@ -60,19 +61,33 @@ class ListCategory(QSortFilterProxyModel):
|
||||
sortcol = 0
|
||||
self.sort(sortcol)
|
||||
|
||||
def lessThan(self, _lindex, rindex):
|
||||
def lessThan(self, lindex, rindex):
|
||||
"""Custom sorting implementation.
|
||||
|
||||
Prefers all items which start with self._pattern. Other than that, keep
|
||||
items in their original order.
|
||||
Prefers all items which start with self._pattern. Other than that, uses
|
||||
normal Python string sorting.
|
||||
|
||||
Args:
|
||||
_lindex: The QModelIndex of the left item (*left* < right)
|
||||
lindex: The QModelIndex of the left item (*left* < right)
|
||||
rindex: The QModelIndex of the right item (left < *right*)
|
||||
|
||||
Return:
|
||||
True if left < right, else False
|
||||
"""
|
||||
qtutils.ensure_valid(lindex)
|
||||
qtutils.ensure_valid(rindex)
|
||||
|
||||
left = self.srcmodel.data(lindex)
|
||||
right = self.srcmodel.data(rindex)
|
||||
return not right.startswith(self._pattern)
|
||||
|
||||
leftstart = left.startswith(self._pattern)
|
||||
rightstart = right.startswith(self._pattern)
|
||||
|
||||
if leftstart and not rightstart:
|
||||
return True
|
||||
elif rightstart and not leftstart:
|
||||
return False
|
||||
elif self._sort:
|
||||
return left < right
|
||||
else:
|
||||
return False
|
||||
|
@ -43,7 +43,7 @@ def helptopic(*, info):
|
||||
for opt in configdata.DATA.values())
|
||||
|
||||
model.add_category(listcategory.ListCategory("Commands", cmdlist))
|
||||
model.add_category(listcategory.ListCategory("Settings", sorted(settings)))
|
||||
model.add_category(listcategory.ListCategory("Settings", settings))
|
||||
return model
|
||||
|
||||
|
||||
@ -59,7 +59,8 @@ def quickmark(*, info=None): # pylint: disable=unused-argument
|
||||
model = completionmodel.CompletionModel(column_widths=(30, 70, 0))
|
||||
marks = objreg.get('quickmark-manager').marks.items()
|
||||
model.add_category(listcategory.ListCategory('Quickmarks', marks,
|
||||
delete_func=delete))
|
||||
delete_func=delete,
|
||||
sort=False))
|
||||
return model
|
||||
|
||||
|
||||
@ -75,7 +76,8 @@ def bookmark(*, info=None): # pylint: disable=unused-argument
|
||||
model = completionmodel.CompletionModel(column_widths=(30, 70, 0))
|
||||
marks = objreg.get('bookmark-manager').marks.items()
|
||||
model.add_category(listcategory.ListCategory('Bookmarks', marks,
|
||||
delete_func=delete))
|
||||
delete_func=delete,
|
||||
sort=False))
|
||||
return model
|
||||
|
||||
|
||||
|
@ -61,9 +61,9 @@ def url(*, info):
|
||||
bookmarks = objreg.get('bookmark-manager').marks.items()
|
||||
|
||||
model.add_category(listcategory.ListCategory(
|
||||
'Quickmarks', quickmarks, delete_func=_delete_quickmark))
|
||||
'Quickmarks', quickmarks, delete_func=_delete_quickmark, sort=False))
|
||||
model.add_category(listcategory.ListCategory(
|
||||
'Bookmarks', bookmarks, delete_func=_delete_bookmark))
|
||||
'Bookmarks', bookmarks, delete_func=_delete_bookmark, sort=False))
|
||||
|
||||
if info.config.get('completion.web_history_max_items') != 0:
|
||||
hist_cat = histcategory.HistoryCategory(delete_func=_delete_history)
|
||||
|
@ -24,27 +24,36 @@ import pytest
|
||||
from qutebrowser.completion.models import listcategory
|
||||
|
||||
|
||||
@pytest.mark.parametrize('pattern, before, after', [
|
||||
@pytest.mark.parametrize('pattern, before, after, after_nosort', [
|
||||
('foo',
|
||||
[('foo', ''), ('bar', '')],
|
||||
[('foo', '')],
|
||||
[('foo', '')]),
|
||||
|
||||
('foo',
|
||||
[('foob', ''), ('fooc', ''), ('fooa', '')],
|
||||
[('fooa', ''), ('foob', ''), ('fooc', '')],
|
||||
[('foob', ''), ('fooc', ''), ('fooa', '')]),
|
||||
|
||||
# prefer foobar as it starts with the pattern
|
||||
('foo',
|
||||
[('barfoo', ''), ('foobar', '')],
|
||||
[('foobar', ''), ('barfoo', '')]),
|
||||
[('barfoo', ''), ('foobaz', ''), ('foobar', '')],
|
||||
[('foobar', ''), ('foobaz', ''), ('barfoo', '')],
|
||||
[('foobaz', ''), ('foobar', ''), ('barfoo', '')]),
|
||||
|
||||
('foo',
|
||||
[('foo', 'bar'), ('bar', 'foo'), ('bar', 'bar')],
|
||||
[('foo', 'bar'), ('bar', 'foo')],
|
||||
[('foo', 'bar'), ('bar', 'foo')]),
|
||||
])
|
||||
def test_set_pattern(pattern, before, after, model_validator):
|
||||
def test_set_pattern(pattern, before, after, after_nosort, model_validator):
|
||||
"""Validate the filtering and sorting results of set_pattern."""
|
||||
cat = listcategory.ListCategory('Foo', before)
|
||||
model_validator.set_model(cat)
|
||||
cat.set_pattern(pattern)
|
||||
model_validator.validate(after)
|
||||
|
||||
cat = listcategory.ListCategory('Foo', before, sort=False)
|
||||
model_validator.set_model(cat)
|
||||
cat.set_pattern(pattern)
|
||||
model_validator.validate(after_nosort)
|
||||
|
@ -466,9 +466,9 @@ def test_session_completion(qtmodeltester, session_manager_stub):
|
||||
qtmodeltester.check(model)
|
||||
|
||||
_check_completions(model, {
|
||||
"Sessions": [('default', None, None),
|
||||
('1', None, None),
|
||||
('2', None, None)]
|
||||
"Sessions": [('1', None, None),
|
||||
('2', None, None),
|
||||
('default', None, None)]
|
||||
})
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user