Repository: qutebrowser/qutebrowser

Issue:
completion.web_history.exclude doesn't work when set from config.py
See Reddit: [completion.web_history.exclude isn't working or am I not setting it up correctly? : qutebrowser](https://www.reddit.com/r/qutebrowser/comments/jnzwb0/completionweb_historyexclude_isnt_working_or_am_i/)

In  qutebrowser/browser/history.py we do:

```
 194   │     @config.change_filter('completion.web_history.exclude')
 195   │     def _on_config_changed(self):
 196   │         self.metainfo['force_rebuild'] = True
```

but that change filter won't be run when `config.py` is initially loaded...

I guess a proper fix for this would need to store the exclude patterns (or a hash of them) in the database so we know we need to regenerate whenever they changed.


Discussion:
I have been testing a few solutions to this and I think I have something that works and doesn't break anything else (at least with a config.py), I'll be submitting a pull request in a while after I test with a YAML config.



Resolving patch:
diff --git a/qutebrowser/browser/history.py b/qutebrowser/browser/history.py
--- a/qutebrowser/browser/history.py
+++ b/qutebrowser/browser/history.py
@@ -79,7 +79,7 @@ class CompletionMetaInfo(sql.SqlTable):
     """Table containing meta-information for the completion."""
 
     KEYS = {
-        'force_rebuild': False,
+        'excluded_patterns': '',
     }
 
     def __init__(self, parent=None):
@@ -158,9 +158,14 @@ def __init__(self, progress, parent=None):
 
         if sql.Query('pragma user_version').run().value() < _USER_VERSION:
             self.completion.delete_all()
-        if self.metainfo['force_rebuild']:
+
+        # Get a string of all patterns
+        patterns = config.instance.get_str('completion.web_history.exclude')
+
+        # If patterns changed, update them in database and rebuild completion
+        if self.metainfo['excluded_patterns'] != patterns:
+            self.metainfo['excluded_patterns'] = patterns
             self.completion.delete_all()
-            self.metainfo['force_rebuild'] = False
 
         if not self.completion:
             # either the table is out-of-date or the user wiped it manually
@@ -183,18 +188,12 @@ def __init__(self, progress, parent=None):
                                        'ORDER BY atime desc '
                                        'limit :limit offset :offset')
 
-        config.instance.changed.connect(self._on_config_changed)
-
     def __repr__(self):
         return utils.get_repr(self, length=len(self))
 
     def __contains__(self, url):
         return self._contains_query.run(val=url).value()
 
-    @config.change_filter('completion.web_history.exclude')
-    def _on_config_changed(self):
-        self.metainfo['force_rebuild'] = True
-
     @contextlib.contextmanager
     def _handle_sql_errors(self):
         try:


Test patch:
diff --git a/tests/unit/browser/test_history.py b/tests/unit/browser/test_history.py
--- a/tests/unit/browser/test_history.py
+++ b/tests/unit/browser/test_history.py
@@ -402,30 +402,12 @@ def test_user_version(self, web_history, stubs, monkeypatch):
             ('example.com/2', '', 2),
         ]
 
-    def test_force_rebuild(self, web_history, stubs):
-        """Ensure that completion is regenerated if we force a rebuild."""
-        web_history.add_url(QUrl('example.com/1'), redirect=False, atime=1)
-        web_history.add_url(QUrl('example.com/2'), redirect=False, atime=2)
-        web_history.completion.delete('url', 'example.com/2')
-
-        hist2 = history.WebHistory(progress=stubs.FakeHistoryProgress())
-        assert list(hist2.completion) == [('example.com/1', '', 1)]
-        hist2.metainfo['force_rebuild'] = True
-
-        hist3 = history.WebHistory(progress=stubs.FakeHistoryProgress())
-        assert list(hist3.completion) == [
-            ('example.com/1', '', 1),
-            ('example.com/2', '', 2),
-        ]
-        assert not hist3.metainfo['force_rebuild']
-
     def test_exclude(self, config_stub, web_history, stubs):
         """Ensure that patterns in completion.web_history.exclude are ignored.
 
         This setting should only be used for the completion.
         """
         config_stub.val.completion.web_history.exclude = ['*.example.org']
-        assert web_history.metainfo['force_rebuild']
 
         web_history.add_url(QUrl('http://example.com'),
                             redirect=False, atime=1)
@@ -435,16 +417,35 @@ def test_exclude(self, config_stub, web_history, stubs):
         hist2 = history.WebHistory(progress=stubs.FakeHistoryProgress())
         assert list(hist2.completion) == [('http://example.com', '', 1)]
 
-    def test_unrelated_config_change(self, config_stub, web_history):
-        config_stub.val.history_gap_interval = 1234
-        assert not web_history.metainfo['force_rebuild']
+    def test_pattern_change_rebuild(self, config_stub, web_history, stubs):
+        """Ensure that completion is rebuilt when exclude patterns change."""
+        config_stub.val.completion.web_history.exclude = ['*.example.org']
+
+        web_history.add_url(QUrl('http://example.com'),
+                            redirect=False, atime=1)
+        web_history.add_url(QUrl('http://example.org'),
+                            redirect=False, atime=2)
+
+        hist2 = history.WebHistory(progress=stubs.FakeHistoryProgress())
+        assert list(hist2.completion) == [
+            ('http://example.com', '', 1),
+        ]
+
+        config_stub.val.completion.web_history.exclude = []
+
+        hist3 = history.WebHistory(progress=stubs.FakeHistoryProgress())
+        assert list(hist3.completion) == [
+            ('http://example.com', '', 1),
+            ('http://example.org', '', 2)
+        ]
 
     @pytest.mark.parametrize('patch_threshold', [True, False])
     def test_progress(self, web_history, config_stub, monkeypatch, stubs,
                       patch_threshold):
         web_history.add_url(QUrl('example.com/1'), redirect=False, atime=1)
         web_history.add_url(QUrl('example.com/2'), redirect=False, atime=2)
-        web_history.metainfo['force_rebuild'] = True
+        # Change cached patterns to trigger a completion rebuild
+        web_history.metainfo['excluded_patterns'] = 'http://example.org'
 
         if patch_threshold:
             monkeypatch.setattr(history.WebHistory, '_PROGRESS_THRESHOLD', 1)
@@ -475,12 +476,12 @@ def test_setitem_keyerror(self, metainfo):
             metainfo['does_not_exist'] = 42
 
     def test_contains(self, metainfo):
-        assert 'force_rebuild' in metainfo
+        assert 'excluded_patterns' in metainfo
 
     def test_modify(self, metainfo):
-        assert not metainfo['force_rebuild']
-        metainfo['force_rebuild'] = True
-        assert metainfo['force_rebuild']
+        assert not metainfo['excluded_patterns']
+        metainfo['excluded_patterns'] = 'https://example.com/'
+        assert metainfo['excluded_patterns']
 
 
 class TestHistoryProgress: