From cdcde09b803a667be8d31148a6b0f9dbf989e361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Klinkovsk=C3=BD?= Date: Tue, 9 Aug 2016 13:57:56 +0200 Subject: [PATCH 01/16] hints: refactor filter_hints method --- qutebrowser/browser/hints.py | 84 +++++++++++++----------------------- 1 file changed, 30 insertions(+), 54 deletions(-) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 7ffa634eb..1229d68c0 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -767,6 +767,22 @@ class HintManager(QObject): return self._context.hint_mode + def _get_visible_hints(self): + """Get elements which are currently visible. + """ + visible = {} + for string, elem in self._context.elems.items(): + try: + if not self._is_hidden(elem.label): + visible[string] = elem + except webelem.Error: + pass + if not visible: + # Whoops, filtered all hints + modeman.leave(self._win_id, usertypes.KeyMode.hint, + 'all filtered') + return visible + def handle_partial_key(self, keystr): """Handle a new partial keypress.""" log.hints.debug("Handling new keystring: '{}'".format(keystr)) @@ -791,57 +807,6 @@ class HintManager(QObject): except webelem.Error: pass - def _filter_number_hints(self): - """Apply filters for numbered hints and renumber them. - - Return: - Elements which are still visible - """ - # renumber filtered hints - elems = [] - for e in self._context.all_elems: - try: - if not self._is_hidden(e.label): - elems.append(e) - except webelem.Error: - pass - if not elems: - # Whoops, filtered all hints - modeman.leave(self._win_id, usertypes.KeyMode.hint, - 'all filtered') - return {} - - strings = self._hint_strings(elems) - self._context.elems = {} - for elem, string in zip(elems, strings): - elem.label.set_inner_xml(string) - self._context.elems[string] = elem - keyparsers = objreg.get('keyparsers', scope='window', - window=self._win_id) - keyparser = keyparsers[usertypes.KeyMode.hint] - keyparser.update_bindings(strings, preserve_filter=True) - - return self._context.elems - - def _filter_non_number_hints(self): - """Apply filters for letter/word hints. - - Return: - Elements which are still visible - """ - visible = {} - for string, elem in self._context.elems.items(): - try: - if not self._is_hidden(elem.label): - visible[string] = elem - except webelem.Error: - pass - if not visible: - # Whoops, filtered all hints - modeman.leave(self._win_id, usertypes.KeyMode.hint, - 'all filtered') - return visible - def filter_hints(self, filterstr): """Filter displayed hints according to a text. @@ -856,9 +821,11 @@ class HintManager(QObject): else: self._filterstr = filterstr + visible = [] for elem in self._context.all_elems: try: if self._filter_matches(filterstr, str(elem.elem)): + visible.append(elem) if self._is_hidden(elem.label): # hidden element which matches again -> show it self._show_elem(elem.label) @@ -869,9 +836,16 @@ class HintManager(QObject): pass if self._context.hint_mode == 'number': - visible = self._filter_number_hints() - else: - visible = self._filter_non_number_hints() + # renumber filtered hints + strings = self._hint_strings(visible) + self._context.elems = {} + for elem, string in zip(visible, strings): + elem.label.set_inner_xml(string) + self._context.elems[string] = elem + keyparsers = objreg.get('keyparsers', scope='window', + window=self._win_id) + keyparser = keyparsers[usertypes.KeyMode.hint] + keyparser.update_bindings(strings, preserve_filter=True) if (len(visible) == 1 and config.get('hints', 'auto-follow') and @@ -882,6 +856,8 @@ class HintManager(QObject): window=self._win_id) normal_parser = keyparsers[usertypes.KeyMode.normal] normal_parser.set_inhibited_timeout(timeout) + # change visible to dict + visible = self._get_visible_hints() # unpacking gets us the first (and only) key in the dict. self.fire(*visible) From 1d2a34812b75462c524b0e0a170e079ec471042d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Klinkovsk=C3=BD?= Date: Tue, 9 Aug 2016 14:18:46 +0200 Subject: [PATCH 02/16] hints: refactor handling of auto-follow option --- qutebrowser/browser/hints.py | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 1229d68c0..fcb540de9 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -783,6 +783,22 @@ class HintManager(QObject): 'all filtered') return visible + def _handle_auto_follow(self, visible=None): + """Handle the auto-follow option. + """ + if visible is None: + visible = self._get_visible_hints() + + if len(visible) == 1 and config.get('hints', 'auto-follow'): + # apply auto-follow-timeout + timeout = config.get('hints', 'auto-follow-timeout') + keyparsers = objreg.get('keyparsers', scope='window', + window=self._win_id) + normal_parser = keyparsers[usertypes.KeyMode.normal] + normal_parser.set_inhibited_timeout(timeout) + # unpacking gets us the first (and only) key in the dict. + self.fire(*visible) + def handle_partial_key(self, keystr): """Handle a new partial keypress.""" log.hints.debug("Handling new keystring: '{}'".format(keystr)) @@ -847,19 +863,11 @@ class HintManager(QObject): keyparser = keyparsers[usertypes.KeyMode.hint] keyparser.update_bindings(strings, preserve_filter=True) - if (len(visible) == 1 and - config.get('hints', 'auto-follow') and - filterstr is not None): - # apply auto-follow-timeout - timeout = config.get('hints', 'auto-follow-timeout') - keyparsers = objreg.get('keyparsers', scope='window', - window=self._win_id) - normal_parser = keyparsers[usertypes.KeyMode.normal] - normal_parser.set_inhibited_timeout(timeout) - # change visible to dict - visible = self._get_visible_hints() - # unpacking gets us the first (and only) key in the dict. - self.fire(*visible) + # Note: filter_hints can be called with non-None filterstr only + # when number mode is active + if filterstr is not None: + # pass self._context.elems as the dict of visible hints + self._handle_auto_follow(self._context.elems) def fire(self, keystr, force=False): """Fire a completed hint. From 9841b01d0d46c71e66e578cb2799828a484b7663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Klinkovsk=C3=BD?= Date: Tue, 9 Aug 2016 14:25:40 +0200 Subject: [PATCH 03/16] hints: generalize auto-follow for any unique match fixes #1809 --- qutebrowser/browser/hints.py | 1 + qutebrowser/config/configdata.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index fcb540de9..ec12789fe 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -822,6 +822,7 @@ class HintManager(QObject): self._hide_elem(elem.label) except webelem.Error: pass + self._handle_auto_follow() def filter_hints(self, filterstr): """Filter displayed hints according to a text. diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 6837ed70a..258bf8ed0 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -928,8 +928,9 @@ def data(readonly=False): ('auto-follow', SettingValue(typ.Bool(), 'true'), - "Follow a hint immediately when the hint text is completely " - "matched."), + "Follow a hint immediately when there is a unique match on the " + "hint text (in letter and word modes) or in the hint filter " + "(in number mode)."), ('auto-follow-timeout', SettingValue(typ.Int(), '0'), From ec5387c6744d58ae6d4157b5ea85ea80480b6301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Klinkovsk=C3=BD?= Date: Tue, 9 Aug 2016 18:03:27 +0200 Subject: [PATCH 04/16] hints: fix docstrings --- qutebrowser/browser/hints.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index ec12789fe..514ebf0b1 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -768,8 +768,7 @@ class HintManager(QObject): return self._context.hint_mode def _get_visible_hints(self): - """Get elements which are currently visible. - """ + """Get elements which are currently visible.""" visible = {} for string, elem in self._context.elems.items(): try: @@ -784,8 +783,7 @@ class HintManager(QObject): return visible def _handle_auto_follow(self, visible=None): - """Handle the auto-follow option. - """ + """Handle the auto-follow option.""" if visible is None: visible = self._get_visible_hints() From 108d735e0718ecb47b8f372ec6eae6608f08ee07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Klinkovsk=C3=BD?= Date: Tue, 9 Aug 2016 18:04:29 +0200 Subject: [PATCH 05/16] hints: fix corner-case in _hint_strings --- qutebrowser/browser/hints.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 514ebf0b1..ec5f4d56c 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -394,6 +394,8 @@ class HintManager(QObject): Return: A list of hint strings, in the same order as the elements. """ + if not elems: + return [] hint_mode = self._context.hint_mode if hint_mode == 'word': try: From 7be942fadca1cfa4c102af70d7a6f853c0e3c9f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Klinkovsk=C3=BD?= Date: Wed, 10 Aug 2016 11:18:11 +0200 Subject: [PATCH 06/16] hints: fix leaving filter in number mode --- qutebrowser/browser/hints.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index ec5f4d56c..298b96702 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -778,10 +778,6 @@ class HintManager(QObject): visible[string] = elem except webelem.Error: pass - if not visible: - # Whoops, filtered all hints - modeman.leave(self._win_id, usertypes.KeyMode.hint, - 'all filtered') return visible def _handle_auto_follow(self, visible=None): @@ -852,6 +848,12 @@ class HintManager(QObject): except webelem.Error: pass + if not visible: + # Whoops, filtered all hints + modeman.leave(self._win_id, usertypes.KeyMode.hint, + 'all filtered') + return + if self._context.hint_mode == 'number': # renumber filtered hints strings = self._hint_strings(visible) From a31565f46e7403f1d040ea9745f58a22b9fe3a4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Klinkovsk=C3=BD?= Date: Wed, 10 Aug 2016 12:40:16 +0200 Subject: [PATCH 07/16] hints: make auto-follow a quadruple option instead of binary --- qutebrowser/browser/hints.py | 41 ++++++++++-- qutebrowser/config/config.py | 2 + qutebrowser/config/configdata.py | 19 ++++-- tests/end2end/features/hints.feature | 99 ++++++++++++++++++++++++++-- 4 files changed, 146 insertions(+), 15 deletions(-) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 298b96702..1e569372f 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -632,6 +632,15 @@ class HintManager(QObject): # Do multi-word matching return all(word in elemstr for word in filterstr.split()) + def _filter_matches_exactly(self, filterstr, elemstr): + """Return True if `filterstr` exactly matches `elemstr`.""" + # Empty string and None never match + if not filterstr: + return False + filterstr = filterstr.casefold() + elemstr = elemstr.casefold() + return filterstr == elemstr + def _start_cb(self, elems): """Initialize the elements and labels based on the context set.""" filterfunc = webelem.FILTERS.get(self._context.group, lambda e: True) @@ -658,6 +667,9 @@ class HintManager(QObject): modeman.enter(self._win_id, usertypes.KeyMode.hint, 'HintManager.start') + # to make auto-follow == 'always' work + self._handle_auto_follow() + @cmdutils.register(instance='hintmanager', scope='tab', name='hint', star_args_optional=True, maxsplit=2, backend=usertypes.Backend.QtWebKit) @@ -780,12 +792,28 @@ class HintManager(QObject): pass return visible - def _handle_auto_follow(self, visible=None): + def _handle_auto_follow(self, keystr="", filterstr="", visible=None): """Handle the auto-follow option.""" if visible is None: visible = self._get_visible_hints() - if len(visible) == 1 and config.get('hints', 'auto-follow'): + if len(visible) != 1: + return + + auto_follow = config.get('hints', 'auto-follow') + + if auto_follow == "always": + follow = True + elif auto_follow == "unique-match": + follow = (keystr or filterstr) + elif auto_follow == "full-match": + elemstr = str(list(visible.values())[0].elem) + filter_match = self._filter_matches_exactly(filterstr, elemstr) + follow = keystr in visible or filter_match + else: + follow = False + + if follow: # apply auto-follow-timeout timeout = config.get('hints', 'auto-follow-timeout') keyparsers = objreg.get('keyparsers', scope='window', @@ -818,7 +846,7 @@ class HintManager(QObject): self._hide_elem(elem.label) except webelem.Error: pass - self._handle_auto_follow() + self._handle_auto_follow(keystr=keystr) def filter_hints(self, filterstr): """Filter displayed hints according to a text. @@ -870,16 +898,17 @@ class HintManager(QObject): # when number mode is active if filterstr is not None: # pass self._context.elems as the dict of visible hints - self._handle_auto_follow(self._context.elems) + self._handle_auto_follow(filterstr=filterstr, + visible=self._context.elems) def fire(self, keystr, force=False): """Fire a completed hint. Args: keystr: The keychain string to follow. - force: When True, follow even when auto-follow is false. + force: When True, follow even when auto-follow is not 'never'. """ - if not (force or config.get('hints', 'auto-follow')): + if not (force or config.get('hints', 'auto-follow') != 'never'): self.handle_partial_key(keystr) self._context.to_follow = keystr return diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index a5521e275..204605a6a 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -363,6 +363,8 @@ class ConfigManager(QObject): _get_value_transformer({'false': 'none', 'true': 'debug'}), ('ui', 'keyhint-blacklist'): _get_value_transformer({'false': '*', 'true': ''}), + ('hints', 'auto-follow'): + _get_value_transformer({'false': 'never', 'true': 'unique-match'}), } changed = pyqtSignal(str, str) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 258bf8ed0..fb3d5e505 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -927,10 +927,21 @@ def data(readonly=False): "The dictionary file to be used by the word hints."), ('auto-follow', - SettingValue(typ.Bool(), 'true'), - "Follow a hint immediately when there is a unique match on the " - "hint text (in letter and word modes) or in the hint filter " - "(in number mode)."), + SettingValue(typ.String( + valid_values=typ.ValidValues( + ('always', "Auto-follow whenever there is only a single " + "hint on a page."), + ('unique-match', "Auto-follow whenever there is a unique " + "non-empty match in either the hint string (word mode) " + "or filter (number mode)."), + ('full-match', "Follow the hint when the user typed the " + "whole hint (letter, word or number mode) or the " + "element's text (only in number mode)."), + ('never', "The user will always need to press Enter to " + "follow a hint."), + )), 'unique-match'), + "Controls when a hint can be automatically followed without the " + "user pressing Enter."), ('auto-follow-timeout', SettingValue(typ.Int(), '0'), diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature index a03a9aef0..51a9b9890 100644 --- a/tests/end2end/features/hints.feature +++ b/tests/end2end/features/hints.feature @@ -39,10 +39,10 @@ Feature: Using hints - data/hello.txt (active) Scenario: Entering and leaving hinting mode (issue 1464) - When I open data/hints/html/simple.html - And I run :hint - And I run :fake-key -g - Then no crash should happen + When I open data/hints/html/simple.html + And I run :hint + And I run :fake-key -g + Then no crash should happen Scenario: Using :hint spawn with flags and -- (issue 797) When I open data/hints/html/simple.html @@ -225,7 +225,7 @@ Feature: Using hints Scenario: Multi-word matching When I open data/hints/number.html And I set hints -> mode to number - And I set hints -> auto-follow to true + And I set hints -> auto-follow to unique-match And I set hints -> auto-follow-timeout to 0 And I run :hint all And I press the keys "ten pos" @@ -265,3 +265,92 @@ Feature: Using hints And I press the key "s" And I run :follow-hint 1 Then data/numbers/7.txt should be loaded + + ### auto-follow option + + Scenario: Using hints -> auto-follow == 'always' in letter mode + When I open data/hints/html/simple.html + And I set hints -> mode to letter + And I set hints -> auto-follow to always + And I run :hint + Then data/hello.txt should be loaded + + # unique-match is actually the same as full-match in letter mode + Scenario: Using hints -> auto-follow == 'unique-match' in letter mode + When I open data/hints/html/simple.html + And I set hints -> mode to letter + And I set hints -> auto-follow to unique-match + And I run :hint + And I press the key "a" + Then data/hello.txt should be loaded + + Scenario: Using hints -> auto-follow == 'full-match' in letter mode + When I open data/hints/html/simple.html + And I set hints -> mode to letter + And I set hints -> auto-follow to full-match + And I run :hint + And I press the key "a" + Then data/hello.txt should be loaded + + # FIXME: not sure where I broke this... + @xfail_norun + Scenario: Using hints -> auto-follow == 'never' in letter mode + When I open data/hints/html/simple.html + And I set hints -> mode to letter + And I set hints -> auto-follow to never + And I run :hint + And I press the key "a" + And I press the key "Enter" + Then data/hello.txt should be loaded + + Scenario: Using hints -> auto-follow == 'always' in number mode + When I open data/hints/html/simple.html + And I set hints -> mode to number + And I set hints -> auto-follow to always + And I run :hint + Then data/hello.txt should be loaded + + Scenario: Using hints -> auto-follow == 'unique-match' in number mode + When I open data/hints/html/simple.html + And I set hints -> mode to number + And I set hints -> auto-follow to unique-match + And I run :hint + And I press the key "f" + Then data/hello.txt should be loaded + + Scenario: Using hints -> auto-follow == 'full-match' in number mode + When I open data/hints/html/simple.html + And I set hints -> mode to number + And I set hints -> auto-follow to full-match + And I run :hint + And I press the key "f" + And I press the key "o" + And I press the key "l" + And I press the key "l" + And I press the key "o" + And I press the key "w" + And I press the key " " + And I press the key "m" + And I press the key "e" + And I press the key "!" + Then data/hello.txt should be loaded + + Scenario: Using hints -> auto-follow == 'never' in number mode + When I open data/hints/html/simple.html + And I set hints -> mode to number + And I set hints -> auto-follow to full-match + And I run :hint + And I press the key "f" + And I press the key "o" + And I press the key "l" + And I press the key "l" + And I press the key "o" + And I press the key "w" + And I press the key " " + And I press the key "m" + And I press the key "e" + And I press the key "!" + And I press the key "Enter" + Then data/hello.txt should be loaded + + # TODO: tests for word mode - it tries to access /usr/share/dict/words on the system From 0d4322b1f235a16c9ebbe8e28a034625d08c17f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Klinkovsk=C3=BD?= Date: Wed, 10 Aug 2016 13:01:41 +0200 Subject: [PATCH 08/16] fix braces in boolean assignments --- qutebrowser/browser/hints.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 1e569372f..51f984d10 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -805,11 +805,11 @@ class HintManager(QObject): if auto_follow == "always": follow = True elif auto_follow == "unique-match": - follow = (keystr or filterstr) + follow = keystr or filterstr elif auto_follow == "full-match": elemstr = str(list(visible.values())[0].elem) filter_match = self._filter_matches_exactly(filterstr, elemstr) - follow = keystr in visible or filter_match + follow = (keystr in visible) or filter_match else: follow = False From f69c02a2b1e42c130ddb435764359a891f948da8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Klinkovsk=C3=BD?= Date: Wed, 10 Aug 2016 13:05:53 +0200 Subject: [PATCH 09/16] tests: fix hints auto-follow tests --- tests/end2end/features/hints.feature | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature index 51a9b9890..fc7dba39c 100644 --- a/tests/end2end/features/hints.feature +++ b/tests/end2end/features/hints.feature @@ -292,15 +292,13 @@ Feature: Using hints And I press the key "a" Then data/hello.txt should be loaded - # FIXME: not sure where I broke this... - @xfail_norun Scenario: Using hints -> auto-follow == 'never' in letter mode When I open data/hints/html/simple.html And I set hints -> mode to letter And I set hints -> auto-follow to never And I run :hint And I press the key "a" - And I press the key "Enter" + And I press the key "" Then data/hello.txt should be loaded Scenario: Using hints -> auto-follow == 'always' in number mode @@ -350,7 +348,7 @@ Feature: Using hints And I press the key "m" And I press the key "e" And I press the key "!" - And I press the key "Enter" + And I press the key "" Then data/hello.txt should be loaded # TODO: tests for word mode - it tries to access /usr/share/dict/words on the system From 605e90a22222a509bee06e1d23dc372bc858e658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Klinkovsk=C3=BD?= Date: Wed, 10 Aug 2016 13:14:21 +0200 Subject: [PATCH 10/16] simplify hints auto-follow tests --- tests/end2end/features/hints.feature | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature index fc7dba39c..abaf07a21 100644 --- a/tests/end2end/features/hints.feature +++ b/tests/end2end/features/hints.feature @@ -321,16 +321,8 @@ Feature: Using hints And I set hints -> mode to number And I set hints -> auto-follow to full-match And I run :hint - And I press the key "f" - And I press the key "o" - And I press the key "l" - And I press the key "l" - And I press the key "o" - And I press the key "w" - And I press the key " " - And I press the key "m" - And I press the key "e" - And I press the key "!" + # this actually presses the keys one by one + And I press the key "follow me!" Then data/hello.txt should be loaded Scenario: Using hints -> auto-follow == 'never' in number mode @@ -338,16 +330,8 @@ Feature: Using hints And I set hints -> mode to number And I set hints -> auto-follow to full-match And I run :hint - And I press the key "f" - And I press the key "o" - And I press the key "l" - And I press the key "l" - And I press the key "o" - And I press the key "w" - And I press the key " " - And I press the key "m" - And I press the key "e" - And I press the key "!" + # this actually presses the keys one by one + And I press the key "follow me!" And I press the key "" Then data/hello.txt should be loaded From 24f8ed8ac18e1b1a05549773acb8f5ed06514485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Klinkovsk=C3=BD?= Date: Wed, 10 Aug 2016 14:58:21 +0200 Subject: [PATCH 11/16] hints: refactor auto-follow handling in the fire method --- qutebrowser/browser/hints.py | 13 +++++-------- qutebrowser/keyinput/modeparsers.py | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 51f984d10..c7075bd18 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -812,6 +812,9 @@ class HintManager(QObject): follow = (keystr in visible) or filter_match else: follow = False + # save the keystr of the only one visible hint to be picked up + # later by self.follow_hint + self._context.to_follow = list(visible.keys())[0] if follow: # apply auto-follow-timeout @@ -901,18 +904,12 @@ class HintManager(QObject): self._handle_auto_follow(filterstr=filterstr, visible=self._context.elems) - def fire(self, keystr, force=False): + def fire(self, keystr): """Fire a completed hint. Args: keystr: The keychain string to follow. - force: When True, follow even when auto-follow is not 'never'. """ - if not (force or config.get('hints', 'auto-follow') != 'never'): - self.handle_partial_key(keystr) - self._context.to_follow = keystr - return - # Handlers which take a QWebElement elem_handlers = { Target.normal: self._actions.click, @@ -985,7 +982,7 @@ class HintManager(QObject): keystring = self._context.to_follow elif keystring not in self._context.elems: raise cmdexc.CommandError("No hint {}!".format(keystring)) - self.fire(keystring, force=True) + self.fire(keystring) @pyqtSlot() def on_contents_size_changed(self): diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py index 223a09925..fb923bf92 100644 --- a/qutebrowser/keyinput/modeparsers.py +++ b/qutebrowser/keyinput/modeparsers.py @@ -231,7 +231,7 @@ class HintKeyParser(keyparser.CommandKeyParser): if keytype == self.Type.chain: hintmanager = objreg.get('hintmanager', scope='tab', window=self._win_id, tab='current') - hintmanager.fire(cmdstr) + hintmanager.handle_partial_key(cmdstr) else: # execute as command super().execute(cmdstr, keytype, count) From 073e5555caef9e98254f6d996ebf0c3a5fb143b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Klinkovsk=C3=BD?= Date: Wed, 10 Aug 2016 16:12:09 +0200 Subject: [PATCH 12/16] add more tests for hints auto-follow --- tests/end2end/features/hints.feature | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature index abaf07a21..a5ec33a04 100644 --- a/tests/end2end/features/hints.feature +++ b/tests/end2end/features/hints.feature @@ -292,6 +292,14 @@ Feature: Using hints And I press the key "a" Then data/hello.txt should be loaded + Scenario: Using hints -> auto-follow == 'never' without Enter in letter mode + When I open data/hints/html/simple.html + And I set hints -> mode to letter + And I set hints -> auto-follow to never + And I run :hint + And I press the key "a" + Then "Leaving mode KeyMode.hint (reason: followed)" should not be logged + Scenario: Using hints -> auto-follow == 'never' in letter mode When I open data/hints/html/simple.html And I set hints -> mode to letter @@ -325,10 +333,19 @@ Feature: Using hints And I press the key "follow me!" Then data/hello.txt should be loaded + Scenario: Using hints -> auto-follow == 'never' without Enter in number mode + When I open data/hints/html/simple.html + And I set hints -> mode to number + And I set hints -> auto-follow to never + And I run :hint + # this actually presses the keys one by one + And I press the key "follow me!" + Then "Leaving mode KeyMode.hint (reason: followed)" should not be logged + Scenario: Using hints -> auto-follow == 'never' in number mode When I open data/hints/html/simple.html And I set hints -> mode to number - And I set hints -> auto-follow to full-match + And I set hints -> auto-follow to never And I run :hint # this actually presses the keys one by one And I press the key "follow me!" From efb680dfb1d0baf985958e61b7790d3fb684e2fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Klinkovsk=C3=BD?= Date: Wed, 10 Aug 2016 18:21:19 +0200 Subject: [PATCH 13/16] hints: make HintManager.fire private It's not called by other classes anymore, it shouldn't be part of public API because wrong value to the keystr parameter causes KeyError. --- qutebrowser/browser/hints.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index c7075bd18..297c80eda 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -824,7 +824,7 @@ class HintManager(QObject): normal_parser = keyparsers[usertypes.KeyMode.normal] normal_parser.set_inhibited_timeout(timeout) # unpacking gets us the first (and only) key in the dict. - self.fire(*visible) + self._fire(*visible) def handle_partial_key(self, keystr): """Handle a new partial keypress.""" @@ -904,7 +904,7 @@ class HintManager(QObject): self._handle_auto_follow(filterstr=filterstr, visible=self._context.elems) - def fire(self, keystr): + def _fire(self, keystr): """Fire a completed hint. Args: @@ -982,7 +982,7 @@ class HintManager(QObject): keystring = self._context.to_follow elif keystring not in self._context.elems: raise cmdexc.CommandError("No hint {}!".format(keystring)) - self.fire(keystring) + self._fire(keystring) @pyqtSlot() def on_contents_size_changed(self): From 58ded41e5d7f611cf8629f174c8725d93eb28ac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Klinkovsk=C3=BD?= Date: Wed, 10 Aug 2016 18:48:10 +0200 Subject: [PATCH 14/16] hints: move _filterstr into _context --- qutebrowser/browser/hints.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 297c80eda..57ffe2de3 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -79,6 +79,7 @@ class HintContext: to_follow: The link to follow when enter is pressed. args: Custom arguments for userscript/spawn rapid: Whether to do rapid hinting. + filterstr: Used to save the filter string for restoring in rapid mode. tab: The WebTab object we started hinting in. group: The group of web elements to hint. """ @@ -90,6 +91,7 @@ class HintContext: self.baseurl = None self.to_follow = None self.rapid = False + self.filterstr = None self.frames = [] self.args = [] self.tab = None @@ -309,7 +311,6 @@ class HintManager(QObject): _context: The HintContext for the current invocation. _win_id: The window ID this HintManager is associated with. _tab_id: The tab ID this HintManager is associated with. - _filterstr: Used to save the filter string for restoring in rapid mode. Signals: See HintActions @@ -342,7 +343,6 @@ class HintManager(QObject): self._win_id = win_id self._tab_id = tab_id self._context = None - self._filterstr = None self._word_hinter = WordHinter() self._actions = HintActions(win_id) @@ -381,7 +381,6 @@ class HintManager(QObject): window=self._win_id) message_bridge.maybe_reset_text(text) self._context = None - self._filterstr = None def _hint_strings(self, elems): """Calculate the hint strings for elems. @@ -861,9 +860,9 @@ class HintManager(QObject): and `self._filterstr` are None, all hints are shown. """ if filterstr is None: - filterstr = self._filterstr + filterstr = self._context.filterstr else: - self._filterstr = filterstr + self._context.filterstr = filterstr visible = [] for elem in self._context.all_elems: From cdcc9996a08ecba3c6217ba33d33280f21352bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Klinkovsk=C3=BD?= Date: Wed, 10 Aug 2016 18:55:12 +0200 Subject: [PATCH 15/16] hints: remove unused context attribute and duplicate initialization --- qutebrowser/browser/hints.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 57ffe2de3..592f8ee1d 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -62,7 +62,6 @@ class HintContext: """Context namespace used for hinting. Attributes: - frames: The QWebFrames to use. all_elems: A list of all (elem, label) namedtuples ever created. elems: A mapping from key strings to (elem, label) namedtuples. May contain less elements than `all_elems` due to filtering. @@ -92,7 +91,6 @@ class HintContext: self.to_follow = None self.rapid = False self.filterstr = None - self.frames = [] self.args = [] self.tab = None self.group = None @@ -766,7 +764,6 @@ class HintManager(QObject): self._context.baseurl = tabbed_browser.current_url() except qtutils.QtValueError: raise cmdexc.CommandError("No URL set for this page yet!") - self._context.tab = tab self._context.args = args self._context.group = group selector = webelem.SELECTORS[self._context.group] From 9b1c07e2e2943b20b2f1f0f9d438ad3cd9217ce2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Klinkovsk=C3=BD?= Date: Thu, 11 Aug 2016 15:20:52 +0200 Subject: [PATCH 16/16] add tests for hints auto-follow in word mode --- tests/end2end/features/hints.feature | 44 +++++++++++++++++++++++- tests/end2end/features/test_hints_bdd.py | 25 ++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature index a5ec33a04..52c6c88e4 100644 --- a/tests/end2end/features/hints.feature +++ b/tests/end2end/features/hints.feature @@ -352,4 +352,46 @@ Feature: Using hints And I press the key "" Then data/hello.txt should be loaded - # TODO: tests for word mode - it tries to access /usr/share/dict/words on the system + Scenario: Using hints -> auto-follow == 'always' in word mode + When I open data/hints/html/simple.html + And I set hints -> mode to word + And I set hints -> auto-follow to always + And I run :hint + Then data/hello.txt should be loaded + + Scenario: Using hints -> auto-follow == 'unique-match' in word mode + When I open data/hints/html/simple.html + And I set hints -> mode to word + And I set hints -> auto-follow to unique-match + And I run :hint + # the link gets "hello" as the hint + And I press the key "h" + Then data/hello.txt should be loaded + + Scenario: Using hints -> auto-follow == 'full-match' in word mode + When I open data/hints/html/simple.html + And I set hints -> mode to word + And I set hints -> auto-follow to full-match + And I run :hint + # this actually presses the keys one by one + And I press the key "hello" + Then data/hello.txt should be loaded + + Scenario: Using hints -> auto-follow == 'never' without Enter in word mode + When I open data/hints/html/simple.html + And I set hints -> mode to word + And I set hints -> auto-follow to never + And I run :hint + # this actually presses the keys one by one + And I press the key "hello" + Then "Leaving mode KeyMode.hint (reason: followed)" should not be logged + + Scenario: Using hints -> auto-follow == 'never' in word mode + When I open data/hints/html/simple.html + And I set hints -> mode to word + And I set hints -> auto-follow to never + And I run :hint + # this actually presses the keys one by one + And I press the key "hello" + And I press the key "" + Then data/hello.txt should be loaded diff --git a/tests/end2end/features/test_hints_bdd.py b/tests/end2end/features/test_hints_bdd.py index 775c347be..b5304cb74 100644 --- a/tests/end2end/features/test_hints_bdd.py +++ b/tests/end2end/features/test_hints_bdd.py @@ -17,5 +17,30 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . +import textwrap + +import pytest + import pytest_bdd as bdd bdd.scenarios('hints.feature') + + +@pytest.fixture(autouse=True) +def set_up_word_hints(tmpdir, quteproc): + dict_file = tmpdir / 'dict' + dict_file.write(textwrap.dedent(""" + one + two + three + four + five + six + seven + eight + nine + ten + eleven + twelve + thirteen + """)) + quteproc.set_setting('hints', 'dictionary', str(dict_file))