From c297f047d224d9d681a45b5712bcb67a80044b55 Mon Sep 17 00:00:00 2001 From: Ryan Roden-Corrent Date: Wed, 31 May 2017 12:35:43 -0400 Subject: [PATCH] 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. --- qutebrowser/completion/completer.py | 37 ++++++++++++------- qutebrowser/completion/completionwidget.py | 28 ++++++-------- qutebrowser/completion/models/miscmodels.py | 1 + tests/unit/completion/test_completer.py | 9 ++--- .../unit/completion/test_completionwidget.py | 3 +- 5 files changed, 43 insertions(+), 35 deletions(-) diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py index 94c7f9352..b70325abe 100644 --- a/qutebrowser/completion/completer.py +++ b/qutebrowser/completion/completer.py @@ -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. diff --git a/qutebrowser/completion/completionwidget.py b/qutebrowser/completion/completionwidget.py index 5e207d68d..a476e70dc 100644 --- a/qutebrowser/completion/completionwidget.py +++ b/qutebrowser/completion/completionwidget.py @@ -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.""" diff --git a/qutebrowser/completion/models/miscmodels.py b/qutebrowser/completion/models/miscmodels.py index 62818f767..233ee8dcc 100644 --- a/qutebrowser/completion/models/miscmodels.py +++ b/qutebrowser/completion/models/miscmodels.py @@ -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)) diff --git a/tests/unit/completion/test_completer.py b/tests/unit/completion/test_completer.py index c407aeab8..537baad46 100644 --- a/tests/unit/completion/test_completer.py +++ b/tests/unit/completion/test_completer.py @@ -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', [ diff --git a/tests/unit/completion/test_completionwidget.py b/tests/unit/completion/test_completionwidget.py index 7985bfa5b..66f6e7f89 100644 --- a/tests/unit/completion/test_completionwidget.py +++ b/tests/unit/completion/test_completionwidget.py @@ -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')