diff options
author | Florian Bruhin <git@the-compiler.org> | 2016-04-30 14:44:25 +0200 |
---|---|---|
committer | Florian Bruhin <git@the-compiler.org> | 2016-04-30 14:44:25 +0200 |
commit | 8e619fa74e033e974e36457428eac9087838440d (patch) | |
tree | c1deae92ab1ef205faae100e3f987327a6ee90f9 | |
parent | 5b944fb2727fadab4abd001c766af6cc40908c41 (diff) | |
download | qutebrowser-8e619fa74e033e974e36457428eac9087838440d.tar.gz qutebrowser-8e619fa74e033e974e36457428eac9087838440d.zip |
Fix dictionary hints crash
-rw-r--r-- | qutebrowser/browser/hints.py | 22 | ||||
-rw-r--r-- | tests/integration/data/hinting.txt | 1 | ||||
-rw-r--r-- | tests/integration/data/hints/issue1393.html | 32 | ||||
-rw-r--r-- | tests/integration/data/l33t.txt | 1 | ||||
-rw-r--r-- | tests/integration/data/smart.txt | 1 | ||||
-rw-r--r-- | tests/integration/data/words.txt | 1 | ||||
-rw-r--r-- | tests/integration/test_hints_html.py | 34 |
7 files changed, 86 insertions, 6 deletions
diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 3b71c0266..49ce8641d 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -997,11 +997,14 @@ class WordHinter: def __init__(self): # will be initialized on first use. self.words = set() + self.dictionary = None def ensure_initialized(self): """Generate the used words if yet uninialized.""" - if not self.words: - dictionary = config.get("hints", "dictionary") + dictionary = config.get("hints", "dictionary") + if not self.words or self.dictionary != dictionary: + self.words.clear() + self.dictionary = dictionary try: with open(dictionary, encoding="UTF-8") as wordfile: alphabet = set(string.ascii_lowercase) @@ -1061,12 +1064,17 @@ class WordHinter: return any(hint.startswith(e) or e.startswith(hint) for e in existing) - def new_hint_for(self, elem, existing): + def filter_prefixes(self, hints, existing): + return (h for h in hints if not self.any_prefix(h, existing)) + + def new_hint_for(self, elem, existing, fallback): """Return a hint for elem, not conflicting with the existing.""" new = self.tag_words_to_hints(self.extract_tag_words(elem)) - no_prefixes = (h for h in new if not self.any_prefix(h, existing)) + new_no_prefixes = self.filter_prefixes(new, existing) + fallback_no_prefixes = self.filter_prefixes(fallback, existing) # either the first good, or None - return next(no_prefixes, None) + return (next(new_no_prefixes, None) or + next(fallback_no_prefixes, None)) def hint(self, elems): """Produce hint labels based on the html tags. @@ -1086,7 +1094,9 @@ class WordHinter: used_hints = set() words = iter(self.words) for elem in elems: - hint = self.new_hint_for(elem, used_hints) or next(words) + hint = self.new_hint_for(elem, used_hints, words) + if not hint: + raise WordHintingError("Not enough words in the dictionary.") used_hints.add(hint) hints.append(hint) return hints diff --git a/tests/integration/data/hinting.txt b/tests/integration/data/hinting.txt new file mode 100644 index 000000000..13db8e00b --- /dev/null +++ b/tests/integration/data/hinting.txt @@ -0,0 +1 @@ +hinting diff --git a/tests/integration/data/hints/issue1393.html b/tests/integration/data/hints/issue1393.html new file mode 100644 index 000000000..a292b6e5f --- /dev/null +++ b/tests/integration/data/hints/issue1393.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>Let's Hint some words</title> + </head> + <body> + <h1>Word hints</h1> + + <h2>Smart hints</h2> + <p>In qutebrowser, urls can not only be hinted with letters and + numbers, but also with <a href="../words.txt">words</a>. When there is + a sensible url text available, qutebrowser will even use that + text to create a <a href="../smart.txt">smart</a> hint.</p> + + <h2>Filled hints</h2> + <p>When no smart hints are available, because the hint text is + <a href="../l33t.txt">too</a> short or <a href="../l33t.txt">l33t</a> to + use, words from a dictionary will be used.</p> + + <h2>Hint conflicts</h2> + <p>Of course, hints have to be unique. For instance, all hints + below should get a different hint, whether they're smart or + not:</p> + <ul> + <li><a href="../hinting.txt">hinting</a> should be a smart hint</li> + <li><a href="../l33t.txt">word</a> is a prefix of words</li> + <li><a href="../l33t.txt">3</a> is too 1337</li> + <li><a href="../l33t.txt">4</a> is too 1337</li> + </ul> + </body> +</html> diff --git a/tests/integration/data/l33t.txt b/tests/integration/data/l33t.txt new file mode 100644 index 000000000..1e2266cec --- /dev/null +++ b/tests/integration/data/l33t.txt @@ -0,0 +1 @@ +l33t diff --git a/tests/integration/data/smart.txt b/tests/integration/data/smart.txt new file mode 100644 index 000000000..c2d01532a --- /dev/null +++ b/tests/integration/data/smart.txt @@ -0,0 +1 @@ +smart diff --git a/tests/integration/data/words.txt b/tests/integration/data/words.txt new file mode 100644 index 000000000..71af1a951 --- /dev/null +++ b/tests/integration/data/words.txt @@ -0,0 +1 @@ +words diff --git a/tests/integration/test_hints_html.py b/tests/integration/test_hints_html.py index cc668193d..9a73335b4 100644 --- a/tests/integration/test_hints_html.py +++ b/tests/integration/test_hints_html.py @@ -1,3 +1,4 @@ + # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org> @@ -25,6 +26,7 @@ import os.path import yaml import pytest import bs4 +import textwrap def collect_tests(): @@ -54,3 +56,35 @@ def test_hints(test_name, quteproc): quteproc.wait_for(message='hints: a', category='hints') quteproc.send_cmd(':follow-hint a') quteproc.wait_for_load_finished('data/' + parsed['target']) + + +def test_word_hints_issue1393(quteproc, tmpdir): + dict_file = tmpdir / 'dict' + dict_file.write(textwrap.dedent(""" + alph + beta + gamm + delt + epsi + """)) + targets = [ + ('words', 'words.txt'), + ('smart', 'smart.txt'), + ('hinting', 'hinting.txt'), + ('alph', 'l33t.txt'), + ('beta', 'l33t.txt'), + ('gamm', 'l33t.txt'), + ('delt', 'l33t.txt'), + ('epsi', 'l33t.txt'), + ] + + quteproc.set_setting('hints', 'mode', 'word') + quteproc.set_setting('hints', 'dictionary', str(dict_file)) + + for hint, target in targets: + quteproc.open_path('data/hints/issue1393.html') + quteproc.wait_for_load_finished('data/hints/issue1393.html') + quteproc.send_cmd(':hint') + quteproc.wait_for(message='hints: *', category='hints') + quteproc.send_cmd(':follow-hint {}'.format(hint)) + quteproc.wait_for_load_finished('data/{}'.format(target)) |