Merge remote-tracking branch 'origin/pr/3385'
This commit is contained in:
commit
f32b4d88ba
@ -19,8 +19,6 @@
|
|||||||
|
|
||||||
"""A completion category that queries the SQL History store."""
|
"""A completion category that queries the SQL History store."""
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from PyQt5.QtSql import QSqlQueryModel
|
from PyQt5.QtSql import QSqlQueryModel
|
||||||
|
|
||||||
from qutebrowser.misc import sql
|
from qutebrowser.misc import sql
|
||||||
@ -36,21 +34,7 @@ class HistoryCategory(QSqlQueryModel):
|
|||||||
"""Create a new History completion category."""
|
"""Create a new History completion category."""
|
||||||
super().__init__(parent=parent)
|
super().__init__(parent=parent)
|
||||||
self.name = "History"
|
self.name = "History"
|
||||||
|
self._query = None
|
||||||
# replace ' in timestamp-format to avoid breaking the query
|
|
||||||
timestamp_format = config.val.completion.timestamp_format
|
|
||||||
timefmt = ("strftime('{}', last_atime, 'unixepoch', 'localtime')"
|
|
||||||
.format(timestamp_format.replace("'", "`")))
|
|
||||||
|
|
||||||
self._query = sql.Query(' '.join([
|
|
||||||
"SELECT url, title, {}".format(timefmt),
|
|
||||||
"FROM CompletionHistory",
|
|
||||||
# the incoming pattern will have literal % and _ escaped with '\'
|
|
||||||
# we need to tell sql to treat '\' as an escape character
|
|
||||||
"WHERE ((url || title) LIKE :pat escape '\\')",
|
|
||||||
self._atime_expr(),
|
|
||||||
"ORDER BY last_atime DESC",
|
|
||||||
]), forward_only=False)
|
|
||||||
|
|
||||||
# advertise that this model filters by URL and title
|
# advertise that this model filters by URL and title
|
||||||
self.columns_to_filter = [0, 1]
|
self.columns_to_filter = [0, 1]
|
||||||
@ -86,11 +70,36 @@ class HistoryCategory(QSqlQueryModel):
|
|||||||
# escape to treat a user input % or _ as a literal, not a wildcard
|
# escape to treat a user input % or _ as a literal, not a wildcard
|
||||||
pattern = pattern.replace('%', '\\%')
|
pattern = pattern.replace('%', '\\%')
|
||||||
pattern = pattern.replace('_', '\\_')
|
pattern = pattern.replace('_', '\\_')
|
||||||
# treat spaces as wildcards to match any of the typed words
|
words = ['%{}%'.format(w) for w in pattern.split(' ')]
|
||||||
pattern = re.sub(r' +', '%', pattern)
|
|
||||||
pattern = '%{}%'.format(pattern)
|
# build a where clause to match all of the words in any order
|
||||||
|
# given the search term "a b", the WHERE clause would be:
|
||||||
|
# ((url || title) LIKE '%a%') AND ((url || title) LIKE '%b%')
|
||||||
|
where_clause = ' AND '.join(
|
||||||
|
"(url || title) LIKE :{} escape '\\'".format(i)
|
||||||
|
for i in range(len(words)))
|
||||||
|
|
||||||
|
# replace ' in timestamp-format to avoid breaking the query
|
||||||
|
timestamp_format = config.val.completion.timestamp_format
|
||||||
|
timefmt = ("strftime('{}', last_atime, 'unixepoch', 'localtime')"
|
||||||
|
.format(timestamp_format.replace("'", "`")))
|
||||||
|
|
||||||
|
if not self._query or len(words) != len(self._query.boundValues()):
|
||||||
|
# if the number of words changed, we need to generate a new query
|
||||||
|
# otherwise, we can reuse the prepared query for performance
|
||||||
|
self._query = sql.Query(' '.join([
|
||||||
|
"SELECT url, title, {}".format(timefmt),
|
||||||
|
"FROM CompletionHistory",
|
||||||
|
# the incoming pattern will have literal % and _ escaped
|
||||||
|
# we need to tell sql to treat '\' as an escape character
|
||||||
|
'WHERE ({})'.format(where_clause),
|
||||||
|
self._atime_expr(),
|
||||||
|
"ORDER BY last_atime DESC",
|
||||||
|
]), forward_only=False)
|
||||||
|
|
||||||
with debug.log_time('sql', 'Running completion query'):
|
with debug.log_time('sql', 'Running completion query'):
|
||||||
self._query.run(pat=pattern)
|
self._query.run(**{
|
||||||
|
str(i): w for i, w in enumerate(words)})
|
||||||
self.setQuery(self._query)
|
self.setQuery(self._query)
|
||||||
|
|
||||||
def removeRows(self, row, _count, _parent=None):
|
def removeRows(self, row, _count, _parent=None):
|
||||||
|
@ -61,7 +61,7 @@ def hist(init_sql, config_stub):
|
|||||||
|
|
||||||
('foo bar',
|
('foo bar',
|
||||||
[('foo', ''), ('bar foo', ''), ('xfooyybarz', '')],
|
[('foo', ''), ('bar foo', ''), ('xfooyybarz', '')],
|
||||||
[('xfooyybarz', '')]),
|
[('bar foo', ''), ('xfooyybarz', '')]),
|
||||||
|
|
||||||
('foo%bar',
|
('foo%bar',
|
||||||
[('foo%bar', ''), ('foo bar', ''), ('foobar', '')],
|
[('foo%bar', ''), ('foo bar', ''), ('foobar', '')],
|
||||||
@ -93,6 +93,38 @@ def test_set_pattern(pattern, before, after, model_validator, hist):
|
|||||||
model_validator.validate(after)
|
model_validator.validate(after)
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_pattern_repeated(model_validator, hist):
|
||||||
|
"""Validate multiple subsequent calls to set_pattern."""
|
||||||
|
hist.insert({'url': 'example.com/foo', 'title': 'title1', 'last_atime': 1})
|
||||||
|
hist.insert({'url': 'example.com/bar', 'title': 'title2', 'last_atime': 1})
|
||||||
|
hist.insert({'url': 'example.com/baz', 'title': 'title3', 'last_atime': 1})
|
||||||
|
cat = histcategory.HistoryCategory()
|
||||||
|
model_validator.set_model(cat)
|
||||||
|
|
||||||
|
cat.set_pattern('b')
|
||||||
|
model_validator.validate([
|
||||||
|
('example.com/bar', 'title2'),
|
||||||
|
('example.com/baz', 'title3'),
|
||||||
|
])
|
||||||
|
|
||||||
|
cat.set_pattern('ba')
|
||||||
|
model_validator.validate([
|
||||||
|
('example.com/bar', 'title2'),
|
||||||
|
('example.com/baz', 'title3'),
|
||||||
|
])
|
||||||
|
|
||||||
|
cat.set_pattern('ba ')
|
||||||
|
model_validator.validate([
|
||||||
|
('example.com/bar', 'title2'),
|
||||||
|
('example.com/baz', 'title3'),
|
||||||
|
])
|
||||||
|
|
||||||
|
cat.set_pattern('ba z')
|
||||||
|
model_validator.validate([
|
||||||
|
('example.com/baz', 'title3'),
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('max_items, before, after', [
|
@pytest.mark.parametrize('max_items, before, after', [
|
||||||
(-1, [
|
(-1, [
|
||||||
('a', 'a', '2017-04-16'),
|
('a', 'a', '2017-04-16'),
|
||||||
|
@ -414,12 +414,12 @@ def test_url_completion_no_bookmarks(qtmodeltester, web_history_populated,
|
|||||||
('example.com', 'Site Title', 'am', 1),
|
('example.com', 'Site Title', 'am', 1),
|
||||||
('example.com', 'Site Title', 'com', 1),
|
('example.com', 'Site Title', 'com', 1),
|
||||||
('example.com', 'Site Title', 'ex com', 1),
|
('example.com', 'Site Title', 'ex com', 1),
|
||||||
('example.com', 'Site Title', 'com ex', 0),
|
('example.com', 'Site Title', 'com ex', 1),
|
||||||
('example.com', 'Site Title', 'ex foo', 0),
|
('example.com', 'Site Title', 'ex foo', 0),
|
||||||
('example.com', 'Site Title', 'foo com', 0),
|
('example.com', 'Site Title', 'foo com', 0),
|
||||||
('example.com', 'Site Title', 'exm', 0),
|
('example.com', 'Site Title', 'exm', 0),
|
||||||
('example.com', 'Site Title', 'Si Ti', 1),
|
('example.com', 'Site Title', 'Si Ti', 1),
|
||||||
('example.com', 'Site Title', 'Ti Si', 0),
|
('example.com', 'Site Title', 'Ti Si', 1),
|
||||||
('example.com', '', 'foo', 0),
|
('example.com', '', 'foo', 0),
|
||||||
('foo_bar', '', '_', 1),
|
('foo_bar', '', '_', 1),
|
||||||
('foobar', '', '_', 0),
|
('foobar', '', '_', 0),
|
||||||
|
Loading…
Reference in New Issue
Block a user