From 127412d91c1221b1f6d7c764071aa5bb0d9c60a0 Mon Sep 17 00:00:00 2001 From: Ryan Roden-Corrent Date: Sat, 10 Sep 2016 21:18:59 -0400 Subject: [PATCH] Simplify update_completion. Remove the class variables _cursor_part and _empty_item_index. Instead, split up the commandline around the cursor whenever that information is needed. Using locals instead of class variables makes the logic easier to follow and ends up requiring much less code. --- qutebrowser/completion/completer.py | 157 +++++------------------- tests/unit/completion/test_completer.py | 1 - 2 files changed, 30 insertions(+), 128 deletions(-) diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py index b9f7b711f..5ed5267dd 100644 --- a/qutebrowser/completion/completer.py +++ b/qutebrowser/completion/completer.py @@ -36,7 +36,6 @@ class Completer(QObject): _ignore_change: Whether to ignore the next completion update. _win_id: The window ID this completer is in. _timer: The timer used to trigger the completion update. - _cursor_part: The cursor part index for the next completion update. _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. @@ -52,11 +51,9 @@ class Completer(QObject): self._timer.setSingleShot(True) self._timer.setInterval(0) self._timer.timeout.connect(self._update_completion) - self._cursor_part = None self._last_cursor_pos = None self._last_text = None self._cmd.update_completion.connect(self.schedule_completion_update) - self._cmd.textChanged.connect(self._on_text_edited) def __repr__(self): return utils.get_repr(self) @@ -66,23 +63,22 @@ class Completer(QObject): completion = self.parent() return completion.model() - def _get_completion_model(self, completion, parts, cursor_part): + def _get_completion_model(self, completion, pos_args): """Get a completion model based on an enum member. Args: completion: A usertypes.Completion member. - parts: The parts currently in the commandline. - cursor_part: The part the cursor is in. + pos_args: The positional args entered before the cursor. Return: A completion model or None. """ if completion == usertypes.Completion.option: - section = parts[cursor_part - 1] + section = pos_args[0] model = instances.get(completion).get(section) elif completion == usertypes.Completion.value: - section = parts[cursor_part - 2] - option = parts[cursor_part - 1] + section = pos_args[0] + option = pos_args[1] try: model = instances.get(completion)[section][option] except KeyError: @@ -96,72 +92,46 @@ class Completer(QObject): else: return sortfilter.CompletionFilterModel(source=model, parent=self) - def _filter_cmdline_parts(self, parts, cursor_part): - """Filter a list of commandline parts to exclude flags. - - Args: - parts: A list of parts. - cursor_part: The index of the part the cursor is over. - - Return: - A (parts, cursor_part) tuple with the modified values. - """ - if parts == ['']: - # Empty commandline, i.e. only :. - return [''], 0 - filtered_parts = [] - for i, part in enumerate(parts): - if part == '--': - break - elif part.startswith('-'): - if cursor_part >= i: - cursor_part -= 1 - else: - filtered_parts.append(part) - return filtered_parts, cursor_part - - def _get_new_completion(self, parts, cursor_part): + def _get_new_completion(self, before_cursor, under_cursor): """Get a new completion. Args: - parts: The command chunks to get a completion for. - cursor_part: The part the cursor is over currently. + before_cursor: The command chunks before the cursor. + under_cursor: The command chunk under the cursor. Return: A completion model. """ + if '--' in before_cursor: + return None try: - if parts[cursor_part].startswith('-'): + if under_cursor.startswith('-'): # cursor on a flag - return + return None except IndexError: pass - log.completion.debug("Before filtering flags: parts {}, cursor_part " - "{}".format(parts, cursor_part)) - parts, cursor_part = self._filter_cmdline_parts(parts, cursor_part) - log.completion.debug("After filtering flags: parts {}, cursor_part " - "{}".format(parts, cursor_part)) - if not parts: - return None - if cursor_part == 0: + log.completion.debug("Before removing flags: {}".format(before_cursor)) + before_cursor = [x for x in before_cursor if not x.startswith('-')] + log.completion.debug("After removing flags: {}".format(before_cursor)) + if len(before_cursor) == 0: # '|' or 'set|' model = instances.get(usertypes.Completion.command) return sortfilter.CompletionFilterModel(source=model, parent=self) - # delegate completion to command try: - cmd = cmdutils.cmd_dict[parts[0]] + cmd = cmdutils.cmd_dict[before_cursor[0]] except KeyError: - # entering an unknown command + log.completion.debug("No completion for unknown command: {}" + .format(before_cursor[0])) return None + argpos = len(before_cursor) - 1 try: - idx = cursor_part - 1 - completion = cmd.get_pos_arg_info(idx).completion + completion = cmd.get_pos_arg_info(argpos).completion except IndexError: - # user provided more positional arguments than the command takes + log.completion.debug("No completion in position {}".format(argpos)) return None if completion is None: return None - model = self._get_completion_model(completion, parts, cursor_part) + model = self._get_completion_model(completion, before_cursor[1:]) return model def _quote(self, s): @@ -253,12 +223,10 @@ class Completer(QObject): @pyqtSlot() def _update_completion(self): """Check if completions are available and activate them.""" - self._update_cursor_part() - parts = self._split() + before_cursor, pattern, after_cursor = self._partition() - log.completion.debug( - "Updating completion - prefix {}, parts {}, cursor_part {}".format( - self._cmd.prefix(), parts, self._cursor_part)) + log.completion.debug("Updating completion: {} {} {}".format( + before_cursor, pattern, after_cursor)) if self._ignore_change: log.completion.debug("Ignoring completion update because " @@ -276,19 +244,11 @@ class Completer(QObject): completion.set_model(None) return - model = self._get_new_completion(parts, self._cursor_part) + model = self._get_new_completion(before_cursor, pattern) - try: - pattern = parts[self._cursor_part].strip() - except IndexError: - pattern = '' - - if model is None: - log.completion.debug("No completion model for {}.".format(parts)) - else: - log.completion.debug( - "New completion for {}: {}, with pattern '{}'".format( - parts, model.srcmodel.__class__.__name__, pattern)) + log.completion.debug("Setting completion model to {} with pattern '{}'" + .format(model.srcmodel.__class__.__name__ if model else 'None', + pattern)) completion.set_model(model, pattern) @@ -318,55 +278,6 @@ class Completer(QObject): #log.completion.debug("Splitting '{}' -> {}".format(text, parts)) return parts - @pyqtSlot() - def _update_cursor_part(self): - """Get the part index of the commandline where the cursor is over.""" - cursor_pos = self._cmd.cursorPosition() - snippet = slice(cursor_pos - 1, cursor_pos + 1) - spaces = self._cmd.text()[snippet] == ' ' - cursor_pos -= len(self._cmd.prefix()) - parts = self._split(keep=True) - log.completion.vdebug( - "text: {}, parts: {}, cursor_pos after removing prefix '{}': " - "{}".format(self._cmd.text(), parts, self._cmd.prefix(), - cursor_pos)) - skip = 0 - for i, part in enumerate(parts): - log.completion.vdebug("Checking part {}: {!r}".format(i, parts[i])) - if not part: - skip += 1 - continue - if cursor_pos <= len(part): - # foo| bar - self._cursor_part = i - skip - if spaces: - self._empty_item_idx = i - skip - else: - self._empty_item_idx = None - log.completion.vdebug("cursor_pos {} <= len(part) {}, " - "setting cursor_part {} - {} (skip), " - "empty_item_idx {}".format( - cursor_pos, len(part), i, skip, - self._empty_item_idx)) - break - cursor_pos -= len(part) - log.completion.vdebug( - "Removing len({!r}) -> {} from cursor_pos -> {}".format( - part, len(part), cursor_pos)) - else: - if i == 0: - # Initial `:` press without any text. - self._cursor_part = 0 - else: - self._cursor_part = i - skip - if spaces: - self._empty_item_idx = i - skip - else: - self._empty_item_idx = None - log.completion.debug("cursor_part {}, spaces {}".format( - self._cursor_part, spaces)) - return - def _change_completed_part(self, newtext, immediate=False): """Change the part we're currently completing in the commandline. @@ -390,11 +301,3 @@ class Completer(QObject): self._cmd.setCursorPosition(pos) self._cmd.setFocus() self._cmd.show_cmd.emit() - - @pyqtSlot() - def _on_text_edited(self): - """Reset _empty_item_idx if text was edited.""" - self._empty_item_idx = None - # We also want to update the cursor part and emit _update_completion - # here, but that's already done for us by cursorPositionChanged - # anyways, so we don't need to do it twice. diff --git a/tests/unit/completion/test_completer.py b/tests/unit/completion/test_completer.py index 5532e4400..4326370e2 100644 --- a/tests/unit/completion/test_completer.py +++ b/tests/unit/completion/test_completer.py @@ -174,7 +174,6 @@ def _set_cmd_prompt(cmd, txt): (':open -t |', usertypes.Completion.url), (':open --tab |', usertypes.Completion.url), (':open | -t', usertypes.Completion.url), - (':--foo --bar |', None), (':tab-detach |', None), (':bind --mode=caret |', usertypes.Completion.command), pytest.mark.xfail(reason='issue #74')((':bind --mode caret |',