Merge remote-tracking branch 'origin/pr/4180' into completion
This commit is contained in:
commit
f86cd440de
|
|
@ -145,10 +145,13 @@ This updates `~/.local/share/qutebrowser/blocked-hosts` with downloaded host lis
|
|||
|
||||
[[back]]
|
||||
=== back
|
||||
Syntax: +:back [*--tab*] [*--bg*] [*--window*]+
|
||||
Syntax: +:back [*--tab*] [*--bg*] [*--window*] ['index']+
|
||||
|
||||
Go back in the history of the current tab.
|
||||
|
||||
==== positional arguments
|
||||
* +'index'+: Which page to go back to, count takes precedence.
|
||||
|
||||
==== optional arguments
|
||||
* +*-t*+, +*--tab*+: Go back in a new tab.
|
||||
* +*-b*+, +*--bg*+: Go back in a background tab.
|
||||
|
|
@ -567,10 +570,13 @@ Follow the selected text.
|
|||
|
||||
[[forward]]
|
||||
=== forward
|
||||
Syntax: +:forward [*--tab*] [*--bg*] [*--window*]+
|
||||
Syntax: +:forward [*--tab*] [*--bg*] [*--window*] ['index']+
|
||||
|
||||
Go forward in the history of the current tab.
|
||||
|
||||
==== positional arguments
|
||||
* +'index'+: Which page to go forward to, count takes precedence.
|
||||
|
||||
==== optional arguments
|
||||
* +*-t*+, +*--tab*+: Go forward in a new tab.
|
||||
* +*-b*+, +*--bg*+: Go forward in a background tab.
|
||||
|
|
|
|||
|
|
@ -689,6 +689,12 @@ class AbstractHistory:
|
|||
def _go_to_item(self, item: typing.Any) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def back_items(self) -> typing.List[typing.Any]:
|
||||
raise NotImplementedError
|
||||
|
||||
def forward_items(self) -> typing.List[typing.Any]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class AbstractElements:
|
||||
|
||||
|
|
|
|||
|
|
@ -498,7 +498,7 @@ class CommandDispatcher:
|
|||
self._tabbed_browser.close_tab(self._current_widget(),
|
||||
add_undo=False)
|
||||
|
||||
def _back_forward(self, tab, bg, window, count, forward):
|
||||
def _back_forward(self, tab, bg, window, count, forward, index=None):
|
||||
"""Helper function for :back/:forward."""
|
||||
history = self._current_widget().history
|
||||
# Catch common cases before e.g. cloning tab
|
||||
|
|
@ -512,6 +512,12 @@ class CommandDispatcher:
|
|||
else:
|
||||
widget = self._current_widget()
|
||||
|
||||
if count is None:
|
||||
if index is None:
|
||||
count = 1
|
||||
else:
|
||||
count = abs(history.current_idx() - index)
|
||||
|
||||
try:
|
||||
if forward:
|
||||
widget.history.forward(count)
|
||||
|
|
@ -522,7 +528,9 @@ class CommandDispatcher:
|
|||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('count', value=cmdutils.Value.count)
|
||||
def back(self, tab=False, bg=False, window=False, count=1):
|
||||
@cmdutils.argument('index', completion=miscmodels.back)
|
||||
def back(self, tab=False, bg=False, window=False,
|
||||
count=None, index: int = None):
|
||||
"""Go back in the history of the current tab.
|
||||
|
||||
Args:
|
||||
|
|
@ -530,12 +538,15 @@ class CommandDispatcher:
|
|||
bg: Go back in a background tab.
|
||||
window: Go back in a new window.
|
||||
count: How many pages to go back.
|
||||
index: Which page to go back to, count takes precedence.
|
||||
"""
|
||||
self._back_forward(tab, bg, window, count, forward=False)
|
||||
self._back_forward(tab, bg, window, count, forward=False, index=index)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('count', value=cmdutils.Value.count)
|
||||
def forward(self, tab=False, bg=False, window=False, count=1):
|
||||
@cmdutils.argument('index', completion=miscmodels.forward)
|
||||
def forward(self, tab=False, bg=False, window=False,
|
||||
count=None, index: int = None):
|
||||
"""Go forward in the history of the current tab.
|
||||
|
||||
Args:
|
||||
|
|
@ -543,8 +554,9 @@ class CommandDispatcher:
|
|||
bg: Go forward in a background tab.
|
||||
window: Go forward in a new window.
|
||||
count: How many pages to go forward.
|
||||
index: Which page to go forward to, count takes precedence.
|
||||
"""
|
||||
self._back_forward(tab, bg, window, count, forward=True)
|
||||
self._back_forward(tab, bg, window, count, forward=True, index=index)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('where', choices=['prev', 'next', 'up', 'increment',
|
||||
|
|
|
|||
|
|
@ -97,7 +97,14 @@ def _serialize_item(item, stream):
|
|||
## static_cast<qint64>(entry->GetTimestamp().ToInternalValue());
|
||||
# \x00\x00\x00\x00^\x97$\xe7
|
||||
stream.writeInt64(int(time.time()))
|
||||
|
||||
if item.last_visited is None:
|
||||
unix_msecs = 0
|
||||
else:
|
||||
unix_msecs = item.last_visited.toMSecsSinceEpoch()
|
||||
# 11644516800000 is the number of milliseconds from
|
||||
# 1601-01-01T00:00 (Windows NT Epoch) to 1970-01-01T00:00 (UNIX Epoch)
|
||||
nt_usecs = (unix_msecs + 11644516800000) * 1000
|
||||
stream.writeInt64(nt_usecs)
|
||||
## entry->GetHttpStatusCode();
|
||||
# \x00\x00\x00\xc8
|
||||
stream.writeInt(200)
|
||||
|
|
|
|||
|
|
@ -755,6 +755,12 @@ class WebEngineHistory(browsertab.AbstractHistory):
|
|||
self._tab.before_load_started.emit(item.url())
|
||||
self._history.goToItem(item)
|
||||
|
||||
def back_items(self):
|
||||
return self._history.backItems(self._history.count())
|
||||
|
||||
def forward_items(self):
|
||||
return self._history.forwardItems(self._history.count())
|
||||
|
||||
|
||||
class WebEngineZoom(browsertab.AbstractZoom):
|
||||
|
||||
|
|
|
|||
|
|
@ -658,6 +658,12 @@ class WebKitHistory(browsertab.AbstractHistory):
|
|||
self._tab.before_load_started.emit(item.url())
|
||||
self._history.goToItem(item)
|
||||
|
||||
def back_items(self):
|
||||
return self._history.backItems(self._history.count())
|
||||
|
||||
def forward_items(self):
|
||||
return self._history.forwardItems(self._history.count())
|
||||
|
||||
|
||||
class WebKitElements(browsertab.AbstractElements):
|
||||
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ class CompletionModel(QAbstractItemModel):
|
|||
def columnCount(self, parent=QModelIndex()):
|
||||
"""Override QAbstractItemModel::columnCount."""
|
||||
# pylint: disable=unused-argument
|
||||
return 3
|
||||
return len(self.column_widths)
|
||||
|
||||
def canFetchMore(self, parent):
|
||||
"""Override to forward the call to the categories."""
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
"""Functions that return miscellaneous completion models."""
|
||||
|
||||
import datetime
|
||||
import typing
|
||||
|
||||
from qutebrowser.config import config, configdata
|
||||
|
|
@ -216,3 +217,59 @@ def inspector_position(*, info):
|
|||
category = listcategory.ListCategory("Position (optional)", positions)
|
||||
model.add_category(category)
|
||||
return model
|
||||
|
||||
|
||||
def _qdatetime_to_completion_format(qdate):
|
||||
if not qdate.isValid():
|
||||
ts = 0
|
||||
else:
|
||||
ts = qdate.toSecsSinceEpoch()
|
||||
if ts < 0:
|
||||
ts = 0
|
||||
pydate = datetime.datetime.fromtimestamp(ts)
|
||||
return pydate.strftime(config.val.completion.timestamp_format)
|
||||
|
||||
|
||||
def _back_forward(info, go_forward):
|
||||
tab = objreg.get('tab', scope='tab', window=info.win_id, tab='current')
|
||||
history = tab.history
|
||||
current_idx = history.current_idx()
|
||||
|
||||
model = completionmodel.CompletionModel(column_widths=(5, 36, 50, 9))
|
||||
if go_forward:
|
||||
start = current_idx + 1
|
||||
items = history.forward_items()
|
||||
else:
|
||||
start = 0
|
||||
items = history.back_items()
|
||||
entries = [
|
||||
(
|
||||
str(idx),
|
||||
entry.url().toDisplayString(),
|
||||
entry.title(),
|
||||
_qdatetime_to_completion_format(entry.lastVisited())
|
||||
)
|
||||
for idx, entry in enumerate(items, start)
|
||||
]
|
||||
if not go_forward:
|
||||
# make sure the most recent is at the top for :back
|
||||
entries = reversed(entries)
|
||||
cat = listcategory.ListCategory("History", entries, sort=False)
|
||||
model.add_category(cat)
|
||||
return model
|
||||
|
||||
|
||||
def forward(*, info):
|
||||
"""A model to complete on history of the current tab.
|
||||
|
||||
Used for the :forward command.
|
||||
"""
|
||||
return _back_forward(info, go_forward=True)
|
||||
|
||||
|
||||
def back(*, info):
|
||||
"""A model to complete on history of the current tab.
|
||||
|
||||
Used for the :back command.
|
||||
"""
|
||||
return _back_forward(info, go_forward=False)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import typing
|
|||
import glob
|
||||
import shutil
|
||||
|
||||
from PyQt5.QtCore import QUrl, QObject, QPoint, QTimer
|
||||
from PyQt5.QtCore import Qt, QUrl, QObject, QPoint, QTimer, QDateTime
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
import yaml
|
||||
|
||||
|
|
@ -121,7 +121,7 @@ class TabHistoryItem:
|
|||
"""
|
||||
|
||||
def __init__(self, url, title, *, original_url=None, active=False,
|
||||
user_data=None):
|
||||
user_data=None, last_visited=None):
|
||||
self.url = url
|
||||
if original_url is None:
|
||||
self.original_url = url
|
||||
|
|
@ -130,11 +130,13 @@ class TabHistoryItem:
|
|||
self.title = title
|
||||
self.active = active
|
||||
self.user_data = user_data
|
||||
self.last_visited = last_visited
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, constructor=True, url=self.url,
|
||||
original_url=self.original_url, title=self.title,
|
||||
active=self.active, user_data=self.user_data)
|
||||
active=self.active, user_data=self.user_data,
|
||||
last_visited=self.last_visited)
|
||||
|
||||
|
||||
class SessionManager(QObject):
|
||||
|
|
@ -220,6 +222,8 @@ class SessionManager(QObject):
|
|||
# QtWebEngine
|
||||
user_data = None
|
||||
|
||||
data['last_visited'] = item.lastVisited().toString(Qt.ISODate)
|
||||
|
||||
if tab.history.current_idx() == idx:
|
||||
pos = tab.scroller.pos_px()
|
||||
data['zoom'] = tab.zoom.factor()
|
||||
|
|
@ -429,9 +433,17 @@ class SessionManager(QObject):
|
|||
histentry['original-url'].encode('ascii'))
|
||||
else:
|
||||
orig_url = url
|
||||
if histentry.get("last_visited"):
|
||||
last_visited = QDateTime.fromString(
|
||||
histentry.get("last_visited"),
|
||||
Qt.ISODate,
|
||||
)
|
||||
else:
|
||||
last_visited = None
|
||||
entry = TabHistoryItem(url=url, original_url=orig_url,
|
||||
title=histentry['title'], active=active,
|
||||
user_data=user_data)
|
||||
user_data=user_data,
|
||||
last_visited=last_visited)
|
||||
entries.append(entry)
|
||||
if active:
|
||||
new_tab.title_changed.emit(histentry['title'])
|
||||
|
|
|
|||
|
|
@ -22,10 +22,18 @@
|
|||
import collections
|
||||
import random
|
||||
import string
|
||||
import time
|
||||
from datetime import datetime
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtCore import QUrl, QDateTime
|
||||
try:
|
||||
from PyQt5.QtWebEngineWidgets import (
|
||||
QWebEngineHistory, QWebEngineHistoryItem
|
||||
)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from qutebrowser.misc import objects
|
||||
from qutebrowser.completion import completer
|
||||
|
|
@ -35,7 +43,7 @@ from qutebrowser.utils import usertypes
|
|||
|
||||
|
||||
def _check_completions(model, expected):
|
||||
"""Check that a model contains the expected items in any order.
|
||||
"""Check that a model contains the expected items in order.
|
||||
|
||||
Args:
|
||||
expected: A dict of form
|
||||
|
|
@ -59,7 +67,6 @@ def _check_completions(model, expected):
|
|||
actual[catname].append((name, desc, misc))
|
||||
assert actual == expected
|
||||
# sanity-check the column_widths
|
||||
assert len(model.column_widths) == 3
|
||||
assert sum(model.column_widths) == 100
|
||||
|
||||
|
||||
|
|
@ -1179,3 +1186,76 @@ def test_url_completion_benchmark(benchmark, info,
|
|||
model.set_pattern('ex 123')
|
||||
|
||||
benchmark(bench)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tab_with_history(fake_web_tab, tabbed_browser_stubs, info, monkeypatch):
|
||||
"""Returns a fake tab with some fake history items."""
|
||||
pytest.importorskip('PyQt5.QtWebEngineWidgets')
|
||||
tab = fake_web_tab(QUrl('https://github.com'), 'GitHub', 0)
|
||||
current_idx = 2
|
||||
monkeypatch.setattr(
|
||||
tab.history, 'current_idx',
|
||||
lambda: current_idx,
|
||||
)
|
||||
|
||||
history = []
|
||||
now = time.time()
|
||||
for url, title, ts in [
|
||||
("http://example.com/index", "list of things", now),
|
||||
("http://example.com/thing1", "thing1 detail", now+5),
|
||||
("http://example.com/thing2", "thing2 detail", now+10),
|
||||
("http://example.com/thing3", "thing3 detail", now+15),
|
||||
("http://example.com/thing4", "thing4 detail", now+20),
|
||||
]:
|
||||
entry = mock.Mock(spec=QWebEngineHistoryItem)
|
||||
entry.url.return_value = QUrl(url)
|
||||
entry.title.return_value = title
|
||||
entry.lastVisited.return_value = QDateTime.fromSecsSinceEpoch(ts)
|
||||
history.append(entry)
|
||||
tab.history._history = mock.Mock(spec=QWebEngineHistory)
|
||||
tab.history._history.items.return_value = history
|
||||
monkeypatch.setattr(
|
||||
tab.history, 'back_items',
|
||||
lambda *_args: (
|
||||
entry for idx, entry in enumerate(tab.history._history.items())
|
||||
if idx < current_idx
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
tab.history, 'forward_items',
|
||||
lambda *_args: (
|
||||
entry for idx, entry in enumerate(tab.history._history.items())
|
||||
if idx > current_idx
|
||||
),
|
||||
)
|
||||
|
||||
tabbed_browser_stubs[0].widget.tabs = [tab]
|
||||
tabbed_browser_stubs[0].widget.current_index = 0
|
||||
return tab
|
||||
|
||||
|
||||
def test_back_completion(tab_with_history, info):
|
||||
"""Test back tab history completion."""
|
||||
model = miscmodels.back(info=info)
|
||||
model.set_pattern('')
|
||||
|
||||
_check_completions(model, {
|
||||
"History": [
|
||||
("1", "http://example.com/thing1", "thing1 detail"),
|
||||
("0", "http://example.com/index", "list of things"),
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
def test_forward_completion(tab_with_history, info):
|
||||
"""Test forward tab history completion."""
|
||||
model = miscmodels.forward(info=info)
|
||||
model.set_pattern('')
|
||||
|
||||
_check_completions(model, {
|
||||
"History": [
|
||||
("3", "http://example.com/thing3", "thing3 detail"),
|
||||
("4", "http://example.com/thing4", "thing4 detail"),
|
||||
],
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue