summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Bruhin <git@the-compiler.org>2016-04-30 14:44:25 +0200
committerFlorian Bruhin <git@the-compiler.org>2016-04-30 14:44:25 +0200
commit8e619fa74e033e974e36457428eac9087838440d (patch)
treec1deae92ab1ef205faae100e3f987327a6ee90f9
parent5b944fb2727fadab4abd001c766af6cc40908c41 (diff)
downloadqutebrowser-8e619fa74e033e974e36457428eac9087838440d.tar.gz
qutebrowser-8e619fa74e033e974e36457428eac9087838440d.zip
Fix dictionary hints crash
-rw-r--r--qutebrowser/browser/hints.py22
-rw-r--r--tests/integration/data/hinting.txt1
-rw-r--r--tests/integration/data/hints/issue1393.html32
-rw-r--r--tests/integration/data/l33t.txt1
-rw-r--r--tests/integration/data/smart.txt1
-rw-r--r--tests/integration/data/words.txt1
-rw-r--r--tests/integration/test_hints_html.py34
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))