Don't regenerate completion model needlessly.

If the completion model would stay the same, just keep it and update the
filter pattern rather than instantiating a new model each time the
pattern changes.
This commit is contained in:
Ryan Roden-Corrent 2017-05-31 12:35:43 -04:00
parent 2cd02be7b1
commit c297f047d2
5 changed files with 43 additions and 35 deletions

View File

@ -39,6 +39,7 @@ class Completer(QObject):
_last_cursor_pos: The old cursor position so we avoid double completion
updates.
_last_text: The old command text so we avoid double completion updates.
_last_completion_func: The completion function used for the last text.
"""
def __init__(self, cmd, win_id, parent=None):
@ -52,6 +53,7 @@ class Completer(QObject):
self._timer.timeout.connect(self._update_completion)
self._last_cursor_pos = None
self._last_text = None
self._last_completion_func = None
self._cmd.update_completion.connect(self.schedule_completion_update)
def __repr__(self):
@ -63,7 +65,7 @@ class Completer(QObject):
return completion.model()
def _get_new_completion(self, before_cursor, under_cursor):
"""Get a new completion.
"""Get the completion function based on the current command text.
Args:
before_cursor: The command chunks before the cursor.
@ -81,7 +83,7 @@ class Completer(QObject):
if not before_cursor:
# '|' or 'set|'
log.completion.debug('Starting command completion')
return miscmodels.command()
return miscmodels.command
try:
cmd = cmdutils.cmd_dict[before_cursor[0]]
except KeyError:
@ -90,17 +92,11 @@ class Completer(QObject):
return None
argpos = len(before_cursor) - 1
try:
completion = cmd.get_pos_arg_info(argpos).completion
func = cmd.get_pos_arg_info(argpos).completion
except IndexError:
log.completion.debug("No completion in position {}".format(argpos))
return None
if completion is None:
return None
model = completion(*before_cursor[1:])
log.completion.debug('Starting {} completion'
.format(completion.__name__))
return model
return func
def _quote(self, s):
"""Quote s if it needs quoting for the commandline.
@ -223,9 +219,24 @@ class Completer(QObject):
before_cursor, pattern, after_cursor))
pattern = pattern.strip("'\"")
model = self._get_new_completion(before_cursor, pattern)
log.completion.debug("Setting pattern to '{}'".format(pattern))
completion.set_model(model, pattern)
func = self._get_new_completion(before_cursor, pattern)
if func is None:
log.completion.debug('Clearing completion')
completion.set_model(None)
elif func != self._last_completion_func:
self._last_completion_func = func
args = (x for x in before_cursor[1:] if not x.startswith('-'))
model = func(*args)
log.completion.debug('Starting {} completion'
.format(func.__name__))
completion.set_model(model)
completion.set_pattern(pattern)
else:
log.completion.debug('Setting pattern {}'.format(pattern))
completion.set_pattern(pattern)
self._last_completion_func = None
def _change_completed_part(self, newtext, before, after, immediate=False):
"""Change the part we're currently completing in the commandline.

View File

@ -261,32 +261,29 @@ class CompletionView(QTreeView):
elif config.get('completion', 'show') == 'auto':
self.show()
def set_model(self, model, pattern=None):
def set_model(self, model):
"""Switch completion to a new model.
Called from on_update_completion().
Args:
model: The model to use.
pattern: The filter pattern to set (what the user entered).
"""
if model is None:
self._active = False
self.hide()
return
if self.model() is not None:
self.model().deleteLater()
self.selectionModel().deleteLater()
model.setParent(self)
old_model = self.model()
if model is not old_model:
sel_model = self.selectionModel()
self.setModel(model)
self._column_widths = model.column_widths
self._active = True
self.setModel(model)
self._active = True
if sel_model is not None:
sel_model.deleteLater()
if old_model is not None:
old_model.deleteLater()
self.set_pattern('')
if (config.get('completion', 'show') == 'always' and
model.count() > 0):
@ -297,12 +294,11 @@ class CompletionView(QTreeView):
for i in range(model.rowCount()):
self.expand(model.index(i, 0))
if pattern is not None:
model.set_pattern(pattern)
self._column_widths = model.column_widths
def set_pattern(self, pattern):
self.model().set_pattern(pattern)
self._resize_columns()
self._maybe_update_geometry()
self.show()
def _maybe_update_geometry(self):
"""Emit the update_geometry signal if the config says so."""

View File

@ -137,6 +137,7 @@ def bind(_key):
_key: the key being bound.
"""
# TODO: offer a 'Current binding' completion based on the key.
print(_key)
model = completionmodel.CompletionModel(column_widths=(20, 60, 20))
cmdlist = _get_cmd_completions(include_hidden=True, include_aliases=True)
model.add_category(listcategory.ListCategory("Commands", cmdlist))

View File

@ -191,15 +191,14 @@ def test_update_completion(txt, kind, pattern, pos_args, status_command_stub,
# this test uses | as a placeholder for the current cursor position
_set_cmd_prompt(status_command_stub, txt)
completer_obj.schedule_completion_update()
assert completion_widget_stub.set_model.call_count == 1
args = completion_widget_stub.set_model.call_args[0]
if kind is None:
assert args[0] is None
assert completion_widget_stub.set_pattern.call_count == 0
else:
model = args[0]
assert completion_widget_stub.set_model.call_count == 1
model = completion_widget_stub.set_model.call_args[0][0]
assert model.kind == kind
assert model.pos_args == pos_args
assert args[1] == pattern
completion_widget_stub.set_pattern.assert_called_once_with(pattern)
@pytest.mark.parametrize('before, newtxt, after', [

View File

@ -84,7 +84,8 @@ def test_set_model(completionview):
def test_set_pattern(completionview):
model = completionmodel.CompletionModel()
model.set_pattern = mock.Mock()
completionview.set_model(model, 'foo')
completionview.set_model(model)
completionview.set_pattern('foo')
model.set_pattern.assert_called_with('foo')