Repository: qutebrowser/qutebrowser

Issue:
Completion behaves incorrectly when there's only a SQL model
With this patch:

```diff
diff --git a/qutebrowser/completion/models/urlmodel.py b/qutebrowser/completion/models/urlmodel.py
index e7f3086c4..b2a65ecee 100644
--- a/qutebrowser/completion/models/urlmodel.py
+++ b/qutebrowser/completion/models/urlmodel.py
@@ -56,14 +56,14 @@ def url(*, info):
     """
     model = completionmodel.CompletionModel(column_widths=(40, 50, 10))
 
-    quickmarks = ((url, name) for (name, url)
-                  in objreg.get('quickmark-manager').marks.items())
-    bookmarks = objreg.get('bookmark-manager').marks.items()
-
-    model.add_category(listcategory.ListCategory(
-        'Quickmarks', quickmarks, delete_func=_delete_quickmark))
-    model.add_category(listcategory.ListCategory(
-        'Bookmarks', bookmarks, delete_func=_delete_bookmark))
+    #quickmarks = ((url, name) for (name, url)
+    #              in objreg.get('quickmark-manager').marks.items())
+    #bookmarks = objreg.get('bookmark-manager').marks.items()
+
+    #model.add_category(listcategory.ListCategory(
+    #    'Quickmarks', quickmarks, delete_func=_delete_quickmark))
+    #model.add_category(listcategory.ListCategory(
+    #    'Bookmarks', bookmarks, delete_func=_delete_bookmark))
 
     if info.config.get('completion.web_history_max_items') != 0:
         hist_cat = histcategory.HistoryCategory(delete_func=_delete_history)
```

entering things in the completion doesn't filter anything, and the filter highlights only get added when tabbing.

Adding a `model.add_category(listcategory.ListCategory('Test', []))` seems to fix it.

This was discovered by @amosbird while hacking the source, so it's probably not possible to trigger it in `master`, but I guess it should still be fixed.

cc @rcorre 
Make it possible to hide completion categories without values



Discussion:

I really don't use bookmarks or quickmarks, so I only use completion from history. I'd really like to hide the category headers. Hiding the categories without values would be a good way to do this. ~~In addition, if there is only one category with values, I would like to hide its header, too.~~

Resolving patch:
diff --git a/qutebrowser/completion/models/completionmodel.py b/qutebrowser/completion/models/completionmodel.py
--- a/qutebrowser/completion/models/completionmodel.py
+++ b/qutebrowser/completion/models/completionmodel.py
@@ -60,8 +60,6 @@ def _cat_from_idx(self, index):
     def add_category(self, cat):
         """Add a completion category to the model."""
         self._categories.append(cat)
-        cat.layoutAboutToBeChanged.connect(self.layoutAboutToBeChanged)
-        cat.layoutChanged.connect(self.layoutChanged)
 
     def data(self, index, role=Qt.DisplayRole):
         """Return the item data for index.
@@ -179,8 +177,12 @@ def set_pattern(self, pattern):
             pattern: The filter pattern to set.
         """
         log.completion.debug("Setting completion pattern '{}'".format(pattern))
+        # layoutChanged is broken in pyqt-5.7.1, so we must use metaObject
+        # https://www.riverbankcomputing.com/pipermail/pyqt/2017-January/038483.html
+        self.metaObject().invokeMethod(self, "layoutAboutToBeChanged")
         for cat in self._categories:
             cat.set_pattern(pattern)
+        self.metaObject().invokeMethod(self, "layoutChanged")
 
     def first_item(self):
         """Return the index of the first child (non-category) in the model."""
diff --git a/qutebrowser/completion/models/urlmodel.py b/qutebrowser/completion/models/urlmodel.py
--- a/qutebrowser/completion/models/urlmodel.py
+++ b/qutebrowser/completion/models/urlmodel.py
@@ -56,14 +56,17 @@ def url(*, info):
     """
     model = completionmodel.CompletionModel(column_widths=(40, 50, 10))
 
-    quickmarks = ((url, name) for (name, url)
-                  in objreg.get('quickmark-manager').marks.items())
+    quickmarks = [(url, name) for (name, url)
+                  in objreg.get('quickmark-manager').marks.items()]
     bookmarks = objreg.get('bookmark-manager').marks.items()
 
-    model.add_category(listcategory.ListCategory(
-        'Quickmarks', quickmarks, delete_func=_delete_quickmark, sort=False))
-    model.add_category(listcategory.ListCategory(
-        'Bookmarks', bookmarks, delete_func=_delete_bookmark, sort=False))
+    if quickmarks:
+        model.add_category(listcategory.ListCategory(
+            'Quickmarks', quickmarks, delete_func=_delete_quickmark,
+            sort=False))
+    if bookmarks:
+        model.add_category(listcategory.ListCategory(
+            'Bookmarks', bookmarks, delete_func=_delete_bookmark, sort=False))
 
     if info.config.get('completion.web_history_max_items') != 0:
         hist_cat = histcategory.HistoryCategory(delete_func=_delete_history)


Test patch:
diff --git a/tests/unit/completion/test_completionmodel.py b/tests/unit/completion/test_completionmodel.py
--- a/tests/unit/completion/test_completionmodel.py
+++ b/tests/unit/completion/test_completionmodel.py
@@ -68,17 +68,17 @@ def test_count(counts):
     assert model.count() == sum(counts)
 
 
-@hypothesis.given(strategies.text())
-def test_set_pattern(pat):
+@hypothesis.given(pat=strategies.text())
+def test_set_pattern(pat, qtbot):
     """Validate the filtering and sorting results of set_pattern."""
     model = completionmodel.CompletionModel()
-    cats = [mock.Mock(spec=['set_pattern', 'layoutChanged',
-                            'layoutAboutToBeChanged'])
-            for _ in range(3)]
+    cats = [mock.Mock(spec=['set_pattern']) for _ in range(3)]
     for c in cats:
         c.set_pattern = mock.Mock(spec=[])
         model.add_category(c)
-    model.set_pattern(pat)
+    with qtbot.waitSignals([model.layoutAboutToBeChanged, model.layoutChanged],
+                           order='strict'):
+        model.set_pattern(pat)
     for c in cats:
         c.set_pattern.assert_called_with(pat)
 
diff --git a/tests/unit/completion/test_models.py b/tests/unit/completion/test_models.py
--- a/tests/unit/completion/test_models.py
+++ b/tests/unit/completion/test_models.py
@@ -356,6 +356,50 @@ def test_url_completion(qtmodeltester, web_history_populated,
     })
 
 
+def test_url_completion_no_quickmarks(qtmodeltester, web_history_populated,
+                                      quickmark_manager_stub, bookmarks, info):
+    """Test that the quickmark category is gone with no quickmarks."""
+    model = urlmodel.url(info=info)
+    model.set_pattern('')
+    qtmodeltester.data_display_may_return_none = True
+    qtmodeltester.check(model)
+
+    _check_completions(model, {
+        "Bookmarks": [
+            ('https://github.com', 'GitHub', None),
+            ('https://python.org', 'Welcome to Python.org', None),
+            ('http://qutebrowser.org', 'qutebrowser | qutebrowser', None),
+        ],
+        "History": [
+            ('https://github.com', 'https://github.com', '2016-05-01'),
+            ('https://python.org', 'Welcome to Python.org', '2016-03-08'),
+            ('http://qutebrowser.org', 'qutebrowser', '2015-09-05'),
+        ],
+    })
+
+
+def test_url_completion_no_bookmarks(qtmodeltester, web_history_populated,
+                                     quickmarks, bookmark_manager_stub, info):
+    """Test that the bookmarks category is gone with no bookmarks."""
+    model = urlmodel.url(info=info)
+    model.set_pattern('')
+    qtmodeltester.data_display_may_return_none = True
+    qtmodeltester.check(model)
+
+    _check_completions(model, {
+        "Quickmarks": [
+            ('https://wiki.archlinux.org', 'aw', None),
+            ('https://wikipedia.org', 'wiki', None),
+            ('https://duckduckgo.com', 'ddg', None),
+        ],
+        "History": [
+            ('https://github.com', 'https://github.com', '2016-05-01'),
+            ('https://python.org', 'Welcome to Python.org', '2016-03-08'),
+            ('http://qutebrowser.org', 'qutebrowser', '2015-09-05'),
+        ],
+    })
+
+
 @pytest.mark.parametrize('url, title, pattern, rowcount', [
     ('example.com', 'Site Title', '', 1),
     ('example.com', 'Site Title', 'ex', 1),
@@ -382,7 +426,7 @@ def test_url_completion_pattern(web_history, quickmark_manager_stub,
     model = urlmodel.url(info=info)
     model.set_pattern(pattern)
     # 2, 0 is History
-    assert model.rowCount(model.index(2, 0)) == rowcount
+    assert model.rowCount(model.index(0, 0)) == rowcount
 
 
 def test_url_completion_delete_bookmark(qtmodeltester, bookmarks,